]> BookStack Code Mirror - bookstack/commitdiff
Made a mass of accessibility improvements
authorDan Brown <redacted>
Sat, 24 Aug 2019 17:26:28 +0000 (18:26 +0100)
committerDan Brown <redacted>
Sat, 24 Aug 2019 17:29:02 +0000 (18:29 +0100)
- Changed default focus styles
- Updated dropdowns with keyboard navigation
- Updated modals with esc exiting
- Added accessibility attirbutes where needed
- Made many more elements focusable
- Updated hover effects of many items to also apply when focused within

Related to #1320 and #1198

35 files changed:
app/Http/Controllers/PageController.php
app/helpers.php
resources/assets/js/components/breadcrumb-listing.js
resources/assets/js/components/chapter-toggle.js
resources/assets/js/components/dropdown.js
resources/assets/js/components/overlay.js
resources/assets/js/services/dom.js
resources/assets/js/vues/code-editor.js
resources/assets/sass/_buttons.scss
resources/assets/sass/_components.scss
resources/assets/sass/_forms.scss
resources/assets/sass/_header.scss
resources/assets/sass/_html.scss
resources/assets/sass/_layout.scss
resources/assets/sass/_lists.scss
resources/lang/en/common.php
resources/lang/en/entities.php
resources/views/books/show.blade.php
resources/views/chapters/child-menu.blade.php
resources/views/chapters/show.blade.php
resources/views/comments/comment.blade.php
resources/views/common/header.blade.php
resources/views/components/code-editor.blade.php
resources/views/components/entity-selector-popup.blade.php
resources/views/components/expand-toggle.blade.php
resources/views/components/image-manager.blade.php
resources/views/pages/form.blade.php
resources/views/pages/revisions.blade.php
resources/views/pages/show.blade.php
resources/views/partials/book-tree.blade.php
resources/views/partials/breadcrumb-listing.blade.php
resources/views/partials/entity-dashboard-search-box.blade.php
resources/views/partials/entity-export-menu.blade.php [new file with mode: 0644]
resources/views/partials/entity-list-item-basic.blade.php
resources/views/partials/sort.blade.php

index 8819510a6d4388bd600e0c89bee5e9038a09fbda..ad1e3266597f2183aa0cd72a4b668d052e7eb2a8 100644 (file)
@@ -495,7 +495,7 @@ class PageController extends Controller
 
         $revision->delete();
         session()->flash('success', trans('entities.revision_delete_success'));
