commit
75cedafa33
|
@ -237,6 +237,7 @@
|
|||
"addon.messages.blocknoncontacts": "message",
|
||||
"addon.messages.blockuser": "message",
|
||||
"addon.messages.blockuserconfirm": "message",
|
||||
"addon.messages.cantblockuser": "message",
|
||||
"addon.messages.contactableprivacy": "message",
|
||||
"addon.messages.contactableprivacy_coursemember": "message",
|
||||
"addon.messages.contactableprivacy_onlycontacts": "message",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"blocknoncontacts": "Prevent non-contacts from messaging me",
|
||||
"blockuser": "Block user",
|
||||
"blockuserconfirm": "Are you sure you want to block {{$a}}?",
|
||||
"cantblockuser": "You can't block {{$a}} because they have a role with permission to message all users.",
|
||||
"contactableprivacy": "Accept messages from:",
|
||||
"contactableprivacy_coursemember": "My contacts and anyone in my courses",
|
||||
"contactableprivacy_onlycontacts": "My contacts only",
|
||||
|
|
|
@ -1446,6 +1446,12 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
|
|||
throw new CoreError('No member selected to be blocked.');
|
||||
}
|
||||
|
||||
if (this.otherMember.canmessageevenifblocked) {
|
||||
CoreDomUtils.showErrorModal(Translate.instant('addon.messages.cantblockuser', { $a: this.otherMember.fullname }));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const template = Translate.instant('addon.messages.blockuserconfirm', { $a: this.otherMember.fullname });
|
||||
const okText = Translate.instant('addon.messages.blockuser');
|
||||
|
||||
|
|
|
@ -407,8 +407,8 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async sync(): Promise<void> {
|
||||
await AddonModAssignSync.syncAssign(this.assign!.id);
|
||||
protected sync(): Promise<AddonModAssignSyncResult> {
|
||||
return AddonModAssignSync.syncAssign(this.assign!.id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -235,12 +235,12 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @param inputData The input data.
|
||||
* @return Promise resolved with the data to submit.
|
||||
*/
|
||||
protected prepareSubmissionData(inputData: CoreFormFields): Promise<AddonModAssignSavePluginData> {
|
||||
protected async prepareSubmissionData(inputData: CoreFormFields): Promise<AddonModAssignSavePluginData> {
|
||||
// If there's offline data, always save it in offline.
|
||||
this.saveOffline = this.hasOffline;
|
||||
|
||||
try {
|
||||
return AddonModAssignHelper.prepareSubmissionPluginData(
|
||||
return await AddonModAssignHelper.prepareSubmissionPluginData(
|
||||
this.assign!,
|
||||
this.userSubmission,
|
||||
inputData,
|
||||
|
@ -251,7 +251,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
// Cannot submit in online, prepare for offline usage.
|
||||
this.saveOffline = true;
|
||||
|
||||
return AddonModAssignHelper.prepareSubmissionPluginData(
|
||||
return await AddonModAssignHelper.prepareSubmissionPluginData(
|
||||
this.assign!,
|
||||
this.userSubmission,
|
||||
inputData,
|
||||
|
|
|
@ -514,8 +514,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
|
|||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async sync(): Promise<void> {
|
||||
await AddonModDataPrefetchHandler.sync(this.module, this.courseId);
|
||||
protected sync(): Promise<AddonModDataSyncResult> {
|
||||
return AddonModDataPrefetchHandler.sync(this.module, this.courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -233,8 +233,8 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async sync(): Promise<void> {
|
||||
await AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId);
|
||||
protected sync(): Promise<AddonModSurveySyncResult> {
|
||||
return AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -224,11 +224,17 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
document.addEventListener('ionBackButton', (event: BackButtonEvent) => {
|
||||
// This callback should have the lowest priority in the app.
|
||||
event.detail.register(-100, async () => {
|
||||
const initialPath = CoreNavigator.getCurrentPath();
|
||||
if (initialPath.startsWith('/main/')) {
|
||||
// Main menu has its own callback to handle back. If this callback is called it means we should exit app.
|
||||
CoreApp.closeApp();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This callback can be called at the same time as Ionic's back navigation callback.
|
||||
// Check if the path changes due to the back navigation handler, to know if we're at root level.
|
||||
// Ionic doc recommends IonRouterOutlet.canGoBack, but there's no easy way to get the current outlet from here.
|
||||
const initialPath = CoreNavigator.getCurrentPath();
|
||||
|
||||
// The path seems to change immediately (0 ms timeout), but use 50ms just in case.
|
||||
await CoreUtils.wait(50);
|
||||
|
||||
|
@ -238,8 +244,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
// Quit the app.
|
||||
const nav = <any> window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
nav.app?.exitApp();
|
||||
CoreApp.closeApp();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<ion-item *ngIf="model[modelValueName]">
|
||||
<ion-label color="success">{{ 'core.answered' | translate }}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="expired">
|
||||
<ion-item *ngIf="expired" class="ion-text-wrap">
|
||||
<ion-label color="danger">{{ 'core.login.recaptchaexpired' | translate }}</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
|
|
@ -87,8 +87,7 @@ export class CoreRecaptchaComponent implements OnInit {
|
|||
}
|
||||
|
||||
if (event.data.action == 'expired') {
|
||||
this.expired = true;
|
||||
this.model![this.modelValueName] = '';
|
||||
this.expireRecaptchaAnswer();
|
||||
} else if (event.data.action == 'callback') {
|
||||
this.expired = false;
|
||||
this.model![this.modelValueName] = event.data.value;
|
||||
|
@ -101,4 +100,12 @@ export class CoreRecaptchaComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire the recaptcha answer.
|
||||
*/
|
||||
expireRecaptchaAnswer(): void {
|
||||
this.expired = true;
|
||||
this.model![this.modelValueName] = '';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
@ -36,7 +38,7 @@ import { CoreForms } from '@singletons/form';
|
|||
})
|
||||
export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('credentialsForm') formElement?: ElementRef;
|
||||
@ViewChild('credentialsForm') formElement?: ElementRef<HTMLFormElement>;
|
||||
|
||||
credForm!: FormGroup;
|
||||
siteUrl!: string;
|
||||
|
@ -57,6 +59,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
protected viewLeft = false;
|
||||
protected siteId?: string;
|
||||
protected urlToOpen?: string;
|
||||
protected valueChangeSubscription?: Subscription;
|
||||
|
||||
constructor(
|
||||
protected fb: FormBuilder,
|
||||
|
@ -96,6 +99,28 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
this.siteChecked = true;
|
||||
this.pageLoaded = true;
|
||||
}
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
// Make iOS auto-fill work. The field that isn't focused doesn't get updated, do it manually.
|
||||
// Debounce it to prevent triggering this function too often when the user is typing.
|
||||
this.valueChangeSubscription = this.credForm.valueChanges.pipe(debounceTime(1000)).subscribe((changes) => {
|
||||
if (!this.formElement || !this.formElement.nativeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const usernameInput = this.formElement.nativeElement.querySelector<HTMLInputElement>('input[name="username"]');
|
||||
const passwordInput = this.formElement.nativeElement.querySelector<HTMLInputElement>('input[name="password"]');
|
||||
const usernameValue = usernameInput?.value;
|
||||
const passwordValue = passwordInput?.value;
|
||||
|
||||
if (usernameValue !== undefined && usernameValue !== changes.username) {
|
||||
this.credForm.get('username')?.setValue(usernameValue);
|
||||
}
|
||||
if (passwordValue !== undefined && passwordValue !== changes.password) {
|
||||
this.credForm.get('password')?.setValue(passwordValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -307,6 +332,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
ngOnDestroy(): void {
|
||||
this.viewLeft = true;
|
||||
CoreEvents.trigger(CoreEvents.LOGIN_SITE_UNCHECKED, { config: this.siteConfig }, this.siteId);
|
||||
this.valueChangeSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
} from '@features/login/services/login-helper';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreRecaptchaComponent } from '@components/recaptcha/recaptcha';
|
||||
|
||||
/**
|
||||
* Page to signup using email.
|
||||
|
@ -45,6 +46,7 @@ import { CoreForms } from '@singletons/form';
|
|||
export class CoreLoginEmailSignupPage implements OnInit {
|
||||
|
||||
@ViewChild(IonContent) content?: IonContent;
|
||||
@ViewChild(CoreRecaptchaComponent) recaptchaComponent?: CoreRecaptchaComponent;
|
||||
@ViewChild('ageForm') ageFormElement?: ElementRef;
|
||||
@ViewChild('signupFormEl') signupFormElement?: ElementRef;
|
||||
|
||||
|
@ -341,9 +343,12 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
CoreDomUtils.showAlert(Translate.instant('core.success'), message);
|
||||
CoreNavigator.back();
|
||||
} else {
|
||||
if (result.warnings && result.warnings.length) {
|
||||
let error = result.warnings[0].message;
|
||||
if (error == 'incorrect-captcha-sol') {
|
||||
this.recaptchaComponent?.expireRecaptchaAnswer();
|
||||
|
||||
const warning = result.warnings?.[0];
|
||||
if (warning) {
|
||||
let error = warning.message;
|
||||
if (error == 'incorrect-captcha-sol' || (!error && warning.item == 'recaptcharesponse')) {
|
||||
error = Translate.instant('core.login.recaptchaincorrect');
|
||||
}
|
||||
|
||||
|
|
|
@ -661,9 +661,7 @@ export class CoreLoginHelperProvider {
|
|||
|
||||
// Always open it in browser because the user might have the session stored in there.
|
||||
CoreUtils.openInBrowser(loginUrl);
|
||||
|
||||
const nav = <any> window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
nav.app?.exitApp();
|
||||
CoreApp.closeApp();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -695,9 +693,7 @@ export class CoreLoginHelperProvider {
|
|||
});
|
||||
} else {
|
||||
CoreUtils.openInBrowser(loginUrl);
|
||||
|
||||
const nav = <any> window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
nav.app?.exitApp();
|
||||
CoreApp.closeApp();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -226,16 +226,10 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
// Use a priority lower than 0 (navigation).
|
||||
event.detail.register(-10, async (processNextHandler: () => void) => {
|
||||
// This callback can be called at the same time as Ionic's back navigation callback.
|
||||
// Check if the path changes due to the back navigation handler, to know if we're at root level of the tab.
|
||||
// Ionic doc recommends IonRouterOutlet.canGoBack, but there's no easy way to get the current outlet from here.
|
||||
const initialPath = CoreNavigator.getCurrentPath();
|
||||
|
||||
// The path seems to change immediately (0 ms timeout), but use 50ms just in case.
|
||||
await CoreUtils.wait(50);
|
||||
|
||||
if (CoreNavigator.getCurrentPath() != initialPath) {
|
||||
// Ionic has navigated back, nothing else to do.
|
||||
return;
|
||||
// Check if user is already at the root of a tab.
|
||||
const mainMenuRootRoute = CoreNavigator.getCurrentRoute({ routeData: { isMainMenuRoot: true } });
|
||||
if (!mainMenuRootRoute) {
|
||||
return; // Not at root level, let Ionic handle the navigation.
|
||||
}
|
||||
|
||||
// No back navigation, already at root level. Check if we should change tab.
|
||||
|
|
|
@ -30,6 +30,9 @@ import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.modu
|
|||
deps: [Injector],
|
||||
useFactory: (injector: Injector) => buildTabMainRoutes(injector, {
|
||||
component: CoreMainMenuMorePage,
|
||||
data: {
|
||||
isMainMenuRoot: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
|
|
@ -562,6 +562,14 @@ export class CoreAppProvider {
|
|||
return redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the app.
|
||||
*/
|
||||
closeApp(): void {
|
||||
const nav = <any> window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
nav.app?.exitApp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget redirect data.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue