From ec741923f7653dcbff5a423f0b1130d2e7c9d323 Mon Sep 17 00:00:00 2001
From: userquin <userquin@gmail.com>
Date: Sat, 6 Apr 2024 00:26:22 +0200
Subject: [PATCH 1/3] feat(a11y): add access keys shortcuts

---
 components/main/MainContent.vue                    |  5 +++++
 components/skip/SkipContent.ts                     | 14 ++++++++++++++
 components/skip/SkipContentLink.vue                |  5 +++++
 constants/access-keys.ts                           |  6 ++++++
 layouts/default.vue                                |  1 +
 locales/en.json                                    |  3 ++-
 locales/es.json                                    |  3 ++-
 pages/settings.vue                                 |  2 +-
 pages/settings/about/index.vue                     |  2 +-
 pages/settings/interface/index.vue                 |  2 +-
 pages/settings/language/index.vue                  |  2 +-
 pages/settings/notifications/index.vue             |  2 +-
 pages/settings/notifications/notifications.vue     |  2 +-
 .../settings/notifications/push-notifications.vue  |  2 +-
 pages/settings/preferences/index.vue               |  2 +-
 pages/settings/profile/appearance.vue              |  2 +-
 pages/settings/profile/featured-tags.vue           |  2 +-
 pages/settings/profile/index.vue                   |  2 +-
 pages/settings/users/index.vue                     |  2 +-
 19 files changed, 47 insertions(+), 14 deletions(-)
 create mode 100644 components/skip/SkipContent.ts
 create mode 100644 components/skip/SkipContentLink.vue
 create mode 100644 constants/access-keys.ts

diff --git a/components/main/MainContent.vue b/components/main/MainContent.vue
index 312b5e5288..8a55a1657d 100644
--- a/components/main/MainContent.vue
+++ b/components/main/MainContent.vue
@@ -6,6 +6,8 @@ defineProps<{
   back?: boolean
   /** Do not applying overflow hidden to let use floatable components in title */
   noOverflowHidden?: boolean
+  /** Show the skip content link */
+  skipContent?: string
 }>()
 
 const container = ref()