-        return view('pages.revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
+        return redirect($page->getUrl('/revisions'));
     }
 
     /**
index 9bbfcfbf0fc6535a25cec4750d90006c48d444ea..f36f2e59dcef11b461cd5efdf239f6ea683dda1c 100644 (file)
@@ -133,8 +133,9 @@ function theme_path($path = '') : string
 function icon($name, $attrs = [])
 {
     $attrs = array_merge([
-        'class' => 'svg-icon',
-        'data-icon' => $name
+        'class'     => 'svg-icon',
+        'data-icon' => $name,
+        'role'      => 'presentation',
     ], $attrs);
     $attrString = ' ';
     foreach ($attrs as $attrName => $attr) {
index 11e1522db30753356cc90ee969269055d3235389..7f4344b17d7f1b4452f920d30eda7a3d4d72049e 100644 (file)
@@ -7,35 +7,14 @@ class BreadcrumbListing {
         this.searchInput = elem.querySelector('input');
         this.loadingElem = elem.querySelector('.loading-container');
         this.entityListElem = elem.querySelector('.breadcrumb-listing-entity-list');
-        this.toggleElem = elem.querySelector('[dropdown-toggle]');
 
         // this.loadingElem.style.display = 'none';
         const entityDescriptor = elem.getAttribute('breadcrumb-listing').split(':');
         this.entityType = entityDescriptor[0];
         this.entityId = Number(entityDescriptor[1]);
 
-        this.toggleElem.addEventListener('click', this.onShow.bind(this));
+        this.elem.addEventListener('show', this.onShow.bind(this));
         this.searchInput.addEventListener('input', this.onSearch.bind(this));
-        this.elem.addEventListener('keydown', this.keyDown.bind(this));
-    }
-
-    keyDown(event) {
-        if (event.key === 'ArrowDown') {
-            this.listFocusChange(1);
-            event.preventDefault();
-        } else if  (event.key === 'ArrowUp') {
-            this.listFocusChange(-1);
-            event.preventDefault();
-        }
-    }
-
-    listFocusChange(indexChange = 1) {
-        const links = Array.from(this.entityListElem.querySelectorAll('a:not(.hidden)'));
-        const currentFocused = this.entityListElem.querySelector('a:focus');
-        const currentFocusedIndex = links.indexOf(currentFocused);
-        const defaultFocus = (indexChange > 0) ? links[0] : this.searchInput;
-        const nextElem = links[currentFocusedIndex + indexChange] || defaultFocus;
-        nextElem.focus();
     }
 
     onShow() {
index a751206d16afa1dd2b76708c33f90131f9579c83..bfd0ac7296576f6151aa61e686a3591fd6b72e6a 100644 (file)
@@ -11,12 +11,14 @@ class ChapterToggle {
     open() {
         const list = this.elem.parentNode.querySelector('.inset-list');
         this.elem.classList.add('open');
+        this.elem.setAttribute('aria-expanded', 'true');
         slideDown(list, 240);
     }
 
     close() {
         const list = this.elem.parentNode.querySelector('.inset-list');
         this.elem.classList.remove('open');
+        this.elem.setAttribute('aria-expanded', 'false');
         slideUp(list, 240);
     }
 
index 3887e8432289d23e2f81cd01424c7476428c8e33..e2bb21b0cf3bfbba0cc33ea0c8094a42e78a1460 100644 (file)
@@ -1,3 +1,5 @@
+import {onSelect} from "../services/dom";
+
 /**
  * Dropdown
  * Provides some simple logic to create simple dropdown menus.
@@ -10,14 +12,16 @@ class DropDown {
         this.moveMenu = elem.hasAttribute('dropdown-move-menu');
         this.toggle = elem.querySelector('[dropdown-toggle]');
         this.body = document.body;
+        this.showing = false;
         this.setupListeners();
     }
 
-    show(event) {
+    show(event = null) {
         this.hideAll();
 
         this.menu.style.display = 'block';
         this.menu.classList.add('anim', 'menuIn');
+        this.toggle.setAttribute('aria-expanded', 'true');
 
         if (this.moveMenu) {
             // Move to body to prevent being trapped within scrollable sections
@@ -38,10 +42,17 @@ class DropDown {
         });
 
         // Focus on first input if existing
-        let input = this.menu.querySelector('input');
+        const input = this.menu.querySelector('input');
         if (input !== null) input.focus();
 
-        event.stopPropagation();
+        this.showing = true;
+
+        const showEvent = new Event('show');
+        this.container.dispatchEvent(showEvent);
+
+        if (event) {
+            event.stopPropagation();
+        }
     }
 
     hideAll() {
@@ -53,6 +64,7 @@ class DropDown {
     hide() {
         this.menu.style.display = 'none';
         this.menu.classList.remove('anim', 'menuIn');
+        this.toggle.setAttribute('aria-expanded', 'false');
         if (this.moveMenu) {
             this.menu.style.position = '';
             this.menu.style.left = '';
@@ -60,22 +72,74 @@ class DropDown {
             this.menu.style.width = '';
             this.container.appendChild(this.menu);
         }
+        this.showing = false;
+    }
+
+    getFocusable() {
+        return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
+    }
+
+    focusNext() {
+        const focusable = this.getFocusable();
+        const currentIndex = focusable.indexOf(document.activeElement);
+        let newIndex = currentIndex + 1;
+        if (newIndex >= focusable.length) {
+            newIndex = 0;
+        }
+
+        focusable[newIndex].focus();
+    }
+
+    focusPrevious() {
+        const focusable = this.getFocusable();
+        const currentIndex = focusable.indexOf(document.activeElement);
+        let newIndex = currentIndex - 1;
+        if (newIndex < 0) {
+            newIndex = focusable.length - 1;
+        }
+
+        focusable[newIndex].focus();
     }
 
     setupListeners() {
         // Hide menu on option click
         this.container.addEventListener('click', event => {
-             let possibleChildren = Array.from(this.menu.querySelectorAll('a'));
-             if (possibleChildren.indexOf(event.target) !== -1) this.hide();
+             const possibleChildren = Array.from(this.menu.querySelectorAll('a'));
+             if (possibleChildren.includes(event.target)) {
+                 this.hide();
+             }
+        });
+
+        onSelect(this.toggle, event => {
+            event.stopPropagation();
+            console.log('cat', event);
+            this.show(event);
+            if (event instanceof KeyboardEvent) {
+                this.focusNext();
+            }
+        });
+
+        // Arrow navigation
+        this.container.addEventListener('keydown', event => {
+            if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
+                this.focusNext();
+                event.preventDefault();
+            } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
+                this.focusPrevious();
+                event.preventDefault();
+            } else if (event.key === 'Escape') {
+                this.hide();
+                event.stopPropagation();
+            }
         });
-        // Show dropdown on toggle click
-        this.toggle.addEventListener('click', this.show.bind(this));
-        // Hide menu on enter press
-        this.container.addEventListener('keypress', event => {
-                if (event.keyCode !== 13) return true;
+
+        // Hide menu on enter press or escape
+        this.menu.addEventListener('keydown ', event => {
+            if (event.key === 'Enter') {
                 event.preventDefault();
+                event.stopPropagation();
                 this.hide();
-                return false;
+            }
         });
     }
 
index 1ba5efceadf553e3800f79ed50746d6984014f9f..ad6a01061ec033c0beaebc33fa003a665b57b0d7 100644 (file)
@@ -6,12 +6,22 @@ class Overlay {
         elem.addEventListener('click', event => {
              if (event.target === elem) return this.hide();
         });
+
+        window.addEventListener('keyup', event => {
+            if (event.key === 'Escape') {
+                this.hide();
+            }
+        });
+
         let closeButtons = elem.querySelectorAll('.popup-header-close');
         for (let i=0; i < closeButtons.length; i++) {
             closeButtons[i].addEventListener('click', this.hide.bind(this));
         }
     }
 
+    hide() { this.toggle(false); }
+    show() { this.toggle(true); }
+
     toggle(show = true) {
         let start = Date.now();
         let duration = 240;
@@ -22,6 +32,9 @@ class Overlay {
             this.container.style.opacity = targetOpacity;
             if (elapsedTime > duration) {
                 this.container.style.display = show ? 'flex' : 'none';
+                if (show) {
+                    this.focusOnBody();
+                }
                 this.container.style.opacity = '';
             } else {
                 requestAnimationFrame(setOpacity.bind(this));
@@ -31,8 +44,12 @@ class Overlay {
         requestAnimationFrame(setOpacity.bind(this));
     }
 
-    hide() { this.toggle(false); }
-    show() { this.toggle(true); }
+    focusOnBody() {
+        const body = this.container.querySelector('.popup-body');
+        if (body) {
+            body.focus();
+        }
+    }
 
 }
 
index 797effd986431cfca0b52075ef6784ced7362539..966a4540e84eeb91117cec8ea89c41609d8b28e6 100644 (file)
@@ -22,6 +22,22 @@ export function onEvents(listenerElement, events, callback) {
     }
 }
 
+/**
+ * Helper to run an action when an element is selected.
+ * A "select" is made to be accessible, So can be a click, space-press or enter-press.
+ * @param listenerElement
+ * @param callback
+ */
+export function onSelect(listenerElement, callback) {
+    listenerElement.addEventListener('click', callback);
+    listenerElement.addEventListener('keydown', (event) => {
+        if (event.key === 'Enter' || event.key === ' ') {
+            event.preventDefault();
+            callback(event);
+        }
+    });
+}
+
 /**
  * Set a listener on an element for an event emitted by a child
  * matching the given childSelector param.
index d6f9965a879f0547497d3f046b8056ba5c83ab06..c6df6b1a5de43aafc902fe45fc4a454f2a3ee2dd 100644 (file)
@@ -3,10 +3,10 @@ import codeLib from "../services/code";
 const methods = {
     show() {
         if (!this.editor) this.editor = codeLib.popupEditor(this.$refs.editor, this.language);
-        this.$refs.overlay.style.display = 'flex';
+        this.$refs.overlay.components.overlay.show();
     },
     hide() {
-        this.$refs.overlay.style.display = 'none';
+        this.$refs.overlay.components.overlay.hide();
     },
     updateEditorMode(language) {
         codeLib.setMode(this.editor, language);
index eb7a09342ad60079503534da6294393af6a75fbc..024b9cd7eec8fe27dfab790554b08445dbd376d3 100644 (file)
@@ -1,4 +1,6 @@
 button {
+  background-color: transparent;
+  border: 0;
   font-size: 100%;
 }
 
@@ -47,7 +49,12 @@ $button-border-radius: 2px;
   &:hover, &:focus {
     text-decoration: none;
   }
+  &:focus {
+    outline: 1px dotted currentColor;
+    outline-offset: -$-xs;
+  }
   &:active {
+    outline: 0;
     background-color: darken($primary, 8%);
   }
 }
@@ -83,7 +90,7 @@ $button-border-radius: 2px;
   user-select: none;
   font-size: 0.75rem;
   line-height: 1.4em;
-  &:focus, &:active {
+    &:active {
     outline: 0;
   }
   &:hover {
index 0b683c6e315f3f9f6bdd70e0b8313621a353fc24..a61c235eb15a69bf85207e93d7f0db7b61bd05d9 100644 (file)
   .popup-content {
     overflow-y: auto;
   }
+  &:focus {
+    outline: 0;
+  }
 }
 
 .popup-footer button, .popup-header-close {
@@ -620,7 +623,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
     opacity: 0;
     transition: opacity ease-in-out 120ms;
   }
-  &:hover .actions {
+  &:hover .actions, &:focus-within .actions {
     opacity: 1;
   }
 }
@@ -637,7 +640,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
     }
     a { color: #666; }
     span {
-      color: #888;
       padding-left: $-xxs;
     }
   }
index 5fd19bb1f3b14613803b8ecaea6088ac22a23701..3c81d6d9fc64b6363bb702c08dd2f7f490aa9b73 100644 (file)
@@ -19,9 +19,6 @@
   &.disabled, &[disabled] {
     background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAMUlEQVQIW2NkwAGuXbv2nxGbHEhCS0uLEUMSJgHShCKJLIEiiS4Bl8QmAZbEJQGSBAC62BuJ+tt7zgAAAABJRU5ErkJggg==);
   }
-  &:focus {
-    outline: 0;
-  }
 }
 
 .fake-input {
index adb014f4a956b135255982d0ea71830138ab3ece..3cf55f1de5d2be869a9e07ed84047f603174621f 100644 (file)
@@ -47,9 +47,7 @@ header {
   }
   .user-name {
     vertical-align: top;
-    padding-top: $-m;
     position: relative;
-    top: -3px;
     display: inline-block;
     cursor: pointer;
     > * {
@@ -73,6 +71,9 @@ header {
   }
 }
 
+.header *, .primary-background * {
+  outline-color: #FFF;
+}
 
 
 .header-search {
@@ -88,6 +89,10 @@ header .search-box {
     color: #EEE;
     z-index: 2;
     padding-left: 40px;
+    &:focus {
+      outline: none;
+      border: 1px solid rgba(255, 255, 255, 0.6);
+    }
   }
   button {
     fill: #EEE;
@@ -103,12 +108,6 @@ header .search-box {
   ::-moz-placeholder { /* Firefox 19+ */
     color: #DDD;
   }
