Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Fix keyboard accessibility
Signed-off-by: Carl Schwan <[email protected]>
  • Loading branch information
CarlSchwan authored and nickvergessen committed Jul 29, 2022
commit 8b7eb8dd4f8ef18e3496c99b314bb1e14b63eaf1
4 changes: 2 additions & 2 deletions js/notifications-main.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions js/notifications-main.js.LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@

/*! For license information please see UserBubble.js.LICENSE.txt */

/*! For license information please see excludeClickOutsideClasses.js.LICENSE.txt */

/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */

/**
Expand Down
2 changes: 1 addition & 1 deletion js/notifications-main.js.map

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^5.3.1",
"howler": "^2.2.3",
"v-click-outside": "^3.2.0",
"vue": "^2.7.4",
"vue-material-design-icons": "^5.1.2",
"vue-tooltip": "^0.1.0"
Expand Down
224 changes: 224 additions & 0 deletions src/Components/HeaderMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <[email protected]>
-
- @author John Molakvoæ <[email protected]>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div :id="id"
v-click-outside="clickOutsideConfig"
:class="{ 'header-menu--opened': opened }"
class="header-menu">
<a class="header-menu__trigger"
href="#"
:aria-label="ariaLabel"
:aria-controls="`header-menu-${id}`"
:aria-expanded="opened"
aria-haspopup="menu"
@click.prevent="toggleMenu">
<slot name="trigger" />
</a>
<div v-show="opened" class="header-menu__carret" />
<div v-show="opened"
:id="`header-menu-${id}`"
class="header-menu__wrapper"
role="menu">
<div class="header-menu__content">
<slot />
</div>
</div>
</div>
</template>

<script>
import { directive as ClickOutside } from 'v-click-outside'
import excludeClickOutsideClasses from '@nextcloud/vue/dist/Mixins/excludeClickOutsideClasses'

export default {
name: 'HeaderMenu',

directives: {
ClickOutside,
},

mixins: [
excludeClickOutsideClasses,
],

props: {
id: {
type: String,
required: true,
},
ariaLabel: {
type: String,
default: '',
},
open: {
type: Boolean,
default: false,
},
},

data() {
return {
opened: this.open,
clickOutsideConfig: {
handler: this.closeMenu,
middleware: this.clickOutsideMiddleware,
},
}
},

watch: {
open(newVal) {
this.opened = newVal
this.$nextTick(() => {
if (this.opened) {
this.openMenu()
} else {
this.closeMenu()
}
})
},
},

mounted() {
document.addEventListener('keydown', this.onKeyDown)
},
beforeDestroy() {
document.removeEventListener('keydown', this.onKeyDown)
},

methods: {
/**
* Toggle the current menu open state
*/
toggleMenu() {
// Toggling current state
if (!this.opened) {
this.openMenu()
} else {
this.closeMenu()
}
},

/**
* Close the current menu
*/
closeMenu() {
if (!this.opened) {
return
}

this.opened = false
this.$emit('close')
this.$emit('update:open', false)
},

/**
* Open the current menu
*/
openMenu() {
if (this.opened) {
return
}

this.opened = true
this.$emit('open')
this.$emit('update:open', true)
},

onKeyDown(event) {
// If opened and escape pressed, close
if (event.key === 'Escape' && this.opened) {
event.preventDefault()

/** user cancelled the menu by pressing escape */
this.$emit('cancel')

/** we do NOT fire a close event to differentiate cancel and close */
this.opened = false
this.$emit('update:open', false)
}
},
},
}
</script>

<style lang="scss" scoped>
.header-menu {
&__trigger {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 44px;
margin: 2px 0;
padding: 0;
cursor: pointer;
opacity: .85;
}

&--opened &__trigger,
&__trigger:hover,
&__trigger:focus,
&__trigger:active {
opacity: 1;
}

&__trigger:focus-visible {
outline: none;
}

&__wrapper {
position: fixed;
z-index: 2000;
top: 50px;
right: 0;
box-sizing: border-box;
margin: 0;
border-radius: 0 0 var(--border-radius) var(--border-radius);
background-color: var(--color-main-background);

filter: drop-shadow(0 1px 5px var(--color-box-shadow));
}

&__carret {
position: absolute;
z-index: 2001; // Because __wrapper is 2000.
left: calc(50% - 10px);
bottom: 0;
width: 0;
height: 0;
content: ' ';
pointer-events: none;
border: 10px solid transparent;
border-bottom-color: var(--color-main-background);
}

&__content {
overflow: auto;
width: 350px;
max-width: 100vw;
min-height: calc(44px * 1.5);
max-height: calc(100vh - 50px * 2);
}
}

