Merge pull request #3864 from crazyserver/MOBILE-3947

Mobile 3947
main
Noel De Martin 2023-11-28 13:50:36 +01:00 committed by GitHub
commit 89123e0df7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 140 additions and 164 deletions

View File

@ -10,8 +10,5 @@ module.exports = {
'!src/testing/**/*', '!src/testing/**/*',
], ],
transformIgnorePatterns: ['node_modules/(?!@stencil|@angular|@ionic|@moodlehq|@ngx-translate|swiper)'], transformIgnorePatterns: ['node_modules/(?!@stencil|@angular|@ionic|@moodlehq|@ngx-translate|swiper)'],
moduleNameMapper: { moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
...pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
'^!raw-loader!.*': 'jest-raw-loader',
},
}; };

7
package-lock.json generated
View File

@ -141,7 +141,6 @@
"gulp-slash": "^1.1.3", "gulp-slash": "^1.1.3",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-preset-angular": "^13.1.4", "jest-preset-angular": "^13.1.4",
"jest-raw-loader": "^1.0.1",
"jsonc-parser": "^2.3.1", "jsonc-parser": "^2.3.1",
"keytar": "^7.2.0", "keytar": "^7.2.0",
"minimatch": "^5.1.0", "minimatch": "^5.1.0",
@ -18822,12 +18821,6 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"dev": true "dev": true
}, },
"node_modules/jest-raw-loader": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/jest-raw-loader/-/jest-raw-loader-1.0.1.tgz",
"integrity": "sha512-g9oaAjeC4/rIJk1Wd3RxVbOfMizowM7LSjEJqa4R9qDX0OjQNABXOhH+GaznUp+DjTGVPi2vPPbQXyX87DOnYg==",
"dev": true
},
"node_modules/jest-regex-util": { "node_modules/jest-regex-util": {
"version": "29.6.3", "version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",

View File

@ -176,7 +176,6 @@
"gulp-slash": "^1.1.3", "gulp-slash": "^1.1.3",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-preset-angular": "^13.1.4", "jest-preset-angular": "^13.1.4",
"jest-raw-loader": "^1.0.1",
"jsonc-parser": "^2.3.1", "jsonc-parser": "^2.3.1",
"keytar": "^7.2.0", "keytar": "^7.2.0",
"minimatch": "^5.1.0", "minimatch": "^5.1.0",

View File

@ -29,8 +29,14 @@
<ion-label position="stacked"> <ion-label position="stacked">
<p class="item-heading" [core-mark-required]="true">{{ 'core.date' | translate }}</p> <p class="item-heading" [core-mark-required]="true">{{ 'core.date' | translate }}</p>
</ion-label> </ion-label>
<ion-datetime formControlName="timestart" [placeholder]="'core.date' | translate" [displayFormat]="dateFormat" <ion-datetime-button datetime="timestart" />
[max]="maxDate" [min]="minDate" [displayTimezone]="displayTimezone" /> <ion-modal [keepContentsMounted]="true">
<ng-template>
<ion-datetime id="timestart" formControlName="timestart" presentation="date-time" [max]="maxDate" [min]="minDate">
<span slot="title">{{'core.date' | translate}}</span>
</ion-datetime>
</ng-template>
</ion-modal>
<core-input-errors [control]="form.controls.timestart" [errorMessages]="errors" /> <core-input-errors [control]="form.controls.timestart" [errorMessages]="errors" />
</ion-item> </ion-item>
@ -154,9 +160,15 @@
</ion-item> </ion-item>
<ion-item *ngIf="form.controls.duration.value === 1"> <ion-item *ngIf="form.controls.duration.value === 1">
<ion-label position="stacked" /> <ion-label position="stacked" />
<ion-datetime formControlName="timedurationuntil" [max]="maxDate" [min]="minDate" <ion-datetime-button datetime="timedurationuntil" />
[placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat" <ion-modal [keepContentsMounted]="true">
[displayTimezone]="displayTimezone" /> <ng-template>
<ion-datetime id="timedurationuntil" formControlName="timedurationuntil" [max]="maxDate" [min]="minDate"
presentation="date-time">
<span slot="title">{{'addon.calendar.durationuntil' | translate}}</span>
</ion-datetime>
</ng-template>
</ion-modal>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label> <ion-label>

View File

@ -45,7 +45,6 @@ import { CoreForms } from '@singletons/form';
import { CoreReminders, CoreRemindersService, CoreRemindersUnits } from '@features/reminders/services/reminders'; import { CoreReminders, CoreRemindersService, CoreRemindersUnits } from '@features/reminders/services/reminders';
import { CoreRemindersSetReminderMenuComponent } from '@features/reminders/components/set-reminder-menu/set-reminder-menu'; import { CoreRemindersSetReminderMenuComponent } from '@features/reminders/components/set-reminder-menu/set-reminder-menu';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { CoreAppProvider } from '@services/app';
/** /**
* Page that displays a form to create/edit an event. * Page that displays a form to create/edit an event.
@ -61,7 +60,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
@ViewChild('editEventForm') formElement!: ElementRef; @ViewChild('editEventForm') formElement!: ElementRef;
title = 'addon.calendar.newevent'; title = 'addon.calendar.newevent';
dateFormat: string;
component = AddonCalendarProvider.COMPONENT; component = AddonCalendarProvider.COMPONENT;
loaded = false; loaded = false;
hasOffline = false; hasOffline = false;
@ -78,7 +76,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
eventId?: number; eventId?: number;
maxDate: string; maxDate: string;
minDate: string; minDate: string;
displayTimezone?: string;
// Form variables. // Form variables.
form: FormGroup; form: FormGroup;
@ -107,11 +104,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
required: Translate.instant('core.required'), required: Translate.instant('core.required'),
}; };
// Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them.
this.dateFormat = CoreTimeUtils.convertPHPToMoment(Translate.instant('core.strftimedatetimeshort'))
.replace(/[[\]]/g, '');
this.displayTimezone = CoreAppProvider.getForcedTimezone();
this.form = new FormGroup({}); this.form = new FormGroup({});
// Initialize form variables. // Initialize form variables.

View File

@ -1,12 +1,18 @@
<span *ngIf="inputMode && form" [formGroup]="form"> <span *ngIf="inputMode && form && searchFields" [formGroup]="form">
<span *ngIf="editMode" [core-mark-required]="field.required" class="core-mark-required"></span> <span *ngIf="editMode" [core-mark-required]="field.required" class="core-mark-required"></span>
<ion-datetime [formControlName]="'f_'+field.id" [placeholder]="'core.date' | translate" [max]="maxDate" [min]="minDate"
[disabled]="searchMode && !searchFields!['f_'+field.id+'_z']" [displayFormat]="format" [displayTimezone]="displayTimezone" /> <ion-datetime-button datetime="datetime" />
<ion-modal [keepContentsMounted]="true">
<ng-template>
<ion-datetime id="datetime" [formControlName]="'f_'+field.id" [max]="maxDate" [min]="minDate"
[disabled]="searchMode && !searchFields['f_'+field.id+'_z']" presentation="date" />
</ng-template>
</ion-modal>
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" /> <core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" />
<ion-item *ngIf="searchMode"> <ion-item *ngIf="searchMode">
<ion-label>{{ 'addon.mod_data.usedate' | translate }}</ion-label> <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-checkbox slot="end" [formControlName]="'f_'+field.id+'_z'" [(ngModel)]="searchFields['f_'+field.id+'_z']" />
</ion-item> </ion-item>
</span> </span>

View File

@ -13,9 +13,7 @@
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { CoreAppProvider } from '@services/app';
import { CoreTimeUtils } from '@services/utils/time'; import { CoreTimeUtils } from '@services/utils/time';
import { Translate } from '@singletons';
import moment, { Moment } from 'moment-timezone'; import moment, { Moment } from 'moment-timezone';
import { AddonModDataFieldPluginBaseComponent } from '../../../classes/base-field-plugin-component'; import { AddonModDataFieldPluginBaseComponent } from '../../../classes/base-field-plugin-component';
@ -28,11 +26,9 @@ import { AddonModDataFieldPluginBaseComponent } from '../../../classes/base-fiel
}) })
export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginBaseComponent { export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginBaseComponent {
format!: string;
displayDate?: number; displayDate?: number;
maxDate?: string; maxDate?: string;
minDate?: string; minDate?: string;
displayTimezone?: string;
/** /**
* @inheritdoc * @inheritdoc
@ -48,23 +44,18 @@ export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginBaseC
let momentInstance: Moment; let momentInstance: Moment;
// Calculate format to use.
this.format = CoreTimeUtils.fixFormatForDatetime(CoreTimeUtils.convertPHPToMoment(
Translate.instant('core.strftimedate'),
));
this.maxDate = CoreTimeUtils.getDatetimeDefaultMax(); this.maxDate = CoreTimeUtils.getDatetimeDefaultMax();
this.minDate = CoreTimeUtils.getDatetimeDefaultMin(); this.minDate = CoreTimeUtils.getDatetimeDefaultMin();
this.displayTimezone = CoreAppProvider.getForcedTimezone();
if (this.searchMode) { if (this.searchMode && this.searchFields) {
this.addControl('f_' + this.field.id + '_z'); this.addControl('f_' + this.field.id + '_z');
momentInstance = this.searchFields!['f_' + this.field.id + '_y'] momentInstance = this.searchFields['f_' + this.field.id + '_y']
? moment(this.searchFields!['f_' + this.field.id + '_y'] + '-' + ? moment(this.searchFields['f_' + this.field.id + '_y'] + '-' +
this.searchFields!['f_' + this.field.id + '_m'] + '-' + this.searchFields!['f_' + this.field.id + '_d']) this.searchFields['f_' + this.field.id + '_m'] + '-' + this.searchFields['f_' + this.field.id + '_d'])
: moment(); : moment();
this.searchFields!['f_' + this.field.id] = CoreTimeUtils.toDatetimeFormat(momentInstance.unix() * 1000); this.searchFields['f_' + this.field.id] = CoreTimeUtils.toDatetimeFormat(momentInstance.unix() * 1000);
} else { } else {
momentInstance = this.value?.content momentInstance = this.value?.content
? moment(parseInt(this.value.content, 10) * 1000) ? moment(parseInt(this.value.content, 10) * 1000)

View File

@ -18,7 +18,12 @@
[courseId]="courseId" [wsNotFiltered]="true" /> [courseId]="courseId" [wsNotFiltered]="true" />
</span> </span>
</ion-label> </ion-label>
<ion-datetime [formControlName]="modelName" [placeholder]="'core.choosedots' | translate" [displayFormat]="format" [max]="max"
[min]="min" [monthNames]="monthNames" [displayTimezone]="displayTimezone" /> <ion-datetime-button datetime="datetime" />
<ion-modal [keepContentsMounted]="true">
<ng-template>
<ion-datetime id="datetime" [formControlName]="modelName" [presentation]="ionDateTimePresentation" [max]="max" [min]="min" />
</ng-template>
</ion-modal>
<core-input-errors [control]="form.controls[modelName]" /> <core-input-errors [control]="form.controls[modelName]" />
</ion-item> </ion-item>

View File

@ -19,10 +19,7 @@ import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper'; import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
import { CoreUserProfileField } from '@features/user/services/user'; import { CoreUserProfileField } from '@features/user/services/user';
import { Translate } from '@singletons';
import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-profilefield-component'; import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-profilefield-component';
import { CoreLang } from '@services/lang';
import { CoreAppProvider } from '@services/app';
/** /**
* Directive to render a datetime user profile field. * Directive to render a datetime user profile field.
@ -33,13 +30,11 @@ import { CoreAppProvider } from '@services/app';
}) })
export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent { export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent {
format?: string; ionDateTimePresentation = 'date';
min?: string; min?: string;
max?: string; max?: string;
valueNumber?: number; valueNumber?: number;
displayValue?: string; displayValue?: string;
monthNames?: string[];
displayTimezone?: string;
/** /**
* Init the data when the field is meant to be displayed without editing. * Init the data when the field is meant to be displayed without editing.
@ -64,16 +59,11 @@ export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileField
protected initForEdit(field: AuthEmailSignupProfileField): void { protected initForEdit(field: AuthEmailSignupProfileField): void {
super.initForEdit(field); super.initForEdit(field);
this.monthNames = CoreLang.getMonthNames();
this.displayTimezone = CoreAppProvider.getForcedTimezone();
// Check if it's only date or it has time too. // Check if it's only date or it has time too.
const hasTime = CoreUtils.isTrueOrOne(field.param3); const hasTime = CoreUtils.isTrueOrOne(field.param3);
// Calculate format to use. // Calculate format to use.
this.format = CoreTimeUtils.fixFormatForDatetime(CoreTimeUtils.convertPHPToMoment( this.ionDateTimePresentation = hasTime ? 'date-time' : 'date';
Translate.instant('core.' + (hasTime ? 'strftimedatetime' : 'strftimedate')),
));
// Check min value. // Check min value.
if (field.param1 && Number(field.param1)) { if (field.param1 && Number(field.param1)) {

View File

@ -52,6 +52,8 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
IonicModule.forRoot( IonicModule.forRoot(
{ {
navAnimation: moodleTransitionAnimation, navAnimation: moodleTransitionAnimation,
innerHTMLTemplatesEnabled: true,
sanitizerEnabled: true,
}, },
), ),
HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content. HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.

View File

@ -15,8 +15,6 @@
import { Component, ElementRef, Input, OnChanges, OnInit } from '@angular/core'; import { Component, ElementRef, Input, OnChanges, OnInit } from '@angular/core';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreForms } from '@singletons/form'; import { CoreForms } from '@singletons/form';
import ChevronUpSVG from '!raw-loader!ionicons/dist/svg/chevron-up.svg';
import ChevronDownSVG from '!raw-loader!ionicons/dist/svg/chevron-down.svg';
/** /**
* Component to show error details. * Component to show error details.
@ -54,11 +52,11 @@ export class CoreErrorInfoComponent implements OnInit, OnChanges {
<label for="${toggleId}" class="core-error-info--toggle" aria-hidden="true"> <label for="${toggleId}" class="core-error-info--toggle" aria-hidden="true">
<span class="core-error-info--hide-content"> <span class="core-error-info--hide-content">
${hideDetailsLabel} ${hideDetailsLabel}
${ChevronUpSVG} <ion-icon name="chevron-up" />
</span> </span>
<span class="core-error-info--show-content"> <span class="core-error-info--show-content">
${showDetailsLabel} ${showDetailsLabel}
${ChevronDownSVG} <ion-icon name="chevron-down" />
</span> </span>
</label> </label>
</div> </div>

View File

@ -1,7 +1,8 @@
:host { :host {
display: contents;
.core-input-error-container {
width: 100%; width: 100%;
.core-input-error-container {
.core-input-error { .core-input-error {
padding: 4px; padding: 4px;
color: var(--danger); color: var(--danger);

View File

@ -2,6 +2,7 @@
:host { :host {
--backdrop-opacity: var(--ion-backdrop-opacity, 0.4); --backdrop-opacity: var(--ion-backdrop-opacity, 0.4);
--border-radius: var(--modal-radius);
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -17,7 +18,7 @@
} }
.sheet-modal--wrapper { .sheet-modal--wrapper {
border-radius: var(--radius-lg) var(--radius-lg) 0 0; border-radius: var(--border-radius) var(--border-radius) 0 0;
@include padding(24px, 16px, 24px, 16px); @include padding(24px, 16px, 24px, 16px);
background-color: var(--ion-overlay-background-color, var(--ion-background-color, #fff)); background-color: var(--ion-overlay-background-color, var(--ion-background-color, #fff));

View File

@ -92,7 +92,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
} }
/** /**
* Detect and act upon changes that Angular cant or wont detect on its own (objects and arrays). * @inheritdoc
*/ */
ngDoCheck(): void { ngDoCheck(): void {
if (!this.componentInstance || this.creatingComponent) { if (!this.componentInstance || this.creatingComponent) {
@ -111,7 +111,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
} }
/** /**
* Detect changes on input properties. * @inheritdoc
*/ */
async ngOnChanges(changes: Record<string, SimpleChange>): Promise<void> { async ngOnChanges(changes: Record<string, SimpleChange>): Promise<void> {
// Only compile if text/javascript has changed or the forceCompile flag has been set to true. // Only compile if text/javascript has changed or the forceCompile flag has been set to true.
@ -148,7 +148,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
} }
/** /**
* Component destroyed. * @inheritdoc
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.componentRef?.destroy(); this.componentRef?.destroy();

View File

@ -81,5 +81,9 @@ ion-item.item {
&.restricted { &.restricted {
font-size: var(--text-size); font-size: var(--text-size);
} }
&[role=button] {
min-height: auto;
min-width: auto;
}
} }
} }

View File

@ -459,8 +459,8 @@ export class CoreLoginSitePage implements OnInit {
}); });
if (errorDetails) { if (errorDetails) {
// Avoid sanitizing JS.
const containerElement = alertElement.querySelector('.core-error-info-container'); const containerElement = alertElement.querySelector('.core-error-info-container');
if (containerElement) { if (containerElement) {
containerElement.innerHTML = CoreErrorInfoComponent.render(errorDetails, errorCode); containerElement.innerHTML = CoreErrorInfoComponent.render(errorDetails, errorCode);
} }

View File

@ -27,6 +27,7 @@ import { CorePlatform } from '@services/platform';
import { AddonFilterMultilangHandler } from '@addons/filter/multilang/services/handlers/multilang'; import { AddonFilterMultilangHandler } from '@addons/filter/multilang/services/handlers/multilang';
import { AddonFilterMultilang2Handler } from '@addons/filter/multilang2/services/handlers/multilang2'; import { AddonFilterMultilang2Handler } from '@addons/filter/multilang2/services/handlers/multilang2';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { CoreLogger } from '@singletons/logger';
/* /*
* Service to handle language features, like changing the current language. * Service to handle language features, like changing the current language.
@ -40,6 +41,11 @@ export class CoreLangProvider {
protected customStrings: CoreLanguageObject = {}; // Strings defined using the admin tool. protected customStrings: CoreLanguageObject = {}; // Strings defined using the admin tool.
protected customStringsRaw?: string; protected customStringsRaw?: string;
protected sitePluginsStrings: CoreLanguageObject = {}; // Strings defined by site plugins. protected sitePluginsStrings: CoreLanguageObject = {}; // Strings defined by site plugins.
protected logger: CoreLogger;
constructor() {
this.logger = CoreLogger.getInstance('CoreLang');
}
async initialize(): Promise<void> { async initialize(): Promise<void> {
// Set fallback language and language to use until the app determines the right language to use. // Set fallback language and language to use until the app determines the right language to use.
@ -171,13 +177,21 @@ export class CoreLangProvider {
// Use british english when parent english is loaded. // Use british english when parent english is loaded.
moment.locale(language == 'en' ? 'en-gb' : language); moment.locale(language == 'en' ? 'en-gb' : language);
const previousLanguage = this.currentLanguage ?? this.getDefaultLanguage();
this.currentLanguage = language; this.currentLanguage = language;
try { try {
await Promise.all([ await this.reloadLanguageStrings();
this.reloadLanguageStrings(), await CoreConfig.set('current_language', language);
CoreConfig.set('current_language', language), } catch (error) {
]); if (language !== previousLanguage) {
this.logger.error(`Language ${language} not available, reverting to ${previousLanguage}`, error);
return this.changeCurrentLanguage(previousLanguage);
}
throw error;
} finally { } finally {
// Load the custom and site plugins strings for the language. // Load the custom and site plugins strings for the language.
if (this.loadLangStrings(this.customStrings, language) || this.loadLangStrings(this.sitePluginsStrings, language)) { if (this.loadLangStrings(this.customStrings, language) || this.loadLangStrings(this.sitePluginsStrings, language)) {

View File

@ -815,14 +815,20 @@ export class CoreDomUtilsProvider {
* @returns Promise resolved with the alert modal. * @returns Promise resolved with the alert modal.
*/ */
async showAlertWithOptions(options: AlertOptions = {}, autocloseTime?: number): Promise<HTMLIonAlertElement> { async showAlertWithOptions(options: AlertOptions = {}, autocloseTime?: number): Promise<HTMLIonAlertElement> {
const hasHTMLTags = CoreTextUtils.hasHTMLTags(<string> options.message || ''); let message = typeof options.message == 'string'
? options.message
: options.message?.value || '';
const hasHTMLTags = CoreTextUtils.hasHTMLTags(message);
if (hasHTMLTags && !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.7')) { if (hasHTMLTags && !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.7')) {
// Treat multilang. // Treat multilang.
options.message = await CoreLang.filterMultilang(<string> options.message); message = await CoreLang.filterMultilang(message);
} }
const alertId = <string> Md5.hashAsciiStr((options.header || '') + '#' + (options.message || '')); options.message = message;
const alertId = Md5.hashAsciiStr((options.header || '') + '#' + (message|| ''));
if (this.displayedAlerts[alertId]) { if (this.displayedAlerts[alertId]) {
// There's already an alert with the same message and title. Return it. // There's already an alert with the same message and title. Return it.

View File

@ -117,7 +117,6 @@ export interface AsyncInstanceWrapper<
resetInstance(): void; resetInstance(): void;
} }
// eslint-disable-next-line @typescript-eslint/ban-types
export type AsyncObject = object; export type AsyncObject = object;
/** /**

View File

@ -518,7 +518,7 @@ ion-alert .alert-button.timed-button{
} }
ion-alert { ion-alert {
--border-radius: var(--radius-xl); --border-radius: var(--modal-radius);
&.md, &.ios { &.md, &.ios {
--max-width: 80%; --max-width: 80%;
@ -545,7 +545,7 @@ ion-alert {
} }
ion-loading { ion-loading {
--border-radius: var(--radius-xl); --border-radius: var(--modal-radius);
.loading-wrapper { .loading-wrapper {
border-radius: var(--border-radius) !important; border-radius: var(--border-radius) !important;
@ -1585,37 +1585,6 @@ ion-item.item-input.item-multiple-inputs {
} }
} }
// Focus highlight for accessibility.
.ion-focused:not(.item-multiple-inputs):not(:focus),
ion-input.has-focus,
ion-card:focus {
@include core-focus();
}
.ion-focused.item-multiple-inputs,
.ion-focused.ion-activatable {
ion-toggle:focus-within,
ion-select:focus-within,
ion-checkbox:focus-within,
ion-radio:focus-within {
@include core-focus();
}
}
// Treat cases where there's a focusable element inside an item, like a button.
ion-item.item-input:not(.item-multiple-inputs):not(:focus),
ion-item.item-has-focus:not(.item-multiple-inputs):not(:focus),
ion-item.item-input ion-input.has-focus {
position: relative;
&::after {
box-shadow: revert;
opacity: revert;
z-index: revert;
}
.item-highlight, .item-inner-highlight {
position: unset;
}
}
ion-item-divider.item, ion-item-divider.item,
ion-item.item.divider { ion-item.item.divider {
min-height: var(--min-height); min-height: var(--min-height);
@ -1653,6 +1622,42 @@ ion-item.item {
outline: none; outline: none;
} }
// Focus highlight for accessibility.
.ion-focused:not(.item-multiple-inputs):not(:focus),
ion-input.has-focus,
ion-card:focus {
@include core-focus();
:focus-visible,
.clickable:focus {
box-shadow: none;
}
}
.ion-focused.item-multiple-inputs {
ion-toggle:focus-within,
ion-select:focus-within,
ion-checkbox:focus-within,
ion-radio:focus-within {
@include core-focus();
}
}
// Treat cases where there's a focusable element inside an item, like a button.
ion-item.item-input:not(.item-multiple-inputs):not(:focus),
ion-item.item-has-focus:not(.item-multiple-inputs):not(:focus),
ion-item.item-input ion-input.has-focus {
position: relative;
&::after {
box-shadow: revert;
opacity: revert;
z-index: revert;
}
.item-highlight, .item-inner-highlight {
position: unset;
}
}
textarea, button, select, input, a, .clickable { textarea, button, select, input, a, .clickable {
&:focus { &:focus {
@include core-focus-style(); @include core-focus-style();
@ -1660,10 +1665,6 @@ textarea, button, select, input, a, .clickable {
} }
} }
.ion-focused:not(.item-multiple-inputs):not(:focus) .clickable:focus {
box-shadow: none;
}
ion-loading:focus-visible, ion-loading:focus-visible,
ion-alert:focus-visible, ion-alert:focus-visible,
ion-popover:focus-visible, ion-popover:focus-visible,
@ -1672,11 +1673,6 @@ ion-modal:focus-visible {
border-radius: 0; border-radius: 0;
} }
input {
--placeholder-color: var(--ion-placeholder-color);
--placeholder-opacity: .85;
}
ion-input .native-input { ion-input .native-input {
&:focus, &:focus-visible { &:focus, &:focus-visible {
box-shadow: none; box-shadow: none;
@ -1684,6 +1680,11 @@ ion-input .native-input {
} }
} }
input {
--placeholder-color: var(--ion-placeholder-color);
--placeholder-opacity: .85;
}
// Disable scroll on parent ion contents to enabled PTR on the ones inside the splitview. See split-view component for more info. // Disable scroll on parent ion contents to enabled PTR on the ones inside the splitview. See split-view component for more info.
ion-content.disable-scroll-y::part(scroll) { ion-content.disable-scroll-y::part(scroll) {
touch-action: auto; touch-action: auto;
@ -1916,6 +1917,9 @@ ion-modal {
--border-radius: var(--modal-radius); --border-radius: var(--modal-radius);
} }
} }
&.ion-datetime-button-overlay {
--border-radius: var(--modal-radius);
}
&.core-modal-lateral, &.core-modal-lateral,
&.core-modal-fullscreen { &.core-modal-fullscreen {
@ -1973,3 +1977,10 @@ ion-popover {
[hidden] { [hidden] {
display: none !important; display: none !important;
} }
// Ion Datetime
ion-item.item-label-stacked ion-datetime-button {
margin-top: 8px;
margin-bottom: 8px;
align-self: self-end;
}

View File

@ -1,27 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { UrlTree } from '@angular/router';
import { NavigationOptions } from '@ionic/angular/common/providers/nav-controller';
declare module '@ionic/angular' {
export class NavController {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
navigateForward(url: string | UrlTree | any[], options?: NavigationOptions): Promise<boolean | null>;
}
}

19
src/types/raw.d.ts vendored
View File

@ -1,19 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
declare module '!raw-loader!*' {
const contents: string;
export = contents;
}

View File

@ -9,6 +9,7 @@ For more information about upgrading, read the official documentation: https://m
- Renamed CoreLoginSitesComponent to CoreLoginSitesModalComponent to make it clear that it's a modal and to avoid confusing it with the new CoreSitesListComponent. - Renamed CoreLoginSitesComponent to CoreLoginSitesModalComponent to make it clear that it's a modal and to avoid confusing it with the new CoreSitesListComponent.
- Removed CoreToLocaleStringPipe deprecated since 3.6.0 - 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 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
=== 4.3.0 === === 4.3.0 ===