-  :-ms-input-placeholder { /* IE 10+ */
-    color: #DDD;
-  }
-  :-moz-placeholder { /* Firefox 18- */
-    color: #DDD;
-  }
   @include between($l, $xl) {
     max-width: 200px;
   }
@@ -243,7 +242,7 @@ header .search-box {
     line-height: 0.8;
     margin: -2px 0 0;
   }
-  &:hover {
+  &:hover, &:focus-within {
     opacity: 1;
   }
 }
index 7c3a3c49b41179aaf673b211ed834a399ca8a54e..de48c8ed1bbe8eac685067c399c583faafe0abdc 100644 (file)
@@ -1,5 +1,10 @@
 * {
   box-sizing: border-box;
+  outline-color: #444444;
+}
+
+*:focus {
+  outline-style: dotted;
 }
 
 html {
index b282b12e272c45455b4123527236d24fe27bacd2..381dc3ff3175f68225dcc65597b66df729379bf7 100644 (file)
@@ -307,7 +307,11 @@ body.flexbox {
     &:hover {
       opacity: 1;
     }
+    &:focus-within {
+      opacity: 1;
+    }
   }
+
 }
 
 @include smaller-than($m) {
index b9b4d092a1d7528c1b36a0c3af91ce244d771637..74c36c86c432edfd3cd05c8839aefdb150a82297 100644 (file)
@@ -414,6 +414,11 @@ ul.pagination {
     background-color: transparent;
     border-color: rgba(0, 0, 0, 0.1);
   }
+  &:focus {
+    background-color: #eee;
+    outline: 1px dotted #666;
+    outline-offset: -2px;
+  }
 }
 
 .entity-list-item-path-sep {
@@ -551,10 +556,14 @@ ul.pagination {
     color: #555;
     fill: #555;
     white-space: nowrap;
-    &:hover {
+    &:hover, &:focus {
       text-decoration: none;
       background-color: #EEE;
     }
+    &:focus {
+      outline: 1px solid rgba(0, 0, 0, 0.2);
+      outline-offset: -2px;
+    }
     svg {
       margin-right: $-s;
       display: inline-block;
index ed880afcf9ab24d0bdd0f30dcf6ae8f5b3de7394..e81cf6a52031dcdcb648112605c64f2454ab52f8 100644 (file)
@@ -40,6 +40,10 @@ return [
     'add' => 'Add',
 
     // Sort Options
+    'sort_options' => 'Sort Options',
+    'sort_direction_toggle' => 'Sort Direction Toggle',
+    'sort_ascending' => 'Sort Ascending',
+    'sort_descending' => 'Sort Descending',
     'sort_name' => 'Name',
     'sort_created_at' => 'Created Date',
     'sort_updated_at' => 'Updated Date',
@@ -57,6 +61,7 @@ return [
     'default' => 'Default',
 
     // Header
+    'profile_menu' => 'Profile Menu',
     'view_profile' => 'View Profile',
     'edit_profile' => 'Edit Profile',
 
index 3208a6dfcc212f339b47e9a573933cd3c7233137..b4fdbf0e550fa736b0972626a9c93d89dfaf80e5 100644 (file)
@@ -176,6 +176,7 @@ return [
     'pages_delete_confirm' => 'Are you sure you want to delete this page?',
     'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?',
     'pages_editing_named' => 'Editing Page :pageName',
+    'pages_edit_draft_options' => 'Draft Options',
     'pages_edit_save_draft' => 'Save Draft',
     'pages_edit_draft' => 'Edit Page Draft',
     'pages_editing_draft' => 'Editing Draft',
index b709b29dcd7ac6ac2d1e0512ec62f0d69845c745..528eb5496d7f041d9c49ccedae665aac1fb037d9 100644 (file)
 
             <hr class="primary-background">
 
-            <div dropdown class="dropdown-container">
-                <div dropdown-toggle class="icon-list-item">
-                    <span>@icon('export')</span>
-                    <span>{{ trans('entities.export') }}</span>
-                </div>
-                <ul class="wide dropdown-menu">
-                    <li><a href="{{ $book->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
-                    <li><a href="{{ $book->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
-                    <li><a href="{{ $book->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
-                </ul>
-            </div>
+            @include('partials.entity-export-menu', ['entity' => $book])
         </div>
     </div>
 
index 36c7f9a243cbc0717a0a5da7637f650e578340f1..95182534648d5e5e7563703b5d1c4c39d233c06b 100644 (file)
@@ -1,10 +1,11 @@
 <div class="chapter-child-menu">
-    <p chapter-toggle class="text-muted @if($bookChild->matchesOrContains($current)) open @endif">
+    <button chapter-toggle type="button" aria-expanded="{{ $isOpen ? 'true' : 'false' }}" aria-label="{{ trans('common.profile_menu') }}"
+            class="text-muted @if($isOpen) open @endif">
         @icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->pages->count()) }}</span>
-    </p>
-    <ul class="sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">
+    </button>
+    <ul class="sub-menu inset-list @if($isOpen) open @endif" @if($isOpen) style="display: block;" @endif role="menu">
         @foreach($bookChild->pages as $childPage)
-            <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}">
+            <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}" role="presentation">
                 @include('partials.entity-list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])
             </li>
         @endforeach
index 0915f9a16120afd97a9a9af1e4f0f0ed278fd36d..eea2e90cc969c352a6b1b7e90749a9bfcb288e35 100644 (file)
 
             <hr class="primary-background"/>
 
-            <div dropdown class="dropdown-container">
-                <div dropdown-toggle class="icon-list-item">
-                    <span>@icon('export')</span>
-                    <span>{{ trans('entities.export') }}</span>
-                </div>
-                <ul class="wide dropdown-menu">
-                    <li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
-                    <li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
-                    <li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
-                </ul>
-            </div>
+            @include('partials.entity-export-menu', ['entity' => $chapter])
         </div>
     </div>
 @stop
index a855031784dc2c181b36691467564064386078c9..9d7f230dc60df03dbc8c2c1c899a981d05de0667 100644 (file)
@@ -1,8 +1,8 @@
 <div class="comment-box mb-m" comment="{{ $comment->id }}" local-id="{{$comment->local_id}}" parent-id="{{$comment->parent_id}}" id="comment{{$comment->local_id}}">
     <div class="header p-s">
-        <div class="grid half no-gap v-center">
-            <div class="meta">
-                <a href="#comment{{$comment->local_id}}" class="text-muted">#{{$comment->local_id}}</a>
+        <div class="grid half left-focus no-gap v-center">
+            <div class="meta text-muted text-small">
+                <a href="#comment{{$comment->local_id}}">#{{$comment->local_id}}</a>
                 &nbsp;&nbsp;
                 @if ($comment->createdBy)
                     <img width="50" src="{{ $comment->createdBy->getAvatar(50) }}" class="avatar" alt="{{ $comment->createdBy->name }}">
             </div>
             <div class="actions text-right">
                 @if(userCan('comment-update', $comment))
-                    <button type="button" class="text-button" action="edit" title="{{ trans('common.edit') }}">@icon('edit')</button>
+                    <button type="button" class="text-button" action="edit" aria-label="{{ trans('common.edit') }}" title="{{ trans('common.edit') }}">@icon('edit')</button>
                 @endif
                 @if(userCan('comment-create-all'))
-                    <button type="button" class="text-button" action="reply" title="{{ trans('common.reply') }}">@icon('reply')</button>
+                    <button type="button" class="text-button" action="reply" aria-label="{{ trans('common.reply') }}" title="{{ trans('common.reply') }}">@icon('reply')</button>
                 @endif
                 @if(userCan('comment-delete', $comment))
                     <div dropdown class="dropdown-container">
-                        <button type="button" dropdown-toggle class="text-button" title="{{ trans('common.delete') }}">@icon('delete')</button>
-                        <ul class="dropdown-menu">
+                        <button type="button" dropdown-toggle aria-haspopup="true" aria-expanded="false" class="text-button" title="{{ trans('common.delete') }}">@icon('delete')</button>
+                        <ul class="dropdown-menu" role="menu">
                             <li class="px-m text-small text-muted pb-s">{{trans('entities.comment_delete_confirm')}}</li>
-                            <li><a action="delete" class="text-button text-neg" >@icon('delete'){{ trans('common.delete') }}</a></li>
+                            <li><a action="delete" href="#" class="text-button text-neg" >@icon('delete'){{ trans('common.delete') }}</a></li>
                         </ul>
                     </div>
                 @endif
index 95fcde65918d0f6d51dc545e0ab169277f416586..f9e12014ec436f0b281df70f2060a3609255ef89 100644 (file)
@@ -16,8 +16,8 @@
         <div class="header-search hide-under-l">
             @if (hasAppAccess())
             <form action="{{ url('/search') }}" method="GET" class="search-box">
-                <button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}">@icon('search') </button>
-                <input id="header-search-box-input" type="text" name="term" tabindex="2"
+                <button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button>
+                <input id="header-search-box-input" type="text" name="term"
                        aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}"
                        value="{{ isset($searchTerm) ? $searchTerm : '' }}">
             </form>
                 @if(signedInUser())
                     <?php $currentUser = user(); ?>
                     <div class="dropdown-container" dropdown>
-                        <span class="user-name hide-under-l" dropdown-toggle>
+                        <span class="user-name py-s hide-under-l" dropdown-toggle
+                              aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
                             <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
                             <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
                         </span>
-                        <ul class="dropdown-menu">
+                        <ul class="dropdown-menu" role="menu">
                             <li>
                                 <a href="{{ url("/user/{$currentUser->id}") }}">@icon('user'){{ trans('common.view_profile') }}</a>
                             </li>
index 7636cd581b63c8c8923ed96ea3101039d54332b0..656a77876a2875949ca5eab2b9235edac1f6459c 100644 (file)
@@ -1,6 +1,6 @@
 <div id="code-editor">
     <div overlay ref="overlay" v-cloak @click="hide()">
-        <div class="popup-body" @click.stop>
+        <div class="popup-body" tabindex="-1" @click.stop>
 
             <div class="popup-header primary-background">
                 <div class="popup-title">{{ trans('components.code_editor') }}</div>
index c497a16d594563db25c1eb6e0e902857d7814c57..6fb3334e9d62e3ed528d368f1987967d8182b161 100644 (file)
@@ -1,6 +1,6 @@
 <div id="entity-selector-wrap">
     <div overlay entity-selector-popup>
-        <div class="popup-body small">
+        <div class="popup-body small" tabindex="-1">
             <div class="popup-header primary-background">
                 <div class="popup-title">{{ trans('entities.entity_select') }}</div>
                 <button type="button" class="popup-header-close">x</button>
index 28af63caf3969fb2087729bdd8e1112305f5b4c1..a24f9ac1e9ec79c7d1c55ed95250f177bf9f42ee 100644 (file)
@@ -3,13 +3,13 @@ $target - CSS selector of items to expand
 $key - Unique key for checking existing stored state.
 --}}
 <?php $isOpen = setting()->getForCurrentUser('section_expansion#'. $key); ?>
