From 064a60ca499daef9b031983e593da9a6efcdb4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 11 May 2021 11:52:57 +0200 Subject: [PATCH 1/5] MOBILE-3753 a11y: Use ion-items on item-divider with clicks --- .../group-conversations.html | 51 ++++++++++++++----- .../forum/components/edit-post/edit-post.html | 17 +++++-- .../mod/forum/components/post/post.html | 16 ++++-- .../pages/new-discussion/new-discussion.html | 18 +++++-- .../core-block-only-title.html | 5 +- .../core-siteplugins-only-title-block.html | 4 +- src/theme/theme.base.scss | 10 ---- src/theme/theme.dark.scss | 4 +- src/theme/theme.light.scss | 5 +- 9 files changed, 84 insertions(+), 46 deletions(-) diff --git a/src/addons/messages/pages/group-conversations/group-conversations.html b/src/addons/messages/pages/group-conversations/group-conversations.html index 518656e04..bbcea3e60 100644 --- a/src/addons/messages/pages/group-conversations/group-conversations.html +++ b/src/addons/messages/pages/group-conversations/group-conversations.html @@ -32,15 +32,24 @@ {{contactRequestsCount}} - + [attr.aria-expanded]="favourites.expanded" + aria-controls="addon-messages-groupconversations-favourite" + role="heading" + detail="false" + >

{{ 'core.favourites' | translate }} ({{ favourites.count }})

{{ favourites.unread }} -
-
+ +
@@ -55,15 +64,24 @@ - + [attr.aria-expanded]="group.expanded" + aria-controls="addon-messages-groupconversations-group" + role="heading" + detail="false" + >

{{ 'addon.messages.groupconversations' | translate }} ({{ group.count }})

{{ group.unread }} -
-
+ +
@@ -77,17 +95,26 @@ - + [attr.aria-expanded]="individual.expanded" + aria-controls="addon-messages-groupconversations-individual" + role="heading" + detail="false" + >

{{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }})

{{ individual.unread }} -
-
+ +
diff --git a/src/addons/mod/forum/components/edit-post/edit-post.html b/src/addons/mod/forum/components/edit-post/edit-post.html index 4543b75f4..812574c03 100644 --- a/src/addons/mod/forum/components/edit-post/edit-post.html +++ b/src/addons/mod/forum/components/edit-post/edit-post.html @@ -25,18 +25,25 @@ (contentChanged)="onMessageChange($event)"> - +

{{ 'addon.mod_forum.advanced' | translate }}

-
- + +
- +
diff --git a/src/addons/mod/forum/components/post/post.html b/src/addons/mod/forum/components/post/post.html index 6e99172ef..e149dba0d 100644 --- a/src/addons/mod/forum/components/post/post.html +++ b/src/addons/mod/forum/components/post/post.html @@ -122,20 +122,26 @@ - +

{{ 'addon.mod_forum.advanced' | translate }}

-
- + +
- +
diff --git a/src/addons/mod/forum/pages/new-discussion/new-discussion.html b/src/addons/mod/forum/pages/new-discussion/new-discussion.html index f28a931a9..999ed8eae 100644 --- a/src/addons/mod/forum/pages/new-discussion/new-discussion.html +++ b/src/addons/mod/forum/pages/new-discussion/new-discussion.html @@ -31,13 +31,21 @@ (contentChanged)="onMessageChange($event)"> - +

{{ 'addon.mod_forum.advanced' | translate }}

-
- + +
{{ 'addon.mod_forum.posttomygroups' | translate }} @@ -61,7 +69,7 @@ [files]="newDiscussion.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [component]="component" [componentId]="forum.cmid" [allowOffline]="true"> - +
diff --git a/src/core/features/block/components/only-title-block/core-block-only-title.html b/src/core/features/block/components/only-title-block/core-block-only-title.html index 4230b25ec..cfdeb1a04 100644 --- a/src/core/features/block/components/only-title-block/core-block-only-title.html +++ b/src/core/features/block/components/only-title-block/core-block-only-title.html @@ -1,4 +1,3 @@ - +

{{ title | translate }}

- -
+
diff --git a/src/core/features/siteplugins/components/only-title-block/core-siteplugins-only-title-block.html b/src/core/features/siteplugins/components/only-title-block/core-siteplugins-only-title-block.html index 1a4a3a63c..04564f703 100644 --- a/src/core/features/siteplugins/components/only-title-block/core-siteplugins-only-title-block.html +++ b/src/core/features/siteplugins/components/only-title-block/core-siteplugins-only-title-block.html @@ -1,5 +1,5 @@ - +

{{ title | translate }}

