Merge pull request #2763 from dpalou/MOBILE-3320

Mobile 3320
main
Pau Ferrer Ocaña 2021-05-13 14:34:23 +02:00 committed by GitHub
commit ecab609487
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 209 additions and 121 deletions

View File

@ -37,32 +37,34 @@
<div class="safe-padding-horizontal" [hidden]="showFilter || !showSelectorFilter">
<!-- "Time" selector. -->
<core-combobox [label]="'core.show' | translate" [selection]="selectedFilter" (onChange)="selectedChanged($event)">
<ion-select-option value="allincludinghidden" *ngIf="showFilters.allincludinghidden != 'hidden'">
<ion-select-option class="ion-text-wrap" value="allincludinghidden" *ngIf="showFilters.allincludinghidden != 'hidden'">
{{ 'addon.block_myoverview.allincludinghidden' | translate }}
</ion-select-option>
<ion-select-option value="all" *ngIf="showFilters.all != 'hidden'">
<ion-select-option class="ion-text-wrap" value="all" *ngIf="showFilters.all != 'hidden'">
{{ 'addon.block_myoverview.all' | translate }}
</ion-select-option>
<ion-select-option value="inprogress" *ngIf="showFilters.inprogress != 'hidden'"
<ion-select-option class="ion-text-wrap" value="inprogress" *ngIf="showFilters.inprogress != 'hidden'"
[disabled]="showFilters.inprogress == 'disabled'">
{{ 'addon.block_myoverview.inprogress' | translate }}
</ion-select-option>
<ion-select-option value="future" *ngIf="showFilters.future != 'hidden'" [disabled]="showFilters.future == 'disabled'">
<ion-select-option class="ion-text-wrap" value="future" *ngIf="showFilters.future != 'hidden'"
[disabled]="showFilters.future == 'disabled'">
{{ 'addon.block_myoverview.future' | translate }}
</ion-select-option>
<ion-select-option value="past" *ngIf="showFilters.past != 'hidden'" [disabled]="showFilters.past == 'disabled'">
<ion-select-option class="ion-text-wrap" value="past" *ngIf="showFilters.past != 'hidden'" [disabled]="showFilters.past == 'disabled'">
{{ 'addon.block_myoverview.past' | translate }}
</ion-select-option>
<ng-container *ngIf="showFilters.custom != 'hidden'">
<ng-container *ngFor="let customOption of customFilter; let index = index">
<ion-select-option value="custom-{{index}}">{{ customOption.name }}</ion-select-option>
<ion-select-option class="ion-text-wrap" value="custom-{{index}}">{{ customOption.name }}</ion-select-option>
</ng-container>
</ng-container>
<ion-select-option value="favourite" *ngIf="showFilters.favourite != 'hidden'"
<ion-select-option class="ion-text-wrap" value="favourite" *ngIf="showFilters.favourite != 'hidden'"
[disabled]="showFilters.favourite == 'disabled'">
{{ 'addon.block_myoverview.favourites' | translate }}
</ion-select-option>
<ion-select-option value="hidden" *ngIf="showFilters.hidden != 'hidden'" [disabled]="showFilters.hidden == 'disabled'">
<ion-select-option class="ion-text-wrap" value="hidden" *ngIf="showFilters.hidden != 'hidden'"
[disabled]="showFilters.hidden == 'disabled'">
{{ 'addon.block_myoverview.hiddencourses' | translate }}
</ion-select-option>
</core-combobox>

View File