-<a expand-toggle="{{ $target }}"
+<button type="button" expand-toggle="{{ $target }}"
    expand-toggle-update-endpoint="{{ url('/settings/users/'. $currentUser->id .'/update-expansion-preference/' . $key) }}"
    expand-toggle-is-open="{{ $isOpen ? 'yes' : 'no' }}"
    class="text-muted icon-list-item text-primary">
     <span>@icon('expand-text')</span>
     <span>{{ trans('common.toggle_details') }}</span>
-</a>
+</button>
 @if($isOpen)
     @push('head')
         <style>
index 6781bca5fbbdd462b359336e934adbc3330c08a1..035a6b66fe0c2735f1cf95171878ac9fc122ad52 100644 (file)
@@ -9,7 +9,7 @@
     ])
 
     <div overlay v-cloak @click="hide">
-        <div class="popup-body" @click.stop="">
+        <div class="popup-body" tabindex="-1" @click.stop>
 
             <div class="popup-header primary-background">
                 <div class="popup-title">{{ trans('components.image_select') }}</div>
index 380718dd7f6a53e87a5d4636e75d54fd3d0ccd5d..0d5411e6ccab2e4b245957df904be96fd24ae136 100644 (file)
@@ -26,9 +26,9 @@
 
             <div class="text-center px-m py-xs">
                 <div v-show="draftsEnabled" dropdown dropdown-move-menu class="dropdown-container draft-display text">
