From 7dc22774532872fc99aa7768cf299ab623e9d155 Mon Sep 17 00:00:00 2001
From: tusooa <tusooa@kazv.moe>
Date: Mon, 9 Jan 2023 13:02:16 -0500
Subject: Use stylelint

---
 src/components/emoji_input/emoji_input.vue | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index c9bbc18f..ccba0393 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -91,22 +91,18 @@
 <script src="./emoji_input.js"></script>
 
 <style lang="scss">
-@import '../../_variables.scss';
+@import "../../variables";
 
 .emoji-input {
   display: flex;
   flex-direction: column;
   position: relative;
 
-  &.with-picker input {
-    padding-right: 30px;
-  }
-
   .emoji-picker-icon {
     position: absolute;
     top: 0;
     right: 0;
-    margin: .2em .25em;
+    margin: 0.2em 0.25em;
     font-size: 1.3em;
     cursor: pointer;
     line-height: 24px;
@@ -123,14 +119,19 @@
     margin-top: 2px;
 
     &.hide {
-      display: none
+      display: none;
     }
   }
 
-  input, textarea {
+  input,
+  textarea {
     flex: 1 0 auto;
   }
 
+  &.with-picker input {
+    padding-right: 30px;
+  }
+
   .hidden-overlay {
     opacity: 0;
     pointer-events: none;
@@ -140,8 +141,10 @@
     right: 0;
     left: 0;
     overflow: hidden;
+
     /* DEBUG STUFF */
     color: red;
+
     /* set opacity to non-zero to see the overlay */
 
     .caret {
@@ -151,6 +154,7 @@
     }
   }
 }
+
 .autocomplete {
   &-panel {
     position: absolute;
@@ -160,7 +164,7 @@
     display: flex;
     cursor: pointer;
     padding: 0.2em 0.4em;
-    border-bottom: 1px solid rgba(0, 0, 0, 0.4);
+    border-bottom: 1px solid rgb(0 0 0 / 40%);
     height: 32px;
 
     .image {
@@ -169,7 +173,6 @@
       line-height: 32px;
       text-align: center;
       font-size: 32px;
-
       margin-right: 4px;
 
       img {
@@ -199,6 +202,7 @@
       background-color: $fallback--fg;
       background-color: var(--selectedMenuPopover, $fallback--fg);
       color: var(--selectedMenuPopoverText, $fallback--text);
+
       --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
       --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
       --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
-- 
cgit v1.2.3-70-g09d2


From 4db7f07421a6dc49f07dee9140a024ef5195a934 Mon Sep 17 00:00:00 2001
From: tusooa <tusooa@kazv.moe>
Date: Thu, 24 Nov 2022 23:26:31 -0500
Subject: Make autocomplete items buttons

---
 src/components/emoji_input/emoji_input.vue | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index ccba0393..6df2cebe 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -53,6 +53,7 @@
             v-for="(suggestion, index) in suggestions"
             :key="index"
             class="autocomplete-item"
+            role="button"
             :class="{ highlighted: index === highlighted }"
             @click.stop.prevent="onClick($event, suggestion)"
           >
-- 
cgit v1.2.3-70-g09d2


From 6235af4592c52a657415ffae772bd83ec106bc13 Mon Sep 17 00:00:00 2001
From: tusooa <tusooa@kazv.moe>
Date: Sat, 21 Jan 2023 01:07:07 -0500
Subject: Make screenreaders read out autocomplete results

---
 src/components/emoji_input/emoji_input.js          | 53 ++++++++++++++--------
 src/components/emoji_input/emoji_input.vue         | 19 +++++++-
 .../post_status_form/post_status_form.vue          | 53 ++++++++++++----------
 .../screen_reader_notice/screen_reader_notice.js   | 21 +++++++++
 .../screen_reader_notice/screen_reader_notice.vue  | 21 +++++++++
 src/i18n/en.json                                   |  3 +-
 6 files changed, 126 insertions(+), 44 deletions(-)
 create mode 100644 src/components/screen_reader_notice/screen_reader_notice.js
 create mode 100644 src/components/screen_reader_notice/screen_reader_notice.vue

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index ba5f7552..020e9fde 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -1,6 +1,7 @@
 import Completion from '../../services/completion/completion.js'
 import EmojiPicker from '../emoji_picker/emoji_picker.vue'
 import Popover from 'src/components/popover/popover.vue'
+import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue'
 import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
 import { take } from 'lodash'
 import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@@ -109,9 +110,10 @@ const EmojiInput = {
   },
   data () {
     return {
+      randomSeed: `${Math.random()}`.replace('.', '-'),
       input: undefined,
       caretEl: undefined,
-      highlighted: 0,
+      highlighted: -1,
       caret: 0,
       focused: false,
       blurTimeout: null,
@@ -125,7 +127,8 @@ const EmojiInput = {
   components: {
     Popover,
     EmojiPicker,
-    UnicodeDomainIndicator
+    UnicodeDomainIndicator,
+    ScreenReaderNotice
   },
   computed: {
     padEmoji () {
@@ -203,6 +206,12 @@ const EmojiInput = {
         top: this.input.scrollTop,
         left: this.input.scrollLeft
       })
+    },
+    suggestionListId () {
+      return `suggestions-${this.randomSeed}`
+    },
+    suggestionItemId () {
+      return (index) => `suggestion-item-${index}-${this.randomSeed}`
     }
   },
   mounted () {
@@ -278,6 +287,10 @@ const EmojiInput = {
           ...rest,
           img: imageUrl || ''
         }))
+      this.$refs.screenReaderNotice.announce(
+        this.$tc('tool_tip.autocomplete_available',
+          this.suggestions.length,
+          { number: this.suggestions.length }))
     }
   },
   methods: {
@@ -374,27 +387,24 @@ const EmojiInput = {
     },
     cycleBackward (e) {
       const len = this.suggestions.length || 0
-      if (len > 1) {
-        this.highlighted -= 1
-        if (this.highlighted < 0) {
-          this.highlighted = this.suggestions.length - 1
-        }
-        e.preventDefault()
-      } else {
-        this.highlighted = 0
+
+      this.highlighted -= 1
+      if (this.highlighted === -1) {
+        this.input.focus()
+      } else if (this.highlighted < -1) {
+        this.highlighted = len - 1
       }
+      e.preventDefault()
     },
     cycleForward (e) {
       const len = this.suggestions.length || 0
-      if (len > 1) {
-        this.highlighted += 1
-        if (this.highlighted >= len) {
-          this.highlighted = 0
-        }
-        e.preventDefault()
-      } else {
-        this.highlighted = 0
+
+      this.highlighted += 1
+      if (this.highlighted >= len) {
+        this.highlighted = -1
+        this.input.focus()
       }
+      e.preventDefault()
     },
     scrollIntoView () {
       const rootRef = this.$refs.picker.$el
@@ -540,6 +550,13 @@ const EmojiInput = {
       })
     },
     resize () {
+    },
+    autoCompleteItemLabel (suggestion) {
+      if (suggestion.user) {
+        return suggestion.displayText + ' ' + suggestion.detailText
+      } else {
+        return this.maybeLocalizedEmojiName(suggestion)
+      }
     }
   }
 }
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 6df2cebe..4a7b0fa8 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -4,7 +4,13 @@
     class="emoji-input"
     :class="{ 'with-picker': !hideEmojiButton }"
   >
-    <slot />
+    <slot
+      :id="'textbox-' + randomSeed"
+      :aria-owns="suggestionListId"
+      aria-autocomplete="both"
+      :aria-expanded="showSuggestions"
+      :aria-activedescendant="(!showSuggestions || highlighted === -1) ? '' : suggestionItemId(highlighted)"
+    />
     <!-- TODO: make the 'x' disappear if at the end maybe? -->
     <div
       ref="hiddenOverlay"
@@ -18,6 +24,10 @@
       >x</span>
       <span>{{ postText }}</span>
     </div>
+    <screen-reader-notice
+      ref="screenReaderNotice"
+      aria-live="assertive"
+    />
     <template v-if="enableEmojiPicker">
       <button
         v-if="!hideEmojiButton"
@@ -46,15 +56,20 @@
     >
       <template #content>
         <div
+          :id="suggestionListId"
           ref="panel-body"
           class="autocomplete-panel-body"
+          role="listbox"
         >
           <div
             v-for="(suggestion, index) in suggestions"
+            :id="suggestionItemId(index)"
             :key="index"
             class="autocomplete-item"
-            role="button"
+            role="option"
             :class="{ highlighted: index === highlighted }"
+            :aria-label="autoCompleteItemLabel(suggestion)"
+            :aria-selected="index === highlighted"
             @click.stop.prevent="onClick($event, suggestion)"
           >
             <span class="image">
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index c51639db..26a5acc4 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -148,29 +148,36 @@
           @sticker-upload-failed="uploadFailed"
           @shown="handleEmojiInputShow"
         >
-          <textarea
-            ref="textarea"
-            v-model="newStatus.status"
-            :placeholder="placeholder || $t('post_status.default')"
-            rows="1"
-            cols="1"
-            :disabled="posting && !optimisticPosting"
-            class="form-post-body"
-            :class="{ 'scrollable-form': !!maxHeight }"
-            @keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
-            @keydown.meta.enter="postStatus($event, newStatus)"
-            @keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
-            @input="resize"
-            @compositionupdate="resize"
-            @paste="paste"
-          />
-          <p
-            v-if="hasStatusLengthLimit"
-            class="character-counter faint"
-            :class="{ error: isOverLengthLimit }"
-          >
-            {{ charactersLeft }}
-          </p>
+          <template #default="inputProps">
+            <textarea
+              ref="textarea"
+              v-model="newStatus.status"
+              :placeholder="placeholder || $t('post_status.default')"
+              rows="1"
+              cols="1"
+              :disabled="posting && !optimisticPosting"
+              class="form-post-body"
+              :class="{ 'scrollable-form': !!maxHeight }"
+              v-bind="inputProps"
+              :aria-owns="inputProps.ariaOwns"
+              :aria-autocomplete="inputProps.ariaAutocomplete"
+              :aria-activedescendant="inputProps.ariaActiveDescendant"
+              :aria-expanded="inputProps.ariaExpanded"
+              @keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
+              @keydown.meta.enter="postStatus($event, newStatus)"
+              @keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
+              @input="resize"
+              @compositionupdate="resize"
+              @paste="paste"
+            />
+            <p
+              v-if="hasStatusLengthLimit"
+              class="character-counter faint"
+              :class="{ error: isOverLengthLimit }"
+            >
+              {{ charactersLeft }}
+            </p>
+          </template>
         </EmojiInput>
         <div
           v-if="!disableScopeSelector"
diff --git a/src/components/screen_reader_notice/screen_reader_notice.js b/src/components/screen_reader_notice/screen_reader_notice.js
new file mode 100644
index 00000000..3b8eaf37
--- /dev/null
+++ b/src/components/screen_reader_notice/screen_reader_notice.js
@@ -0,0 +1,21 @@
+const ScreenReaderNotice = {
+  props: {
+    ariaLive: {
+      type: String,
+      defualt: 'assertive'
+    }
+  },
+  data () {
+    return {
+      currentText: ''
+    }
+  },
+  methods: {
+    announce (text) {
+      this.currentText = text
+      setTimeout(() => { this.currentText = '' }, 1000)
+    }
+  }
+}
+
+export default ScreenReaderNotice
diff --git a/src/components/screen_reader_notice/screen_reader_notice.vue b/src/components/screen_reader_notice/screen_reader_notice.vue
new file mode 100644
index 00000000..5098b94f
--- /dev/null
+++ b/src/components/screen_reader_notice/screen_reader_notice.vue
@@ -0,0 +1,21 @@
+<template>
+  <div
+    class="screen-reader-text"
+    :aria-live="ariaLive"
+  >
+    {{ currentText }}
+  </div>
+</template>
+
+<script src="./screen_reader_notice.js"></script>
+
+<style lang="scss">
+.screen-reader-text {
+  display: block;
+  width: 1px;
+  height: 1px;
+  margin: -1px;
+  overflow: hidden;
+  visibility: visible;
+}
+</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 1ee1147a..6166a510 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -996,7 +996,8 @@
     "reject_follow_request": "Reject follow request",
     "bookmark": "Bookmark",
     "toggle_expand": "Expand or collapse notification to show post in full",
-    "toggle_mute": "Expand or collapse notification to reveal muted content"
+    "toggle_mute": "Expand or collapse notification to reveal muted content",
+    "autocomplete_available": "{number} result is available | {number} results are available"
   },
   "upload": {
     "error": {
-- 
cgit v1.2.3-70-g09d2


From d1590311211cbf1036c870eef8c7e9e80fab7a3e Mon Sep 17 00:00:00 2001
From: tusooa <tusooa@kazv.moe>
Date: Sat, 21 Jan 2023 16:28:33 -0500
Subject: Add some aria to post status form

---
 src/components/emoji_input/emoji_input.vue           |  3 +++
 src/components/emoji_picker/emoji_picker.vue         |  1 +
 src/components/post_status_form/post_status_form.vue | 10 ++++++++++
 src/components/select/select.js                      |  3 ++-
 src/components/select/select.vue                     |  1 +
 src/i18n/en.json                                     |  2 ++
 6 files changed, 19 insertions(+), 1 deletion(-)

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 4a7b0fa8..7f9ecc99 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -16,6 +16,7 @@
       ref="hiddenOverlay"
       class="hidden-overlay"
       :style="overlayStyle"
+      :aria-hidden="true"
     >
       <span>{{ preText }}</span>
       <span
@@ -33,6 +34,7 @@
         v-if="!hideEmojiButton"
         class="button-unstyled emoji-picker-icon"
         type="button"
+        :title="$t('emoji.add_emoji')"
         @click.prevent="togglePicker"
       >
         <FAIcon :icon="['far', 'smile-beam']" />
@@ -53,6 +55,7 @@
       ref="suggestorPopover"
       class="autocomplete-panel"
       placement="bottom"
+      :trigger-attrs="{ 'aria-hidden': true }"
     >
       <template #content>
         <div
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index ca90bf26..6972164b 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -3,6 +3,7 @@
     ref="popover"
     trigger="click"
     popover-class="emoji-picker popover-default"
+    :trigger-attrs="{ 'aria-hidden': true }"
     @show="onPopoverShown"
     @close="onPopoverClosed"
   >
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 328e145f..64a8887c 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -30,6 +30,9 @@
           <span>{{ $t('post_status.scope_notice.public') }}</span>
           <a
             class="fa-scale-110 fa-old-padding dismiss"
+            :title="$t('post_status.scope_notice_dismiss')"
+            role="button"
+            tabindex="0"
             @click.prevent="dismissScopeNotice()"
           >
             <FAIcon icon="times" />
@@ -42,6 +45,9 @@
           <span>{{ $t('post_status.scope_notice.unlisted') }}</span>
           <a
             class="fa-scale-110 fa-old-padding dismiss"
+            :title="$t('post_status.scope_notice_dismiss')"
+            role="button"
+            tabindex="0"
             @click.prevent="dismissScopeNotice()"
           >
             <FAIcon icon="times" />
@@ -54,6 +60,9 @@
           <span>{{ $t('post_status.scope_notice.private') }}</span>
           <a
             class="fa-scale-110 fa-old-padding dismiss"
+            :title="$t('post_status.scope_notice_dismiss')"
+            role="button"
+            tabindex="0"
             @click.prevent="dismissScopeNotice()"
           >
             <FAIcon icon="times" />
@@ -199,6 +208,7 @@
               id="post-content-type"
               v-model="newStatus.contentType"
               class="form-control"
+              :attrs="{ 'aria-label': $t('post_status.content_type_selection') }"
             >
               <option
                 v-for="postFormat in postFormats"
diff --git a/src/components/select/select.js b/src/components/select/select.js
index ec571a14..34d64fd2 100644
--- a/src/components/select/select.js
+++ b/src/components/select/select.js
@@ -13,6 +13,7 @@ export default {
     'modelValue',
     'disabled',
     'unstyled',
-    'kind'
+    'kind',
+    'attrs'
   ]
 }
diff --git a/src/components/select/select.vue b/src/components/select/select.vue
index 0ce4ea82..1797afc8 100644
--- a/src/components/select/select.vue
+++ b/src/components/select/select.vue
@@ -6,6 +6,7 @@
     <select
       :disabled="disabled"
       :value="modelValue"
+      v-bind="attrs"
       @change="$emit('update:modelValue', $event.target.value)"
     >
       <slot />
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ebd7d0af..d0965f84 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -266,6 +266,7 @@
       "text/markdown": "Markdown",
       "text/bbcode": "BBCode"
     },
+    "content_type_selection": "Post format",
     "content_warning": "Subject (optional)",
     "default": "Just landed in L.A.",
     "direct_warning_to_all": "This post will be visible to all the mentioned users.",
@@ -283,6 +284,7 @@
       "private": "This post will be visible to your followers only",
       "unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
     },
+    "scope_notice_dismiss": "Close this notice",
     "scope": {
       "direct": "Direct - post to mentioned users only",
       "private": "Followers-only - post to followers only",
-- 
cgit v1.2.3-70-g09d2