@@ -26,6 +28,9 @@ const containerClass = computed(() => {
 
 <template>
   <div ref="container" :class="containerClass">
+    <SkipContentLink v-if="skipContent">
+      {{ $t(skipContent) }}
+    </SkipContentLink>
     <div
       sticky top-0 z10
       pt="[env(safe-area-inset-top,0)]"
diff --git a/components/skip/SkipContent.ts b/components/skip/SkipContent.ts
new file mode 100644
index 0000000000..872a4b016a
--- /dev/null
+++ b/components/skip/SkipContent.ts
@@ -0,0 +1,14 @@
+// @unocss-include
+import { accessKeys } from '~/constants/access-keys'
+
+export default defineComponent({
+  setup() {
+    const { t } = useI18n()
+    return () => h('a', {
+      id: 'skip',
+      class: 'sr-only',
+      href: '#skip-content',
+      accesskey: accessKeys.SkipContent,
+    }, t(`a11y.skip_navigation`))
+  },
+})
diff --git a/components/skip/SkipContentLink.vue b/components/skip/SkipContentLink.vue
new file mode 100644
index 0000000000..c3b92201c6
--- /dev/null
+++ b/components/skip/SkipContentLink.vue
@@ -0,0 +1,5 @@
+<template>
+  <NuxtLink id="skip-content" sr-only>
+    <slot />
+  </NuxtLink>
+</template>
diff --git a/constants/access-keys.ts b/constants/access-keys.ts
new file mode 100644
index 0000000000..82b8aa3e08
--- /dev/null
+++ b/constants/access-keys.ts
@@ -0,0 +1,6 @@
+export const accessKeys = {
+  Home: 'S',
+  SkipContent: '1',
+  Search: '2',
+  Shortcuts: '3',
+} as const
diff --git a/layouts/default.vue b/layouts/default.vue
index ecf6c106fd..5634302f5b 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -16,6 +16,7 @@ const isGrayscale = usePreferences('grayscaleMode')
 
 <template>
   <div h-full :data-mode="isHydrated && isGrayscale ? 'grayscale' : ''" data-tauri-drag-region>
+    <SkipContent />
     <main flex w-full mxa lg:max-w-80rem class="native:grid native:sm:grid-cols-[auto_1fr] native:lg:grid-cols-[auto_minmax(600px,2fr)_1fr]">
       <aside class="native:w-auto w-1/8 md:w-1/6 lg:w-1/5 xl:w-1/4 zen-hide" hidden sm:flex justify-end xl:me-4 native:me-0 relative>
         <div sticky top-0 w-20 xl:w-100 h-100dvh flex="~ col" lt-xl-items-center>
diff --git a/locales/en.json b/locales/en.json
index 645eab0cc1..d8990ab1e6 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -4,7 +4,8 @@
     "loading_titled_page": "Loading {0} page, please wait",
     "locale_changed": "Language changed to {0}",
     "locale_changing": "Changing language, please wait",
-    "route_loaded": "Page {0} loaded"
+    "route_loaded": "Page {0} loaded",
+    "skip_navigation": "Skip navigation"
   },
   "account": {
     "authorize": "Authorize to follow",
diff --git a/locales/es.json b/locales/es.json
index c5d5bf6a7c..6323fb8958 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -4,7 +4,8 @@
     "loading_titled_page": "Cargando página {0}, espera por favor",
     "locale_changed": "Idioma cambiado a {0}",
     "locale_changing": "Cambiando idioma, espera por favor",
-    "route_loaded": "Página {0} cargada"
+    "route_loaded": "Página {0} cargada",
+    "skip_navigation": "Saltar navegación"
   },
   "account": {
     "authorize": "Autorizar seguimiento",
diff --git a/pages/settings.vue b/pages/settings.vue
index 1f53497fd8..cd5464c800 100644
--- a/pages/settings.vue
+++ b/pages/settings.vue
@@ -18,7 +18,7 @@ const isRootPath = computed(() => route.name === 'settings')
   <div>
     <div min-h-screen flex>
       <div border="e base" :class="isRootPath ? 'block lg:flex-none flex-1' : 'hidden lg:block'">
-        <MainContent>
+        <MainContent :skip-content="isRootPath ? 'nav.settings' : undefined">
           <template #title>
             <div timeline-title-style flex items-center gap-2 @click="$scrollToTop">
               <div i-ri:settings-3-line />
diff --git a/pages/settings/about/index.vue b/pages/settings/about/index.vue
index d025514a8a..08f1245df3 100644
--- a/pages/settings/about/index.vue
+++ b/pages/settings/about/index.vue
@@ -17,7 +17,7 @@ function handleShowCommit() {
 </script>
 
 <template>
-  <MainContent back-on-small-screen>
+  <MainContent back-on-small-screen skip-content="settings.about.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <span>{{ $t('settings.about.label') }}</span>
diff --git a/pages/settings/interface/index.vue b/pages/settings/interface/index.vue
index 94ef7e97a9..2639fd4cde 100644
--- a/pages/settings/interface/index.vue
+++ b/pages/settings/interface/index.vue
@@ -7,7 +7,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back-on-small-screen>
+  <MainContent back-on-small-screen skip-content="settings.interface.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <span>{{ $t('settings.interface.label') }}</span>
diff --git a/pages/settings/language/index.vue b/pages/settings/language/index.vue
index 9359af7104..ae465b3f48 100644
--- a/pages/settings/language/index.vue
+++ b/pages/settings/language/index.vue
@@ -15,7 +15,7 @@ const status = computed(() => {
 </script>
 
 <template>
-  <MainContent back-on-small-screen>
+  <MainContent back-on-small-screen skip-content="settings.language.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <span>{{ $t('settings.language.label') }}</span>
diff --git a/pages/settings/notifications/index.vue b/pages/settings/notifications/index.vue
index 467dae7f84..48490e4b99 100644
--- a/pages/settings/notifications/index.vue
+++ b/pages/settings/notifications/index.vue
@@ -12,7 +12,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back-on-small-screen>
+  <MainContent back-on-small-screen skip-content="settings.notifications.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <span>{{ $t('settings.notifications.label') }}</span>
diff --git a/pages/settings/notifications/notifications.vue b/pages/settings/notifications/notifications.vue
index 0cb65cda0c..12124de833 100644
--- a/pages/settings/notifications/notifications.vue
+++ b/pages/settings/notifications/notifications.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back>
+  <MainContent back skip-content="settings.notifications.notifications.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <div i-ri:test-tube-line />
diff --git a/pages/settings/notifications/push-notifications.vue b/pages/settings/notifications/push-notifications.vue
index af5d93ed1c..cce6ce32ab 100644
--- a/pages/settings/notifications/push-notifications.vue
+++ b/pages/settings/notifications/push-notifications.vue
@@ -14,7 +14,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back>
+  <MainContent back skip-content="settings.notifications.push_notifications.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <span>{{ $t('settings.notifications.push_notifications.label') }}</span>
diff --git a/pages/settings/preferences/index.vue b/pages/settings/preferences/index.vue
index b0d6d235bf..09e8737cc8 100644
--- a/pages/settings/preferences/index.vue
+++ b/pages/settings/preferences/index.vue
@@ -9,7 +9,7 @@ const userSettings = useUserSettings()
 </script>
 
 <template>
-  <MainContent back-on-small-screen>
+  <MainContent back-on-small-screen skip-content="settings.preferences.label">
     <template #title>
       <h1 text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         {{ $t('settings.preferences.label') }}
diff --git a/pages/settings/profile/appearance.vue b/pages/settings/profile/appearance.vue
index 54d409543d..54bd1ac613 100644
--- a/pages/settings/profile/appearance.vue
+++ b/pages/settings/profile/appearance.vue
@@ -103,7 +103,7 @@ onReactivated(refreshInfo)
 </script>
 
 <template>
-  <MainContent back>
+  <MainContent back skip-content="settings.profile.appearance.title">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <span>{{ $t('settings.profile.appearance.title') }}</span>
diff --git a/pages/settings/profile/featured-tags.vue b/pages/settings/profile/featured-tags.vue
index 491bf8b74a..0ca91a5246 100644
--- a/pages/settings/profile/featured-tags.vue
+++ b/pages/settings/profile/featured-tags.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back>
+  <MainContent back skip-content="settings.profile.featured_tags.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <div i-ri:test-tube-line />
diff --git a/pages/settings/profile/index.vue b/pages/settings/profile/index.vue
index 5b40258c45..4f2f509635 100644
--- a/pages/settings/profile/index.vue
+++ b/pages/settings/profile/index.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back-on-small-screen>
+  <MainContent back-on-small-screen skip-content="settings.profile.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <span>{{ $t('settings.profile.label') }}</span>
diff --git a/pages/settings/users/index.vue b/pages/settings/users/index.vue
index b44e978191..5f4f0a099a 100644
--- a/pages/settings/users/index.vue
+++ b/pages/settings/users/index.vue
@@ -66,7 +66,7 @@ async function importTokens() {
 </script>
 
 <template>
-  <MainContent back-on-small-screen>
+  <MainContent back-on-small-screen skip-content="settings.users.label">
     <template #title>
       <div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
         <span>{{ $t('settings.users.label') }}</span>

From 0b12a6733977d3b76a1adab6e82ca3062f096185 Mon Sep 17 00:00:00 2001
From: userquin <userquin@gmail.com>
Date: Sat, 6 Apr 2024 00:52:02 +0200
Subject: [PATCH 2/3] chore: update skip components

---
 components/main/MainContent.vue | 2 +-
 components/skip/SkipContent.ts  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/components/main/MainContent.vue b/components/main/MainContent.vue
index 8a55a1657d..370b900233 100644
--- a/components/main/MainContent.vue
+++ b/components/main/MainContent.vue
@@ -6,7 +6,7 @@ defineProps<{
   back?: boolean
   /** Do not applying overflow hidden to let use floatable components in title */
   noOverflowHidden?: boolean
-  /** Show the skip content link */
+  /** Add the skip content link: it is the translation key */
   skipContent?: string
 }>()
 
diff --git a/components/skip/SkipContent.ts b/components/skip/SkipContent.ts
index 872a4b016a..be411ac76f 100644
--- a/components/skip/SkipContent.ts
+++ b/components/skip/SkipContent.ts
@@ -5,7 +5,7 @@ export default defineComponent({
   setup() {
     const { t } = useI18n()
     return () => h('a', {
-      id: 'skip',
+      id: 'skip-navigation',
       class: 'sr-only',
       href: '#skip-content',
       accesskey: accessKeys.SkipContent,

From c0915afdb7cfd4d506c407543e9f9db89703f586 Mon Sep 17 00:00:00 2001
From: userquin <userquin@gmail.com>
Date: Sat, 6 Apr 2024 01:43:13 +0200
Subject: [PATCH 3/3] chore: add main pages

---
 pages/blocks.vue        | 2 +-
 pages/bookmarks.vue     | 2 +-
 pages/conversations.vue | 2 +-
 pages/domain_blocks.vue | 2 +-
 pages/favourites.vue    | 2 +-
 pages/hashtags.vue      | 2 +-
 pages/home.vue          | 2 +-
 pages/mutes.vue         | 2 +-
 pages/notifications.vue | 6 +++++-
 pages/pinned.vue        | 2 +-
 10 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/pages/blocks.vue b/pages/blocks.vue
index df78355d4b..4d898a9cef 100644
--- a/pages/blocks.vue
+++ b/pages/blocks.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back>
+  <MainContent back skip-content="nav.blocked_users">
     <template #title>
       <span timeline-title-style>{{ $t('nav.blocked_users') }}</span>
     </template>
diff --git a/pages/bookmarks.vue b/pages/bookmarks.vue
index efebad8c9a..9bbc87d927 100644
--- a/pages/bookmarks.vue
+++ b/pages/bookmarks.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent>
+  <MainContent skip-content="nav.bookmarks">
     <template #title>
       <NuxtLink to="/bookmarks" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
         <div i-ri:bookmark-line />
diff --git a/pages/conversations.vue b/pages/conversations.vue
index f0ab6d4099..4e68c6a359 100644
--- a/pages/conversations.vue
+++ b/pages/conversations.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent>
+  <MainContent skip-content="nav.conversations">
     <template #title>
       <NuxtLink to="/conversations" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
         <div i-ri:at-line />
diff --git a/pages/domain_blocks.vue b/pages/domain_blocks.vue
index 5f981c3e67..56f61060bf 100644
--- a/pages/domain_blocks.vue
+++ b/pages/domain_blocks.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back>
+  <MainContent back skip-content="nav.blocked_domains">
     <template #title>
       <span timeline-title-style>{{ $t('nav.blocked_domains') }}</span>
     </template>
diff --git a/pages/favourites.vue b/pages/favourites.vue
index 9882be355a..f8c1609657 100644
--- a/pages/favourites.vue
+++ b/pages/favourites.vue
@@ -12,7 +12,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent>
+  <MainContent skip-content="nav.favourites">
     <template #title>
       <NuxtLink to="/favourites" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
         <div :class="useStarFavoriteIcon ? 'i-ri:star-line' : 'i-ri:heart-3-line'" />
diff --git a/pages/hashtags.vue b/pages/hashtags.vue
index e7569e64d1..5cc2bc458a 100644
--- a/pages/hashtags.vue
+++ b/pages/hashtags.vue
@@ -16,7 +16,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent>
+  <MainContent skip-content="nav.hashtags">
     <template #title>
       <NuxtLink to="/hashtags" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
         <div class="i-ri:hashtag" />
diff --git a/pages/home.vue b/pages/home.vue
index 27383c2d61..30023eb534 100644
--- a/pages/home.vue
+++ b/pages/home.vue
@@ -16,7 +16,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent>
+  <MainContent skip-content="nav.home">
     <template #title>
       <NuxtLink to="/home" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
         <div i-ri:home-5-line />
diff --git a/pages/mutes.vue b/pages/mutes.vue
index a9b5d24249..54e0f41a22 100644
--- a/pages/mutes.vue
+++ b/pages/mutes.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent back>
+  <MainContent back skip-content="nav.muted_users">
     <template #title>
       <span timeline-title-style>{{ $t('nav.muted_users') }}</span>
     </template>
diff --git a/pages/notifications.vue b/pages/notifications.vue
index bb3fec610e..175fdd310b 100644
--- a/pages/notifications.vue
+++ b/pages/notifications.vue
@@ -65,10 +65,14 @@ const moreOptions = computed<CommonRouteTabMoreOption>(() => ({
   tooltip: filterText.value,
   match: !!filter.value,
 }))
+const skipContent = computed(() => {
+  const name = route.params.filter
+  return name ? `tab.notifications_${name}` : 'tab.notifications_all'
+})
 </script>
 
 <template>
-  <MainContent>
+  <MainContent :skip-content="skipContent">
     <template #title>
       <NuxtLink to="/notifications" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
         <div i-ri:notification-4-line />
diff --git a/pages/pinned.vue b/pages/pinned.vue
index 5ea9c7c4db..0c79febced 100644
--- a/pages/pinned.vue
+++ b/pages/pinned.vue
@@ -11,7 +11,7 @@ useHydratedHead({
 </script>
 
 <template>
-  <MainContent>
+  <MainContent skip-content="account.pinned">
     <template #title>
       <NuxtLink to="/public/pinned" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
         <div i-ri:pushpin-line />