-                    <a dropdown-toggle  class="text-primary text-button"><span class="faded-text" v-text="draftText"></span>&nbsp; @icon('more')</a>
+                    <a dropdown-toggle aria-haspopup="true" aria-expanded="false" title="{{ trans('entities.pages_edit_draft_options') }}" class="text-primary text-button"><span class="faded-text" v-text="draftText"></span>&nbsp; @icon('more')</a>
                     @icon('check-circle', ['class' => 'text-pos draft-notification svg-icon', ':class' => '{visible: draftUpdated}'])
-                    <ul class="dropdown-menu">
+                    <ul class="dropdown-menu" role="menu">
                         <li>
                             <a @click="saveDraft()" class="text-pos">@icon('save'){{ trans('entities.pages_edit_save_draft') }}</a>
                         </li>
index f3fb048bc85ded30fe5fbd05ce2e3df452f8495e..d1491bdec0dcd4ec2ba0cc39a09814fef2ee871f 100644 (file)
                                 @else
                                     <a href="{{ $revision->getUrl() }}" target="_blank">{{ trans('entities.pages_revisions_preview') }}</a>
                                     <span class="text-muted">&nbsp;|&nbsp;</span>
-                                    <a href="{{ $revision->getUrl('restore') }}"></a>
                                     <div dropdown class="dropdown-container">