-
+ diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 7069ba7aa..d10a8615f 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -185,16 +185,6 @@ ion-alert.core-nohead { } } -// Ionic item divider. -ion-item-divider { - .item-detail-icon { - color: var(--ion-item-detail-icon-color); - font-size: var(--ion-item-detail-icon-font-size); - opacity: var(--ion-item-detail-icon-opacity); - padding-inline-end: 16px; - } -} - // Ionic list. ion-list.list-md { padding: 0; diff --git a/src/theme/theme.dark.scss b/src/theme/theme.dark.scss index 25b8887cc..ef871c0e6 100644 --- a/src/theme/theme.dark.scss +++ b/src/theme/theme.dark.scss @@ -67,8 +67,8 @@ } --ion-item-background: #{$ion-item-background-dark}; - --ion-item-detail-icon-color: var(--white); - ion-item-divider { + --ion-item-detail-icon-: var(--white); + ion-item-divider, ion-item.divider { --background: var(--black); --color: var(--white); } diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index 5ffcbe996..8bd13bd09 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -172,10 +172,11 @@ } --item-divider-min-height: calc(var(--a11y-min-target-size) + 8px); - ion-item-divider { + ion-item-divider, ion-item.divider { --background: var(--gray-lighter); --color: inherit; - min-height: var(--item-divider-min-height); + --min-height: var(--item-divider-min-height); + min-height: var(--min-height); } --core-combobox-background: var(--ion-item-background); From ef0ed6c7fb90ef1c84cb9cb8b019b30c151ce6a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 11 May 2021 11:54:30 +0200 Subject: [PATCH 2/5] MOBILE-3753 calendar: Fix calendar navigation --- .../calendar/addon-calendar-calendar.html | 132 +++++++++++------- .../components/calendar/calendar.scss | 2 + .../calendar/components/calendar/calendar.ts | 40 +++++- .../calendar/pages/edit-event/edit-event.html | 75 ++++------ src/addons/calendar/services/calendar.ts | 1 + src/core/classes/aria-role-button.ts | 8 +- .../rich-text-editor/rich-text-editor.ts | 34 ++++- 7 files changed, 179 insertions(+), 113 deletions(-) diff --git a/src/addons/calendar/components/calendar/addon-calendar-calendar.html b/src/addons/calendar/components/calendar/addon-calendar-calendar.html index d80b34e36..7fe5c0da7 100644 --- a/src/addons/calendar/components/calendar/addon-calendar-calendar.html +++ b/src/addons/calendar/components/calendar/addon-calendar-calendar.html @@ -18,7 +18,7 @@
-

{{ periodName }}

+

{{ periodName }}

@@ -29,58 +29,88 @@
- - - - - {{ day.shortname | translate }} - {{ day.fullname | translate }} - - + +
+ + + + {{ day.fullname | translate }} + + + + +
+
- - - - - -

{{ day.mday }}

- - -

- - -
- -
- - - - - {{ event.timestart * 1000 | coreFormatDate: timeFormat }} - - - - {{ 'addon.calendar.type' + event.formattedType | translate }} - {{ event.iconTitle }} - - {{event.name}} -
-
-

- {{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }} + + + + + +

+ + {{ day.periodName | translate }}

-
-
- - -
+ + +

+ + +
+ +
+ + + + + {{ event.timestart * 1000 | coreFormatDate: timeFormat }} + + + + + {{ 'addon.calendar.type' + event.formattedType | translate }} + {{ event.iconTitle }} + + {{event.name}} +
+
+

+ {{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }} +