@ -12,13 +12,27 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<div class="safe-padding-horizontal">
<core-combobox [selection]="filter" (onChange)="switchFilter($event)">
<ion-select-option value="all">{{ 'core.all' | translate }}</ion-select-option>
<ion-select-option value="overdue">{{ 'addon.block_timeline.overdue' | translate }}</ion-select-option>
<ion-select-option disabled value="disabled">{{ 'addon.block_timeline.duedate' | translate }}</ion-select-option>
<ion-select-option value="next7days">{{ 'addon.block_timeline.next7days' | translate }}</ion-select-option>
<ion-select-option value="next30days">{{ 'addon.block_timeline.next30days' | translate }}</ion-select-option>
<ion-select-option value="next3months">{{ 'addon.block_timeline.next3months' | translate }}</ion-select-option>
<ion-select-option value="next6months">{{ 'addon.block_timeline.next6months' | translate }}</ion-select-option>
<ion-select-option class="ion-text-wrap" value="all">
{{ 'core.all' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" value="overdue">
{{ 'addon.block_timeline.overdue' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" disabled value="disabled">
{{ 'addon.block_timeline.duedate' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" value="next7days">
{{ 'addon.block_timeline.next7days' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" value="next30days">
{{ 'addon.block_timeline.next30days' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" value="next3months">
{{ 'addon.block_timeline.next3months' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" value="next6months">
{{ 'addon.block_timeline.next6months' | translate }}
</ion-select-option>
</core-combobox>
</div>
<core-loading [hideUntil]="timeline.loaded" [hidden]="sort != 'sortbydates'" class="core-loading-center">

View File

@ -32,10 +32,16 @@ function buildRoutes(injector: Injector): Routes {
return [
{
path: 'index',
data: {
isMainMenuRoot: true,
},
loadChildren: () => import('@/addons/calendar/pages/index/index.module').then(m => m.AddonCalendarIndexPageModule),
},
{
path: 'list',
data: {
isMainMenuRoot: true,
},
loadChildren: () => import('@/addons/calendar/pages/list/list.module').then(m => m.AddonCalendarListPageModule),
},
{

View File

@ -76,7 +76,7 @@
<ion-label><h2 [core-mark-required]="true">{{ 'core.course' | translate }}</h2></ion-label>
<ion-select formControlName="groupcourseid"
interface="action-sheet" [placeholder]="'core.noselection' | translate"
(ionChange)="groupCourseSelected($event)">
(ionChange)="groupCourseSelected()">
<ion-select-option *ngFor="let course of courses" [value]="course.id">
{{ course.fullname }}
</ion-select-option>

View File

@ -396,10 +396,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
/**
* A course was selected, get its groups.
*
* @param courseId Course ID.
*/
async groupCourseSelected(courseId: number): Promise<void> {
async groupCourseSelected(): Promise<void> {
const courseId = this.form.controls.groupcourseid.value;
if (!courseId) {
return;
}

View File

@ -73,7 +73,8 @@
<ion-item>
<ion-label>
<h2>{{ 'addon.calendar.when' | translate }}</h2>
<p [innerHTML]="event.formattedtime"></p>
<core-format-text [text]="event.formattedtime" [contextLevel]="event.contextLevel"
[contextInstanceId]="event.contextInstanceId"></core-format-text>
</ion-label>
<ion-note slot="end" *ngIf="!isSplitViewOn && event.deleted">
<ion-icon name="fas-trash" aria-hidden="true"></ion-icon> {{ 'core.deletedoffline' | translate }}

View File

@ -103,8 +103,6 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
this.canEdit = AddonCalendar.canEditEventsInSite();
this.canDelete = AddonCalendar.canDeleteEventsInSite();
this.asyncConstructor();
// Listen for event edited. If current event is edited, reload the data.
this.editEventObserver = CoreEvents.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data) => {
if (data && data.eventId == this.eventId) {
@ -136,7 +134,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
});
}
protected async asyncConstructor(): Promise<void> {
protected async initReminders(): Promise<void> {
if (this.notificationsEnabled) {
this.monthNames = CoreLang.getMonthNames();
@ -159,6 +157,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
this.syncIcon = CoreConstants.ICON_LOADING;
this.fetchEvent();
this.initReminders();
}
/**

View File

@ -8,17 +8,17 @@
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-item *ngIf="defaultTime != -1">
<ion-label>{{ 'addon.calendar.defaultnotificationtime' | translate }}</ion-label>
<ion-select [(ngModel)]="defaultTime" (ionChange)="updateDefaultTime($event)" interface="action-sheet">
<ion-select-option value="0">{{ 'core.settings.disabled' | translate }}</ion-select-option>
<ion-select-option value="10">{{ 600 | coreDuration }}</ion-select-option>
<ion-select-option value="30">{{ 1800 | coreDuration }}</ion-select-option>
<ion-select-option value="60">{{ 3600 | coreDuration }}</ion-select-option>
<ion-select-option value="120">{{ 7200 | coreDuration }}</ion-select-option>
<ion-select-option value="360">{{ 21600 | coreDuration }}</ion-select-option>
<ion-select-option value="720">{{ 43200 | coreDuration }}</ion-select-option>
<ion-select-option value="1440">{{ 86400 | coreDuration }}</ion-select-option>
<ion-select [(ngModel)]="defaultTime" (ionChange)="updateDefaultTime(defaultTime)" interface="action-sheet">
<ion-select-option [value]="0">{{ 'core.settings.disabled' | translate }}</ion-select-option>
<ion-select-option [value]="10">{{ 600 | coreDuration }}</ion-select-option>
<ion-select-option [value]="30">{{ 1800 | coreDuration }}</ion-select-option>
<ion-select-option [value]="60">{{ 3600 | coreDuration }}</ion-select-option>
<ion-select-option [value]="120">{{ 7200 | coreDuration }}</ion-select-option>
<ion-select-option [value]="360">{{ 21600 | coreDuration }}</ion-select-option>
<ion-select-option [value]="720">{{ 43200 | coreDuration }}</ion-select-option>
<ion-select-option [value]="1440">{{ 86400 | coreDuration }}</ion-select-option>
</ion-select>
</ion-item>
</ion-list>

View File

@ -26,7 +26,7 @@ import { CoreSites } from '@services/sites';
})
export class AddonCalendarSettingsPage implements OnInit {
defaultTime = 0;
defaultTime = -1;
/**
* View loaded.

View File

@ -0,0 +1,44 @@
// (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 { Injectable } from '@angular/core';
import { CanActivate, UrlTree } from '@angular/router';
import { Router } from '@singletons';
import { AddonMessagesMainMenuHandlerService } from '../services/handlers/mainmenu';
import { AddonMessages } from '../services/messages';
/**
* Guard to redirect to the right page based on the current Moodle site version.
*/
@Injectable({ providedIn: 'root' })
export class AddonMessagesIndexGuard implements CanActivate {
/**
* @inheritdoc
*/
canActivate(): UrlTree {
return this.guard();
}
/**
* Check if there is a pending redirect and trigger it.
*/
private guard(): UrlTree {
const enabled = AddonMessages.isGroupMessagingEnabled();
const path = `/main/${AddonMessagesMainMenuHandlerService.PAGE_NAME}/` + ( enabled ? 'group-conversations' : 'index');
return Router.parseUrl(path);
}
}

View File

@ -16,6 +16,7 @@ import { Injector, NgModule } from '@angular/core';
import { Route, RouterModule, ROUTES, Routes } from '@angular/router';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
import { AddonMessagesIndexGuard } from './guards';
export const AddonMessagesDiscussionRoute: Route = {
path: 'discussion',
@ -51,8 +52,7 @@ function buildRoutes(injector: Injector): Routes {
.then(m => m.AddonMessagesContactsPageModule),
},
...buildTabMainRoutes(injector, {
redirectTo: 'index',
pathMatch: 'full',
canActivate: [AddonMessagesIndexGuard],
}),
];
}

View File

@ -26,6 +26,9 @@ import { AddonMessagesDiscussions35Page } from './discussions.page';
const mobileRoutes: Routes = [
{
path: '',
data: {
isMainMenuRoot: true,
},
component: AddonMessagesDiscussions35Page,
},
AddonMessagesDiscussionRoute,
@ -34,6 +37,9 @@ const mobileRoutes: Routes = [
const tabletRoutes: Routes = [
{
path: '',
data: {
isMainMenuRoot: true,
},
component: AddonMessagesDiscussions35Page,
children: [
AddonMessagesDiscussionRoute,

View File

@ -25,6 +25,9 @@ import { AddonMessagesGroupConversationsPage } from './group-conversations.page'
const mobileRoutes: Routes = [
{
path: '',
data: {
isMainMenuRoot: true,
},
component: AddonMessagesGroupConversationsPage,
},
AddonMessagesDiscussionRoute,
@ -33,6 +36,9 @@ const mobileRoutes: Routes = [
const tabletRoutes: Routes = [
{
path: '',
data: {
isMainMenuRoot: true,
},
component: AddonMessagesGroupConversationsPage,
children: [
AddonMessagesDiscussionRoute,

View File

@ -18,6 +18,7 @@ import { CoreContentLinksAction } from '@features/contentlinks/services/contentl
import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons';
import { AddonMessages } from '../messages';
import { AddonMessagesMainMenuHandlerService } from './mainmenu';
/**
* Content links handler for messaging index.
@ -37,9 +38,7 @@ export class AddonMessagesIndexLinkHandlerService extends CoreContentLinksHandle
getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{
action: async (siteId): Promise<void> => {
const pageName = await AddonMessages.getMainMessagesPagePathInSite(siteId);
CoreNavigator.navigateToSitePath(pageName, {
CoreNavigator.navigateToSitePath(AddonMessagesMainMenuHandlerService.PAGE_NAME, {
siteId,
preferCurrentTab: false,
});

View File

@ -43,7 +43,7 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler,
protected handler: CoreMainMenuHandlerToDisplay = {
icon: 'fas-comments',
title: 'addon.messages.messages',
page: AddonMessages.getMainMessagesPagePath(),
page: AddonMessagesMainMenuHandlerService.PAGE_NAME,
class: 'addon-messages-handler',
showBadge: true, // Do not check isMessageCountEnabled because we'll use fallback it not enabled.
badge: '',
@ -108,8 +108,6 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler,
* @return Data needed to render the handler.
*/
getDisplayData(): CoreMainMenuHandlerToDisplay {
this.handler.page = AddonMessages.getMainMessagesPagePath();
if (this.handler.loading) {
this.refreshBadge();
}

View File

@ -20,6 +20,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonMessages } from '../messages';
import { AddonMessagesMainMenuHandlerService } from './mainmenu';
/**
* Handler for messaging push notifications clicks.
@ -61,7 +62,6 @@ export class AddonMessagesPushClickHandlerService implements CorePushNotificatio
// Check if group messaging is enabled, to determine which page should be loaded.
const enabled = await AddonMessages.isGroupMessagingEnabledInSite(notification.site);
const pageName = await AddonMessages.getMainMessagesPagePathInSite(notification.site);
let nextPageParams: Params | undefined;
@ -76,7 +76,7 @@ export class AddonMessagesPushClickHandlerService implements CorePushNotificatio
};
}
await CoreNavigator.navigateToSitePath(pageName, {
await CoreNavigator.navigateToSitePath(AddonMessagesMainMenuHandlerService.PAGE_NAME, {
siteId: notification.site,
preferCurrentTab: false,
nextNavigation: nextPageParams ?

View File

@ -30,7 +30,6 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreWSExternalWarning } from '@services/ws';
import { makeSingleton } from '@singletons';
import { CoreError } from '@classes/errors/error';
import { AddonMessagesMainMenuHandlerService } from './handlers/mainmenu';
import { AddonMessagesSyncEvents, AddonMessagesSyncProvider } from './messages-sync';
const ROOT_CACHE_KEY = 'mmaMessages:';
@ -1411,29 +1410,6 @@ export class AddonMessagesProvider {
throw new CoreError('Error getting message preferences');
}
/**
* Gets the site main messages page path for a site.
*
* @param siteId Site ID. If not defined, use current site.
* @return Main messages page path of the site.
*/
async getMainMessagesPagePathInSite(siteId?: string): Promise<string> {
const enabled = await this.isGroupMessagingEnabledInSite(siteId);
return AddonMessagesMainMenuHandlerService.PAGE_NAME + ( enabled ? '/group-conversations' : '');
}
/**
* Gets the site main messages page path.
*
* @return Main messages page path of the site.
*/
getMainMessagesPagePath(): string {
const enabled = this.isGroupMessagingEnabled();
return AddonMessagesMainMenuHandlerService.PAGE_NAME + ( enabled ? '/group-conversations' : '');
}
/**
* Get messages according to the params.
*

View File

@ -188,11 +188,11 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider
/**
* Sync all quizzes on a site.
*
* @param siteId Site ID to sync.
* @param force Wether to force sync not depending on last execution.
* @param siteId Site ID to sync.
* @param Promise resolved if sync is successful, rejected if sync fails.
*/
protected async syncAllQuizzesFunc(siteId: string, force: boolean): Promise<void> {
protected async syncAllQuizzesFunc(force: boolean, siteId: string): Promise<void> {
// Get all offline attempts.
const attempts = await AddonModQuizOffline.getAllAttempts(siteId);

View File

@ -36,9 +36,15 @@
</ion-item>
<core-combobox [selection]="type" (onChange)="typeChanged($event)">
<ion-select-option value="site">{{ 'addon.notes.sitenotes' | translate }}</ion-select-option>
<ion-select-option value="course">{{ 'addon.notes.coursenotes' | translate }}</ion-select-option>
<ion-select-option value="personal">{{ 'addon.notes.personalnotes' | translate }}</ion-select-option>
<ion-select-option class="ion-text-wrap" value="site">
{{ 'addon.notes.sitenotes' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" value="course">
{{ 'addon.notes.coursenotes' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" value="personal">
{{ 'addon.notes.personalnotes' | translate }}
</ion-select-option>
</core-combobox>
<ion-card class="core-warning-card" *ngIf="hasOffline">

View File

@ -21,6 +21,9 @@ function buildRoutes(injector: Injector): Routes {
return [
{
path: 'list',
data: {
isMainMenuRoot: true,
},
loadChildren: () => import('./pages/list/list.module').then(m => m.AddonNotificationsListPageModule),
},
...buildTabMainRoutes(injector, {

View File

@ -43,7 +43,7 @@
<!-- Show processor selector. -->
<core-combobox *ngIf="preferences && preferences.processors && preferences.processors.length > 0"
[selection]="currentProcessor!.name" (onChange)="changeProcessor($event)">
<ion-select-option *ngFor="let processor of preferences.processors" [value]="processor.name">
<ion-select-option class="ion-text-wrap" *ngFor="let processor of preferences.processors" [value]="processor.name">
{{ processor.displayname }}
</ion-select-option>
</core-combobox>

View File

@ -15,8 +15,12 @@
<core-loading [hideUntil]="filesLoaded" *ngIf="showPrivateFiles || showSiteFiles">
<!-- Allow selecting the files to see: private or site. -->
<core-combobox [selection]="root" (onChange)="rootChanged($event)" *ngIf="showPrivateFiles && showSiteFiles && !path">
<ion-select-option value="my">{{ 'addon.privatefiles.privatefiles' | translate }}</ion-select-option>
<ion-select-option value="site">{{ 'addon.privatefiles.sitefiles' | translate }}</ion-select-option>
<ion-select-option class="ion-text-wrap" value="my">
{{ 'addon.privatefiles.privatefiles' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" value="site">
{{ 'addon.privatefiles.sitefiles' | translate }}
</ion-select-option>
</core-combobox>
<!-- Display info about space used and space left. -->

View File

@ -19,12 +19,19 @@ import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.modu
function buildRoutes(injector: Injector): Routes {
return [
{
path: 'root',
data: {
isMainMenuRoot: true,
},
loadChildren: () => import('./pages/index/index.module').then(m => m.AddonPrivateFilesIndexPageModule),
},
{
path: ':hash',
loadChildren: () => import('./pages/index/index.module').then(m => m.AddonPrivateFilesIndexPageModule),
},
...buildTabMainRoutes(injector, {
redirectTo: 'root', // Fake "hash".
redirectTo: 'root',
pathMatch: 'full',
}),
];

View File

@ -48,7 +48,6 @@ export class AddonPrivateFilesMainMenuHandlerService implements CoreMainMenuHand
icon: 'fas-folder',
title: 'addon.privatefiles.files',
page: AddonPrivateFilesMainMenuHandlerService.PAGE_NAME,
subPage: 'root',
class: 'addon-privatefiles-handler',
};
}

View File

@ -30,7 +30,7 @@ import { CoreUtils } from '@services/utils/utils';
})
export class CoreAutoFocusDirective implements AfterViewInit {
@Input('core-auto-focus') showKeyboard: boolean | string = true;
@Input('core-auto-focus') autoFocus: boolean | string = true;
protected element: HTMLElement;
@ -42,7 +42,7 @@ export class CoreAutoFocusDirective implements AfterViewInit {
* @inheritdoc
*/
ngAfterViewInit(): void {
if (this.showKeyboard === 'nofocus') {
if (CoreUtils.isFalseOrZero(this.autoFocus)) {
return;
}
@ -78,8 +78,7 @@ export class CoreAutoFocusDirective implements AfterViewInit {
return;
}
const showKeyboard = this.showKeyboard === '' || CoreUtils.isTrueOrOne(this.showKeyboard);
CoreDomUtils.focusElement(element, showKeyboard);
CoreDomUtils.focusElement(element);
if (element != document.activeElement) {
this.setFocus(retries - 1);

View File

@ -33,7 +33,7 @@
<ion-label class="sr-only">{{ 'core.login.username' | translate }}</ion-label>
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}"
formControlName="username" autocapitalize="none" autocorrect="off" autocomplete="username" enterkeyhint="next"
required="true" core-auto-focus>
required="true">
</ion-input>
</ion-item>
<ion-item *ngIf="siteChecked && !isBrowserSSO" class="ion-margin-bottom">

View File

@ -31,7 +31,7 @@
<ion-item>
<ion-label></ion-label>
<ion-input type="text" name="value" placeholder="{{ 'core.login.usernameoremail' | translate }}"
formControlName="value" autocapitalize="none" autocorrect="off" [core-auto-focus]="showKeyboard">
formControlName="value" autocapitalize="none" autocorrect="off" [core-auto-focus]="autoFocus">
</ion-input>
</ion-item>
<ion-button type="submit" class="ion-margin" expand="block" [disabled]="!myForm.valid">

View File

@ -35,7 +35,7 @@ export class CoreLoginForgottenPasswordPage implements OnInit {
myForm!: FormGroup;
siteUrl!: string;
showKeyboard!: boolean;
autoFocus!: boolean;
constructor(
protected formBuilder: FormBuilder,
@ -55,7 +55,7 @@ export class CoreLoginForgottenPasswordPage implements OnInit {
}
this.siteUrl = siteUrl;
this.showKeyboard = Platform.is('tablet');
this.autoFocus = Platform.is('tablet');
this.myForm = this.formBuilder.group({
field: ['username', Validators.required],
value: [CoreNavigator.getRouteParam<string>('username') || '', Validators.required],

View File

@ -42,7 +42,7 @@
<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-auto-focus>
autocomplete="current-password" enterkeyhint="go" required="true">
</ion-input>
</core-show-password>
</ion-item>

View File

@ -26,7 +26,7 @@
<h2>{{ 'core.login.siteaddress' | translate }}</h2>
</ion-label>
<ion-input name="url" type="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}"
formControlName="siteUrl" [core-auto-focus]="showKeyboard">
formControlName="siteUrl" [core-auto-focus]="showKeyboard && !showScanQR">
</ion-input>
</ion-item>
</ng-container>
@ -36,7 +36,7 @@
<h2>{{ 'core.login.siteaddress' | translate }}</h2>
</ion-label>
<ion-input name="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}" formControlName="siteUrl"
[core-auto-focus]="showKeyboard" (ionChange)="searchSite($event, siteForm.value.siteUrl)">
[core-auto-focus]="showKeyboard && !showScanQR" (ionChange)="searchSite($event, siteForm.value.siteUrl)">
</ion-input>
</ion-item>

View File

@ -28,6 +28,9 @@ function buildRoutes(injector: Injector): Routes {
return [
...buildTabMainRoutes(injector, {
path: '',
data: {
isMainMenuRoot: true,
},
component: CoreMainMenuHomePage,
children: routes.children,
}),

View File

@ -13,13 +13,12 @@
// limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { IonTabs } from '@ionic/angular';
import { BackButtonEvent } from '@ionic/core';
import { Subscription } from 'rxjs';
import { CoreApp } from '@services/app';
import { CoreTextUtils } from '@services/utils/text';
import { CoreEvents, CoreEventObserver } from '@singletons/events';
import { CoreMainMenu, CoreMainMenuProvider } from '../../services/mainmenu';
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate';
@ -27,6 +26,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { Translate } from '@singletons';
import { CoreUtils } from '@services/utils/utils';
import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from '@classes/aria-role-tab';
import { CoreNavigator } from '@services/navigator';
/**
* Page that displays the main menu of the app.
@ -61,7 +61,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
constructor(
protected route: ActivatedRoute,
protected changeDetector: ChangeDetectorRef,
protected router: Router,
) {
this.resizeFunction = this.initHandlers.bind(this);
this.backButtonFunction = this.backButtonClicked.bind(this);
@ -172,28 +171,26 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
return;
}
const trimmedUrl = CoreTextUtils.trimCharacter(this.router.url, '/');
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
// Current tab was clicked. Check if user is already at root level.
if (trimmedUrl == CoreTextUtils.trimCharacter(page, '/')) {
// Already at root level, nothing to do.
return;
const mainMenuRootRoute = CoreNavigator.getCurrentRoute({ routeData: { isMainMenuRoot: true } });
if (mainMenuRootRoute) {
return; // Already at root level, nothing to do.
}
// Current route doesn't define isMainMenuRoot. Check if the current path is the tab one.
const currentPath = CoreNavigator.getCurrentPath();
if (currentPath == `/main/${page}`) {
return; // Already at root level, nothing to do.
}
// Ask the user if he wants to go back to the root page of the tab.
e.preventDefault();
e.stopPropagation();
try {
const tab = this.tabs.find((tab) => tab.page == page);
// Use tab's subPage to check if user is already at root level.
if (tab?.subPage && trimmedUrl ==
CoreTextUtils.trimCharacter(CoreTextUtils.concatenatePaths(tab.page, tab.subPage), '/')) {
// Already at root level, nothing to do.
return;
}
if (tab?.title) {
await CoreDomUtils.showConfirm(Translate.instant('core.confirmgotabroot', {
name: Translate.instant(tab.title),

View File

@ -136,7 +136,6 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
* Open a handler.
*
* @param handler Handler to open.
* @todo: use subPage?
*/
openHandler(handler: CoreMainMenuHandlerData): void {
const params = handler.pageParams;

View File

@ -42,7 +42,6 @@ export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler {
icon: 'fas-tachometer-alt',
title: 'core.mainmenu.home',
page: CoreMainMenuHomeHandlerService.PAGE_NAME,
// @todo: subPage? The page can change due to core-tabs.
class: 'core-home-handler',
};
}

View File

@ -33,13 +33,6 @@ export interface CoreMainMenuHandlerData {
*/
page: string;
/**
* Sub page loaded when the handler page is loaded.
* If your module performs a redirect when it's opened you need to specify the sub page in here.
* E.g. if page is 'foo' but it redirects to 'foo/bar' when opened, this value must be 'bar'.
*/
subPage?: string;
/**
* Title to display for the handler.
*/

View File

@ -3,7 +3,7 @@
<ion-item>
<ion-label></ion-label>
<ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder"
[autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus || 'nofocus'"
[autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus"
[disabled]="disabled" role="searchbox" (ionFocus)="focus($event)">
</ion-input>
<ion-button slot="end" fill="clear" type="submit" size="small" [attr.aria-label]="searchLabel"

View File

@ -19,8 +19,10 @@
</ion-col>
<ion-col size="12" size-sm="6" *ngIf="collections && collections.length > 1">
<core-combobox [selection]="collectionId" (onChange)="searchTags($event)" [disabled]="searching">
<ion-select-option [value]="0">{{ 'core.tag.inalltagcoll' | translate }}</ion-select-option>
<ion-select-option *ngFor="let collection of collections" [value]="collection.id">
<ion-select-option class="ion-text-wrap" [value]="0">
{{ 'core.tag.inalltagcoll' | translate }}
</ion-select-option>
<ion-select-option class="ion-text-wrap" *ngFor="let collection of collections" [value]="collection.id">
{{ collection.name }}</ion-select-option>
</core-combobox>
</ion-col>

View File

@ -31,6 +31,9 @@ function buildRoutes(injector: Injector): Routes {
},
{
path: 'search',
data: {
isMainMenuRoot: true,
},
loadChildren: () => import('@features/tag//pages/search/search.page.module').then(m => m.CoreTagSearchPageModule),
},
CoreTagIndexAreaRoute,

View File

@ -519,6 +519,31 @@ export class CoreNavigatorService {
return path;
}
/**
* Get the full path of a certain route, including parent routes paths.
*
* @param route Route.
* @return Path.
*/
getRouteFullPath(route: ActivatedRoute | null): string {
if (!route) {
return '';
}
const parentPath = this.getRouteFullPath(route.parent);
const routePath = route.snapshot.url.join('/');
if (!parentPath && !routePath) {
return '';
} else if (parentPath && !routePath) {
return parentPath;
} else if (!parentPath && routePath) {
return '/' + routePath;
} else {
return parentPath + '/' + routePath;
}
}
}
export const CoreNavigator = makeSingleton(CoreNavigatorService);

View File

@ -391,12 +391,11 @@ export class CoreDomUtilsProvider {
* Focus an element and open keyboard.
*
* @param el HTML element to focus.
* @param showKeyboard Show keyboard when focusing the element.
*/
focusElement(el: HTMLElement, showKeyboard = true): void {
focusElement(el: HTMLElement): void {
if (el?.focus) {
el.focus();
if (showKeyboard && CoreApp.isAndroid() && this.supportsInputKeyboard(el)) {
if (CoreApp.isAndroid() && this.supportsInputKeyboard(el)) {
// On some Android versions the keyboard doesn't open automatically.
CoreApp.openKeyboard();
}