-                                        <a dropdown-toggle>{{ trans('entities.pages_revisions_restore') }}</a>
-                                        <ul class="dropdown-menu">
+                                        <a dropdown-toggle href="#" aria-haspopup="true" aria-expanded="false">{{ trans('entities.pages_revisions_restore') }}</a>
+                                        <ul class="dropdown-menu" role="menu">
                                             <li class="px-m py-s"><small class="text-muted">{{trans('entities.revision_restore_confirm')}}</small></li>
                                             <li>
                                                 <form action="{{ $revision->getUrl('/restore') }}" method="POST">
@@ -66,8 +65,8 @@
                                     </div>
                                     <span class="text-muted">&nbsp;|&nbsp;</span>
                                     <div dropdown class="dropdown-container">
-                                        <a dropdown-toggle>{{ trans('common.delete') }}</a>
-                                        <ul class="dropdown-menu">
+                                        <a dropdown-toggle href="#" aria-haspopup="true" aria-expanded="false">{{ trans('common.delete') }}</a>
+                                        <ul class="dropdown-menu" role="menu">
                                             <li class="px-m py-s"><small class="text-muted">{{trans('entities.revision_delete_confirm')}}</small></li>
                                             <li>
                                                 <form action="{{ $revision->getUrl('/delete/') }}" method="POST">