+
+ + + + +
diff --git a/src/addons/calendar/components/calendar/calendar.scss b/src/addons/calendar/components/calendar/calendar.scss index 848d6357c..5e22f595f 100644 --- a/src/addons/calendar/components/calendar/calendar.scss +++ b/src/addons/calendar/components/calendar/calendar.scss @@ -71,6 +71,7 @@ overflow: hidden; white-space: nowrap; color: var(--text-color); + min-height: auto; &.addon-calendar-event-past { opacity: 0.5; @@ -102,6 +103,7 @@ .addon-calendar-weekday { border-bottom: 1px solid var(--addon-calendar-border-color); + font-weight: bold; } .addon-calendar-day-events { diff --git a/src/addons/calendar/components/calendar/calendar.ts b/src/addons/calendar/components/calendar/calendar.ts index 34eb7ee31..5c28a9b69 100644 --- a/src/addons/calendar/components/calendar/calendar.ts +++ b/src/addons/calendar/components/calendar/calendar.ts @@ -41,6 +41,7 @@ import { AddonCalendarOffline } from '../../services/calendar-offline'; import { CoreCategoryData, CoreCourses } from '@features/courses/services/courses'; import { CoreApp } from '@services/app'; import { CoreLocalNotifications } from '@services/local-notifications'; +import { CoreAriaRoleButton } from '@classes/aria-role-button'; /** * Component that displays a calendar. @@ -67,6 +68,8 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro timeFormat?: string; isCurrentMonth = false; isPastMonth = false; + dayAction: AddonCalendarDayButton; + eventAction: AddonCalendarEventButton; protected year?: number; protected month?: number; @@ -88,6 +91,9 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro differs: KeyValueDiffers, ) { + this.dayAction = new AddonCalendarDayButton(this); + this.eventAction = new AddonCalendarEventButton(this); + this.currentSiteId = CoreSites.getCurrentSiteId(); if (CoreLocalNotifications.isAvailable()) { @@ -233,6 +239,10 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro this.weeks.forEach((week) => { week.days.forEach((day) => { + day.periodName = CoreTimeUtils.userDate( + new Date(this.year!, this.month! - 1, day.mday).getTime(), + 'core.strftimedaydate', + ); day.eventsFormated = day.eventsFormated || []; day.filteredEvents = day.filteredEvents || []; day.events.forEach((event) => { @@ -372,7 +382,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro * @param calendarEvent Calendar event.. * @param event Mouse event. */ - eventClicked(calendarEvent: AddonCalendarEventToDisplay, event: MouseEvent): void { + eventClicked(calendarEvent: AddonCalendarEventToDisplay, event: Event): void { this.onEventClicked.emit(calendarEvent.id); event.stopPropagation(); } @@ -525,3 +535,31 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro } } + +/** + * Helper class to manage day button. + */ +class AddonCalendarDayButton extends CoreAriaRoleButton { + + /** + * @inheritdoc + */ + click(event: Event, day: number): void { + this.componentInstance.dayClicked(day); + } + +} + +/** + * Helper class to manage event button. + */ +class AddonCalendarEventButton extends CoreAriaRoleButton { + + /** + * @inheritdoc + */ + click(event: Event, calendarEvent: AddonCalendarEventToDisplay): void { + this.componentInstance.eventClicked(calendarEvent, event); + } + +} diff --git a/src/addons/calendar/pages/edit-event/edit-event.html b/src/addons/calendar/pages/edit-event/edit-event.html index 78c05e1fb..fb22f3bd9 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.html +++ b/src/addons/calendar/pages/edit-event/edit-event.html @@ -16,9 +16,7 @@ -

- {{ 'addon.calendar.eventname' | translate }} -

+

{{ 'addon.calendar.eventname' | translate }}

@@ -27,11 +25,7 @@ - -

- {{ 'core.date' | translate }} -

-
+

{{ 'core.date' | translate }}

@@ -40,13 +34,15 @@ - -

- {{ 'addon.calendar.eventkind' | translate }} -

+ +

{{ 'addon.calendar.eventkind' | translate }}

- +

{{eventTypes[0].name | translate }}

+ {{ type.name | translate }} @@ -55,12 +51,8 @@ - -

- {{ 'core.category' | translate }} -

-
-

{{ 'core.category' | translate }}

+ {{ category.name }} @@ -70,12 +62,8 @@ - -

- {{ 'core.course' | translate }} -

-
-

{{ 'core.course' | translate }}

+ {{ course.fullname }} @@ -85,12 +73,8 @@ - -

- {{ 'core.course' | translate }} -

-
-

{{ 'core.course' | translate }}

+ @@ -104,12 +88,8 @@
- -

- {{ 'core.group' | translate }} -

-
-

{{ 'core.group' | translate }}

+ {{ group.name }} @@ -121,23 +101,20 @@
- +

{{ 'core.showmore' | translate }}

{{ 'core.showless' | translate }}

-
+
- -

{{ 'core.description' | translate }}

-
-

{{ 'core.description' | translate }}

+
@@ -154,9 +131,7 @@ -

- {{ 'addon.calendar.eventduration' | translate }} -

+

{{ 'addon.calendar.eventduration' | translate }}

@@ -200,9 +175,7 @@ -

- {{ 'addon.calendar.repeatedevents' | translate }} -

+

{{ 'addon.calendar.repeatedevents' | translate }}

diff --git a/src/addons/calendar/services/calendar.ts b/src/addons/calendar/services/calendar.ts index c1a01b8ec..b3b00e040 100644 --- a/src/addons/calendar/services/calendar.ts +++ b/src/addons/calendar/services/calendar.ts @@ -2026,6 +2026,7 @@ export type AddonCalendarWeekDay = AddonCalendarDay & { ispast?: boolean; // Calculated in the app. Whether the day is in the past. filteredEvents?: AddonCalendarEventToDisplay[]; // Calculated in the app. Filtered events. eventsFormated?: AddonCalendarEventToDisplay[]; // Events. + periodName?: string; }; /** diff --git a/src/core/classes/aria-role-button.ts b/src/core/classes/aria-role-button.ts index db15a0f9a..28a019a14 100644 --- a/src/core/classes/aria-role-button.ts +++ b/src/core/classes/aria-role-button.ts @@ -36,13 +36,14 @@ export abstract class CoreAriaRoleButton { * A11y key functionality that translates space and enter keys to click action. * * @param event Event. + * @param args Additional args. */ - keyUp(event: KeyboardEvent): void { + keyUp(event: KeyboardEvent, ...args: unknown[]): void { if ((event.key == ' ' || event.key == 'Enter') && this.isAllowed()) { event.preventDefault(); event.stopPropagation(); - this.click(event); + this.click(event, ...args); } } @@ -50,9 +51,10 @@ export abstract class CoreAriaRoleButton { * A11y click functionality. * * @param event Event. + * @param args Additional args. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - click(event?: Event): void { + click(event?: Event, ...args: unknown[]): void { // Nothing defined here. } diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts index b1990106d..0b53152f3 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts @@ -89,8 +89,8 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn protected resetObserver?: CoreEventObserver; protected initHeightInterval?: number; protected isCurrentView = true; - protected toolbarButtonWidth = 40; - protected toolbarArrowWidth = 28; + protected toolbarButtonWidth = 44; + protected toolbarArrowWidth = 44; protected pageInstance: string; protected autoSaveInterval?: number; protected hideMessageTimeout?: number; @@ -100,6 +100,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn protected resizeFunction?: () => Promise; protected selectionChangeFunction?: () => void; protected languageChangedSubscription?: Subscription; + protected resizeObserver?: IntersectionObserver; rteEnabled = false; isPhone = false; @@ -127,6 +128,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn initialSlide: 0, slidesPerView: 6, centerInsufficientSlides: true, + watchSlidesVisibility: true, }; constructor( @@ -136,6 +138,14 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn this.contentChanged = new EventEmitter(); this.element = elementRef.nativeElement as HTMLDivElement; this.pageInstance = 'app_' + Date.now(); // Generate a "unique" ID based on timestamp. + + if ('IntersectionObserver' in window) { + this.resizeObserver = new IntersectionObserver((observerEntry: IntersectionObserverEntry[]) => { + if (observerEntry[0].boundingClientRect.width > 0) { + this.updateToolbarButtons(); + } + }); + } } /** @@ -231,8 +241,12 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn }); this.resizeFunction = this.maximizeEditorSize.bind(this); - this.selectionChangeFunction = this.updateToolbarStyles.bind(this); window.addEventListener('resize', this.resizeFunction!); + + // Start observing the target node for configured mutations + this.resizeObserver?.observe(this.element); + + this.selectionChangeFunction = this.updateToolbarStyles.bind(this); document.addEventListener('selectionchange', this.selectionChangeFunction!); this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, (kbHeight: number) => { @@ -273,7 +287,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn setTimeout(async () => { // Editor is ready, adjust Height if needed. - let height; + let height: number; if (CoreApp.isAndroid()) { // In Android we ignore the keyboard height because it is not part of the web view. @@ -760,6 +774,11 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn * Show the toolbar. */ showToolbar(event: Event): void { + if (!('IntersectionObserver' in window)) { + // Fallback if IntersectionObserver is not supported. + this.updateToolbarButtons(); + } + this.element.classList.add('ion-touched'); this.element.classList.remove('ion-untouched'); this.element.classList.add('has-focus'); @@ -776,7 +795,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn * @param event Event. */ stopBubble(event: Event): void { - if (event.type != 'mouseup' && event.type != 'keyup') { + if (event.type != 'touchend' &&event.type != 'mouseup' && event.type != 'keyup') { event.preventDefault(); } event.stopPropagation(); @@ -840,7 +859,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn * Update the number of toolbar buttons displayed. */ async updateToolbarButtons(): Promise { - if (!this.isCurrentView || !this.toolbar || !this.toolbarSlides) { + if (!this.isCurrentView || !this.toolbar || !this.toolbarSlides || this.element.offsetParent == null) { // Don't calculate if component isn't in current view, the calculations are wrong. return; } @@ -856,7 +875,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn return; } - if (width > length * this.toolbarButtonWidth) { + if (length > 0 && width > length * this.toolbarButtonWidth) { this.slidesOpts = { ...this.slidesOpts, slidesPerView: length }; this.toolbarArrows = false; } else { @@ -1096,6 +1115,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn clearInterval(this.initHeightInterval); clearInterval(this.autoSaveInterval); clearTimeout(this.hideMessageTimeout); + this.resizeObserver?.disconnect(); this.resetObserver?.off(); this.keyboardObserver?.off(); } From de6442daab389410bcc87bac6126313d8947c8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 11 May 2021 17:11:59 +0200 Subject: [PATCH 3/5] MOBILE-3753 messages: Add context to messages badges --- scripts/langindex.json | 6 ++++ src/addons/messages/lang.json | 5 +++- .../group-conversations.html | 28 ++++++++++++++----- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 7ca7eb8c2..97ebde71e 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -281,6 +281,7 @@ "addon.messages.noncontacts": "message", "addon.messages.nousersfound": "local_moodlemobileapp", "addon.messages.numparticipants": "message", + "addon.messages.pendingcontactrequests": "message", "addon.messages.removecontact": "message", "addon.messages.removecontactconfirm": "message", "addon.messages.removefromfavourites": "message", @@ -301,6 +302,8 @@ "addon.messages.unblockuser": "message", "addon.messages.unblockuserconfirm": "message", "addon.messages.unmuteconversation": "message", + "addon.messages.unreadconversations": "message", + "addon.messages.unreadmessages": "message", "addon.messages.useentertosend": "message", "addon.messages.useentertosenddescdesktop": "local_moodlemobileapp", "addon.messages.useentertosenddescmac": "local_moodlemobileapp", @@ -1450,6 +1453,7 @@ "core.course.completion_automatic:todo": "course", "core.course.completion_manual:aria:done": "course", "core.course.completion_manual:aria:markdone": "course", + "core.course.completion_manual:done": "course", "core.course.completion_manual:markdone": "course", "core.course.completion_setby:auto:done": "course", "core.course.completion_setby:auto:todo": "course", @@ -1748,6 +1752,7 @@ "core.hasdatatosync": "local_moodlemobileapp", "core.help": "moodle", "core.hide": "moodle", + "core.hideadvanced": "form", "core.hour": "moodle", "core.hours": "moodle", "core.humanreadablesize": "local_moodlemobileapp", @@ -2114,6 +2119,7 @@ "core.sharedfiles.sharedfiles": "local_moodlemobileapp", "core.sharedfiles.successstorefile": "local_moodlemobileapp", "core.show": "moodle", + "core.showadvanced": "form", "core.showless": "form", "core.showmore": "form", "core.site": "moodle", diff --git a/src/addons/messages/lang.json b/src/addons/messages/lang.json index 0b728d3e9..a6c556cda 100644 --- a/src/addons/messages/lang.json +++ b/src/addons/messages/lang.json @@ -51,6 +51,7 @@ "noncontacts": "Non-contacts", "nousersfound": "No users found", "numparticipants": "{{$a}} participants", + "pendingcontactrequests": "There are {{$a}} pending contact requests", "removecontact": "Remove contact", "removecontactconfirm": "Are you sure you want to remove {{$a}} from your contacts?", "removefromfavourites": "Unstar conversation", @@ -71,6 +72,8 @@ "unblockuser": "Unblock user", "unblockuserconfirm": "Are you sure you want to unblock {{$a}}?", "unmuteconversation": "Unmute", + "unreadconversations": "There are {{$a}} unread conversations", + "unreadmessages": "There are {{$a}} unread messages", "useentertosend": "Use enter to send", "useentertosenddescdesktop": "If disabled, you can use Ctrl+Enter to send the message.", "useentertosenddescmac": "If disabled, you can use Cmd+Enter to send the message.", @@ -81,4 +84,4 @@ "you": "You:", "youhaveblockeduser": "You have blocked this user.", "yourcontactrequestpending": "Your contact request is pending with {{$a}}" -} \ No newline at end of file +} diff --git a/src/addons/messages/pages/group-conversations/group-conversations.html b/src/addons/messages/pages/group-conversations/group-conversations.html index bbcea3e60..5a2a475c6 100644 --- a/src/addons/messages/pages/group-conversations/group-conversations.html +++ b/src/addons/messages/pages/group-conversations/group-conversations.html @@ -25,11 +25,13 @@ - +

{{ 'addon.messages.contacts' | translate }}

- {{contactRequestsCount}} + + + {{ 'addon.messages.pendingcontactrequests' | translate:{$a: contactRequestsCount} }} +
@@ -78,7 +83,10 @@

{{ 'addon.messages.groupconversations' | translate }} ({{ group.count }})

- {{ group.unread }} + + + {{ 'addon.messages.unreadconversations' | translate:{$a: group.unread} }} +
@@ -111,7 +119,10 @@

{{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }})

- {{ individual.unread }} + + + {{ 'addon.messages.unreadconversations' | translate:{$a: individual.unread} }} +
@@ -171,7 +182,10 @@

- {{ conversation.unreadcount }} + {{ conversation.unreadcount }} + + {{ 'addon.messages.unreadmessages' | translate:{$a: conversation.unreadcount} }} + {{conversation.lastmessagedate | coreDateDayOrTime}} From 4c59d4ce8104f58190b9e0bf2d78703532b0d90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 11 May 2021 17:28:10 +0200 Subject: [PATCH 4/5] MOBILE-3753 menu: Add context to main menu badges --- src/addons/messages/services/handlers/mainmenu.ts | 1 + src/addons/notifications/services/handlers/mainmenu.ts | 1 + src/core/features/mainmenu/pages/menu/menu.html | 9 ++++++--- src/core/features/mainmenu/pages/more/more.html | 6 +++++- src/core/features/mainmenu/services/mainmenu-delegate.ts | 5 +++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/addons/messages/services/handlers/mainmenu.ts b/src/addons/messages/services/handlers/mainmenu.ts index 4461ee4f9..2f8021415 100644 --- a/src/addons/messages/services/handlers/mainmenu.ts +++ b/src/addons/messages/services/handlers/mainmenu.ts @@ -47,6 +47,7 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler, class: 'addon-messages-handler', showBadge: true, // Do not check isMessageCountEnabled because we'll use fallback it not enabled. badge: '', + badgeA11yText: 'addon.messages.unreadconversations', loading: true, }; diff --git a/src/addons/notifications/services/handlers/mainmenu.ts b/src/addons/notifications/services/handlers/mainmenu.ts index dbf5aeb1e..5cb4584d2 100644 --- a/src/addons/notifications/services/handlers/mainmenu.ts +++ b/src/addons/notifications/services/handlers/mainmenu.ts @@ -41,6 +41,7 @@ export class AddonNotificationsMainMenuHandlerService implements CoreMainMenuHan class: 'addon-notifications-handler', showBadge: true, badge: '', + badgeA11yText: 'addon.notifications.unreadnotification', loading: true, }; diff --git a/src/core/features/mainmenu/pages/menu/menu.html b/src/core/features/mainmenu/pages/menu/menu.html index 1b2d09b25..ba8099d71 100644 --- a/src/core/features/mainmenu/pages/menu/menu.html +++ b/src/core/features/mainmenu/pages/menu/menu.html @@ -16,11 +16,14 @@ [selected]="tab.page === selectedTab" [tabindex]="selectedTab == tab.page ? 0 : -1" [attr.aria-controls]="tab.id" - [attr.aria-label]="tab.title | translate" > - {{ tab.badge }} + + {{ tab.title | translate }} + + {{ tab.badgeA11yText | translate: {$a : tab.badge } }} + + {{ 'core.more' | translate }} diff --git a/src/core/features/mainmenu/pages/more/more.html b/src/core/features/mainmenu/pages/more/more.html index 79b029c4e..7811f7d7a 100644 --- a/src/core/features/mainmenu/pages/more/more.html +++ b/src/core/features/mainmenu/pages/more/more.html @@ -30,8 +30,12 @@

{{ handler.title | translate}}

- {{handler.badge}} + + + {{ handler.badgeA11yText | translate: {$a : handler.badge } }} + diff --git a/src/core/features/mainmenu/services/mainmenu-delegate.ts b/src/core/features/mainmenu/services/mainmenu-delegate.ts index 862bb5b55..998550f49 100644 --- a/src/core/features/mainmenu/services/mainmenu-delegate.ts +++ b/src/core/features/mainmenu/services/mainmenu-delegate.ts @@ -65,6 +65,11 @@ export interface CoreMainMenuHandlerData { */ badge?: string; + /** + * Accessibility text to add on the badge. Only used if showBadge is true. + */ + badgeA11yText?: string; + /** * If true, the badge number is being loaded. Only used if showBadge is true. */ From a68d84c4d4434f8160e3dbe5cbc3c48a2655e4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 12 May 2021 16:35:45 +0200 Subject: [PATCH 5/5] MOBILE-3753 a11y: More aria role button to a new directive --- .../calendar/addon-calendar-calendar.html | 8 +-- .../calendar/components/calendar/calendar.ts | 35 ---------- src/core/classes/aria-role-button.ts | 70 ------------------- .../user-avatar/core-user-avatar.html | 7 +- .../components/user-avatar/user-avatar.ts | 46 +++--------- src/core/directives/aria-button.ts | 57 +++++++++++++++ src/core/directives/directives.module.ts | 3 + 7 files changed, 73 insertions(+), 153 deletions(-) delete mode 100644 src/core/classes/aria-role-button.ts create mode 100644 src/core/directives/aria-button.ts diff --git a/src/addons/calendar/components/calendar/addon-calendar-calendar.html b/src/addons/calendar/components/calendar/addon-calendar-calendar.html index 7fe5c0da7..ccae4c3f1 100644 --- a/src/addons/calendar/components/calendar/addon-calendar-calendar.html +++ b/src/addons/calendar/components/calendar/addon-calendar-calendar.html @@ -58,9 +58,7 @@ [class.addon-calendar-event-past-day]="isPastMonth || day.ispast" role="button cell" tabindex="0" - (click)="dayClicked(day.mday)" - (keyup)="dayAction.keyUp($event, day.mday)" - (keydown)="dayAction.keyDown($event)" + (ariaButtonClick)="dayClicked(day.mday)" >

@@ -80,9 +78,7 @@ [class.addon-calendar-event-past]="event.ispast" role="button" tabindex="0" - (click)="eventClicked(event, $event)" - (keyup)="eventAction.keyUp($event, event)" - (keydown)="eventAction.keyDown($event)" + (ariaButtonClick)="eventClicked(event, $event)" > { - - /** - * @inheritdoc - */ - click(event: Event, day: number): void { - this.componentInstance.dayClicked(day); - } - -} - -/** - * Helper class to manage event button. - */ -class AddonCalendarEventButton extends CoreAriaRoleButton { - - /** - * @inheritdoc - */ - click(event: Event, calendarEvent: AddonCalendarEventToDisplay): void { - this.componentInstance.eventClicked(calendarEvent, event); - } - -} diff --git a/src/core/classes/aria-role-button.ts b/src/core/classes/aria-role-button.ts deleted file mode 100644 index 28a019a14..000000000 --- a/src/core/classes/aria-role-button.ts +++ /dev/null @@ -1,70 +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. - -export abstract class CoreAriaRoleButton { - - componentInstance: T; - - constructor(componentInstance: T) { - this.componentInstance = componentInstance; - } - - /** - * A11y key functionality that prevents keyDown events. - * - * @param event Event. - */ - keyDown(event: KeyboardEvent): void { - if ((event.key == ' ' || event.key == 'Enter') && this.isAllowed()) { - event.preventDefault(); - event.stopPropagation(); - } - } - - /** - * A11y key functionality that translates space and enter keys to click action. - * - * @param event Event. - * @param args Additional args. - */ - keyUp(event: KeyboardEvent, ...args: unknown[]): void { - if ((event.key == ' ' || event.key == 'Enter') && this.isAllowed()) { - event.preventDefault(); - event.stopPropagation(); - - this.click(event, ...args); - } - } - - /** - * A11y click functionality. - * - * @param event Event. - * @param args Additional args. - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - click(event?: Event, ...args: unknown[]): void { - // Nothing defined here. - } - - /** - * Checks if action is allowed in class. - * - * @returns If allowed. - */ - isAllowed(): boolean { - return true; - } - -} diff --git a/src/core/components/user-avatar/core-user-avatar.html b/src/core/components/user-avatar/core-user-avatar.html index 4b8d7819d..474334be0 100644 --- a/src/core/components/user-avatar/core-user-avatar.html +++ b/src/core/components/user-avatar/core-user-avatar.html @@ -4,18 +4,15 @@ [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'" - (click)="gotoProfile($event)" + (ariaButtonClick)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" [attr.role]="linkProfile ? 'button' : null" - (keydown)="buttonAction.keyDown($event)" - (keyup)="buttonAction.keyUp($event)" [attr.tabindex]="linkProfile ? 0 : null" [class.clickable]="linkProfile" > diff --git a/src/core/components/user-avatar/user-avatar.ts b/src/core/components/user-avatar/user-avatar.ts index 6097a0371..28815d4b4 100644 --- a/src/core/components/user-avatar/user-avatar.ts +++ b/src/core/components/user-avatar/user-avatar.ts @@ -20,7 +20,6 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreUserProvider, CoreUserBasicData } from '@features/user/services/user'; import { CoreNavigator } from '@services/navigator'; -import { CoreAriaRoleButton } from '@classes/aria-role-button'; /** * Component to display a "user avatar". @@ -39,15 +38,13 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { @Input() profileUrl?: string; @Input() linkProfile = true; // Avoid linking to the profile if wanted. @Input() fullname?: string; - @Input() userId?: number; // If provided or found it will be used to link the image to the profile. - @Input() courseId?: number; + @Input() protected userId?: number; // If provided or found it will be used to link the image to the profile. + @Input() protected courseId?: number; @Input() checkOnline = false; // If want to check and show online status. @Input() extraIcon?: string; // Extra icon to show near the avatar. avatarUrl?: string; - buttonAction: CoreUserAvatarButton; - // Variable to check if we consider this user online or not. // @TODO: Use setting when available (see MDL-63972) so we can use site setting. protected timetoshowusers = 300000; // Miliseconds default. @@ -55,7 +52,6 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { protected pictureObserver: CoreEventObserver; constructor() { - this.currentUserId = CoreSites.getCurrentSiteUserId(); this.pictureObserver = CoreEvents.on( @@ -67,15 +63,12 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { }, CoreSites.getCurrentSiteId(), ); - - this.buttonAction = new CoreUserAvatarButton(this); } /** * Component being initialized. */ ngOnInit(): void { - this.setFields(); } @@ -137,14 +130,19 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { * @param event Click event. */ gotoProfile(event: Event): void { - if (!this.buttonAction.isAllowed()) { + if (!this.linkProfile || !this.userId) { return; } event.preventDefault(); event.stopPropagation(); - this.buttonAction.click(); + CoreNavigator.navigateToSitePath('user', { + params: { + userId: this.userId, + courseId: this.courseId, + }, + }); } /** @@ -156,32 +154,6 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { } -/** - * Helper class to manage rol button. - */ -class CoreUserAvatarButton extends CoreAriaRoleButton { - - /** - * @inheritdoc - */ - click(): void { - CoreNavigator.navigateToSitePath('user', { - params: { - userId: this.componentInstance.userId, - courseId: this.componentInstance.courseId, - }, - }); - } - - /** - * @inheritdoc - */ - isAllowed(): boolean { - return this.componentInstance.linkProfile && !!this.componentInstance.userId; - } - -} - /** * Type with all possible formats of user. */ diff --git a/src/core/directives/aria-button.ts b/src/core/directives/aria-button.ts new file mode 100644 index 000000000..092c53737 --- /dev/null +++ b/src/core/directives/aria-button.ts @@ -0,0 +1,57 @@ +// (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 { Directive, ElementRef, OnInit, Output, EventEmitter } from '@angular/core'; + +/** + * Directive to emulate click and key actions following aria role button. + */ +@Directive({ + selector: '[ariaButtonClick]', +}) +export class CoreAriaButtonClickDirective implements OnInit { + + protected element: HTMLElement; + + @Output() ariaButtonClick = new EventEmitter(); + + constructor( + element: ElementRef, + ) { + this.element = element.nativeElement; + } + + /** + * Initialize actions. + */ + ngOnInit(): void { + this.element.addEventListener('click', async (event) => { + this.ariaButtonClick.emit(event); + }); + + this.element.addEventListener('keydown', async (event) => { + if ((event.key == ' ' || event.key == 'Enter')) { + event.preventDefault(); + event.stopPropagation(); + } + }); + + this.element.addEventListener('keyup', async (event) => { + if ((event.key == ' ' || event.key == 'Enter')) { + this.ariaButtonClick.emit(event); + } + }); + } + +} diff --git a/src/core/directives/directives.module.ts b/src/core/directives/directives.module.ts index 3cf6d3303..b83ee4534 100644 --- a/src/core/directives/directives.module.ts +++ b/src/core/directives/directives.module.ts @@ -24,6 +24,7 @@ import { CoreLinkDirective } from './link'; import { CoreLongPressDirective } from './long-press'; import { CoreSupressEventsDirective } from './supress-events'; import { CoreUserLinkDirective } from './user-link'; +import { CoreAriaButtonClickDirective } from './aria-button'; @NgModule({ declarations: [ @@ -37,6 +38,7 @@ import { CoreUserLinkDirective } from './user-link'; CoreLongPressDirective, CoreSupressEventsDirective, CoreUserLinkDirective, + CoreAriaButtonClickDirective, ], exports: [ CoreAutoFocusDirective, @@ -49,6 +51,7 @@ import { CoreUserLinkDirective } from './user-link'; CoreLongPressDirective, CoreSupressEventsDirective, CoreUserLinkDirective, + CoreAriaButtonClickDirective, ], }) export class CoreDirectivesModule {}