</style>
4 changes: 2 additions & 2 deletions src/Components/Notification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ export default {
mounted() {
this._$el = $(this.$el)

// Parents: TransitionGroup > NotificationsList
if (typeof this.$parent.$parent.showBrowserNotifications === 'undefined') {
// Parents: TransitionGroup > Transition > HeaderMenu
if (typeof this.$parent.$parent.$parent.showBrowserNotifications === 'undefined') {
console.error('Failed to read showBrowserNotifications property from App component')
}

Expand Down
32 changes: 17 additions & 15 deletions src/NotificationsApp.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
<template>
<div v-if="!shutdown" class="notifications">
<div ref="button"
class="notifications-button menutoggle"
:class="{ hasNotifications: notifications.length }"
tabindex="0"
role="button"
:aria-label="t('notifications', 'Notifications')"
aria-haspopup="true"
aria-controls="notification-container"
aria-expanded="false"
@click="requestWebNotificationPermissions"
@keydown.enter="requestWebNotificationPermissions">
<HeaderMenu v-if="!shutdown"
id="notifications"
class="notifications-button"
exclude-click-outside-classes="popover"
:open.sync="open"
:aria-label="t('notifications', 'Notifications')"
@open="onOpen">
<template #trigger>
<Bell v-if="notifications.length === 0"
:size="20"
:title="t('notifications', 'Notifications')"
Expand All @@ -27,7 +23,7 @@
<path d="M 19,11.79 C 18.5,11.92 18,12 17.5,12 14.47,12 12,9.53 12,6.5 12,5.03 12.58,3.7 13.5,2.71 13.15,2.28 12.61,2 12,2 10.9,2 10,2.9 10,4 V 4.29 C 7.03,5.17 5,7.9 5,11 v 6 l -2,2 v 1 H 21 V 19 L 19,17 V 11.79 M 12,23 c 1.11,0 2,-0.89 2,-2 h -4 c 0,1.11 0.9,2 2,2 z" />
<path :class="isRedThemed ? 'notification__dot--white' : ''" class="notification__dot" d="M 21,6.5 C 21,8.43 19.43,10 17.5,10 15.57,10 14,8.43 14,6.5 14,4.57 15.57,3 17.5,3 19.43,3 21,4.57 21,6.5" />
</svg>
</div>
</template>

<!-- Notifications list content -->
<div ref="container" class="notification-container">
Expand Down Expand Up @@ -71,7 +67,7 @@
</EmptyContent>
</transition>
</div>
</div>
</HeaderMenu>
</template>

<script>
Expand All @@ -87,6 +83,7 @@ import { listen } from '@nextcloud/notify_push'
import Bell from 'vue-material-design-icons/Bell'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import { getCapabilities } from '@nextcloud/capabilities'
import HeaderMenu from './Components/HeaderMenu'

export default {
name: 'NotificationsApp',
Expand All @@ -96,6 +93,7 @@ export default {
Close,
Bell,
EmptyContent,
HeaderMenu,
Notification,
},

Expand All @@ -120,6 +118,8 @@ export default {
/** @type {number|null} */
interval: null,
pushEndpoints: null,

open: false,
}
},

Expand Down Expand Up @@ -182,6 +182,9 @@ export default {
},

methods: {
onOpen() {
this.requestWebNotificationPermissions()
},
handleNetworkOffline() {
console.debug('Network is offline, slowing down pollingInterval to ' + this.pollIntervalBase * 10)
this._setPollingInterval(this.pollIntervalBase * 10)
Expand Down Expand Up @@ -447,5 +450,4 @@ export default {
.list-leave-active {
width: 100%;
}

</style>
Loading