index 86b0d3f88d39020f7f5f03c34e8b8473f66c309d..3b688161cad8ec7e97461747584eab18772f45dc 100644 (file)
             <hr class="primary-background"/>
 
             {{--Export--}}
-            <div dropdown class="dropdown-container block">
-                <div dropdown-toggle class="icon-list-item">
-                    <span>@icon('export')</span>
-                    <span>{{ trans('entities.export') }}</span>
-                </div>
-                <ul class="dropdown-menu wide">
-                    <li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
-                    <li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
-                    <li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
-                </ul>
-            </div>
+            @include('partials.entity-export-menu', ['entity' => $page])
         </div>
 
     </div>
index 73064dceb46b72b6d9f9f38b23c9aeb5e75d55af..1a1a945487a85e3913801fdebc3175e15a2e5df9 100644 (file)
 
                 @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
                     <div class="entity-list-item no-hover">
-                        <span class="icon text-chapter">
-
-                        </span>
+                        <span role="presentation" class="icon text-chapter"></span>
                         <div class="content">
-                            @include('chapters.child-menu', ['chapter' => $bookChild, 'current' => $current])
+                            @include('chapters.child-menu', [
+                                'chapter' => $bookChild,
+                                'current' => $current,
+                                'isOpen'  => $bookChild->matchesOrContains($current)
+                            ])
                         </div>
                     </div>
 
index 3dea3202355b6d5953cc7d48bb51fc71269555d4..e53cb4c53b9cf59200427f67d6134d90ce1c5bbd 100644 (file)
@@ -1,11 +1,12 @@
 <div class="breadcrumb-listing" dropdown breadcrumb-listing="{{ $entity->getType() }}:{{ $entity->id }}">
-    <div class="breadcrumb-listing-toggle" dropdown-toggle>
+    <div class="breadcrumb-listing-toggle" dropdown-toggle
+         aria-haspopup="true" aria-expanded="false" tabindex="0">
         <div class="separator">@icon('chevron-right')</div>
     </div>
-    <div dropdown-menu class="breadcrumb-listing-dropdown card">
+    <div dropdown-menu class="breadcrumb-listing-dropdown card" role="menu">
         <div class="breadcrumb-listing-search">
             @icon('search')
-            <input autocomplete="off" type="text" name="entity-search">
+            <input autocomplete="off" type="text" name="entity-search" placeholder="{{ trans('common.search') }}" aria-label="{{ trans('common.search') }}">
         </div>
         @include('partials.loading-icon')
         <div class="breadcrumb-listing-entity-list px-m"></div>
index 99d37c5f874bc390de36fbb595e5c9d750379b67..f46c9a8f1327f6ef0487bf8f344318e900099c14 100644 (file)
@@ -1,7 +1,8 @@
 <div class="mb-xl">
     <form v-on:submit.prevent="searchBook" class="search-box flexible">
-        <input v-model="searchTerm" v-on:change="checkSearchForm" type="text" name="term" placeholder="{{ trans('entities.books_search_this') }}">
-        <button type="submit">@icon('search')</button>
-        <button v-if="searching" v-cloak class="search-box-cancel text-neg" v-on:click="clearSearch" type="button">@icon('close')</button>
+        <input v-model="searchTerm" v-on:change="checkSearchForm" type="text" aria-label="{{ trans('entities.books_search_this') }}" name="term" placeholder="{{ trans('entities.books_search_this') }}">
+        <button type="submit" aria-label="{{ trans('common.search') }}">@icon('search')</button>
+        <button v-if="searching" v-cloak class="search-box-cancel text-neg" v-on:click="clearSearch"
+                type="button" aria-label="{{ trans('common.search_clear') }}">@icon('close')</button>
     </form>
 </div>
\ No newline at end of file
diff --git a/resources/views/partials/entity-export-menu.blade.php b/resources/views/partials/entity-export-menu.blade.php
new file mode 100644 (file)
index 0000000..6cf41e9
--- /dev/null
@@ -0,0 +1,12 @@
+<div dropdown class="dropdown-container" >
+    <div dropdown-toggle class="icon-list-item"
+         aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('entities.export') }}" tabindex="0">
+        <span>@icon('export')</span>
+        <span>{{ trans('entities.export') }}</span>
+    </div>
+    <ul class="wide dropdown-menu" role="menu">
+        <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
+        <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
+        <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
+    </ul>
+</div>
\ No newline at end of file
index c4942c71f2dd38af6bb2de09290a710bd08b7912..2ec4bee5cc07dbf13db6b0978effd5990551cdc4 100644 (file)
@@ -1,6 +1,6 @@
 <?php $type = $entity->getType(); ?>
 <a href="{{ $entity->getUrl() }}" class="{{$type}} {{$type === 'page' && $entity->draft ? 'draft' : ''}} {{$classes ?? ''}} entity-list-item" data-entity-type="{{$type}}" data-entity-id="{{$entity->id}}">
-    <span class="icon text-{{$type}}">@icon($type)</span>
+    <span role="presentation" class="icon text-{{$type}}">@icon($type)</span>
     <div class="content">
             <h4 class="entity-list-item-name break-text">{{ $entity->name }}</h4>
             {{ $slot ?? '' }}
index 38145df219f4c9d7c70d7a2a5d47f4273b12c287..09c61d01383fa01f2d5e6f0c1af54b586dff75c7 100644 (file)
 
         <div class="list-sort">
             <div class="list-sort-type dropdown-container" dropdown>
-                <div dropdown-toggle>{{ $options[$selectedSort] }}</div>
+                <div dropdown-toggle aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" tabindex="0">{{ $options[$selectedSort] }}</div>
                 <ul class="dropdown-menu">
                     @foreach($options as $key => $label)
                         <li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}">{{ $label }}</a></li>
                     @endforeach
                 </ul>
             </div>
-            <div class="list-sort-dir" data-sort-dir>
+            <button href="#" class="list-sort-dir" type="button" data-sort-dir
+                    aria-label="{{ trans('common.sort_direction_toggle') }} - {{ $order === 'asc' ? trans('common.sort_ascending') : trans('common.sort_descending') }}" tabindex="0">
                 @icon($order === 'desc' ? 'sort-up' : 'sort-down')
-            </div>
+            </button>
         </div>
     </form>
 </div>
\ No newline at end of file
Morty Proxy This is a proxified and sanitized view of the page, visit original site.