start updating accessibility

This commit is contained in:
Jordan Blasenhauer 2024-06-18 12:08:11 +02:00
parent 4e65e24292
commit f0a57e1c0d
14 changed files with 187 additions and 162 deletions

View file

@ -4,6 +4,7 @@ import Alert from "@components/Widget/Alert.vue";
import { feedbackIndex } from "@utils/tabindex.js";
import { useBannerStore } from "@store/global.js";
import { onBeforeMount } from "vue";
import { v4 as uuidv4 } from "uuid";
/**
@name Dashboard/Feedback.vue
@description This component will display server feedbacks from the user.
@ -13,6 +14,7 @@ import { onBeforeMount } from "vue";
const feedback = reactive({
data: [],
id: uuidv4(),
});
// Handle feedback history panel
@ -24,15 +26,17 @@ const dropdown = reactive({
const bannerStore = useBannerStore();
onBeforeMount(() => {
const dataAtt = 'data-server-flash';
const dataEl = document.querySelector(`[${dataAtt}]`);
const data = dataEl && !dataEl.getAttribute(dataAtt).includes(dataAtt) ? JSON.parse(dataEl.getAttribute(dataAtt)) : [];
feedback.data = data;
})
const dataAtt = "data-server-flash";
const dataEl = document.querySelector(`[${dataAtt}]`);
const data =
dataEl && !dataEl.getAttribute(dataAtt).includes(dataAtt)
? JSON.parse(dataEl.getAttribute(dataAtt))
: [];
feedback.data = data;
});
</script>
<template>
<Alert
v-for="(item, id) in feedback.data"
:title="feedback.data[id].title"
@ -54,7 +58,7 @@ onBeforeMount(() => {
:aria-expanded="dropdown.isOpen ? 'true' : 'false'"
@click="dropdown.isOpen = dropdown.isOpen ? false : true"
class="feedback-float-btn"
:aria-describedby="`feedback-sidebar-toggle-btn-text`"
:aria-labelledby="`feedback-sidebar-toggle-btn-text`"
>
<span class="sr-only" id="feedback-sidebar-toggle-btn-text">
{{ $t("dashboard_feedback_toggle_sidebar") }}
@ -92,7 +96,7 @@ onBeforeMount(() => {
:aria-expanded="dropdown.isOpen ? 'true' : 'false'"
class="feedback-header-close-btn"
@click="dropdown.isOpen = false"
:aria-describedby="`feedback-sidebar-close-btn-text`"
:aria-labelledby="`feedback-sidebar-close-btn-text`"
>
<span class="sr-only" id="feedback-sidebar-close-btn-text">
{{ $t("dashboard_feedback_close_sidebar") }}
@ -126,13 +130,13 @@ onBeforeMount(() => {
<!-- end header -->
<Alert
v-for="(item, id) in feedback.data"
:type="item.type"
:id="item.id"
:title="item.title"
:message="item.message"
:tabId="dropdown.isOpen ? feedbackIndex : '-1'"
/>
v-for="(item, id) in feedback.data"
:type="item.type"
:id="item.id"
:title="item.title"
:message="item.message"
:tabId="dropdown.isOpen ? feedbackIndex : '-1'"
/>
</aside>
<!-- end right sidebar -->
</template>

View file

@ -57,12 +57,13 @@ function updateLangStorage(lang) {
:tabindex="langIndex"
aria-controls="switch-lang"
:aria-expanded="lang.isOpen ? 'true' : 'false'"
:aria-description="$t('dashboard_lang_dropdown_button_desc')"
:aria-labelledby="'current-lang'"
@click="lang.isOpen = lang.isOpen ? false : true"
>
<span id="current-lang" class="sr-only">{{ $i18n.locale }}</span>
<span
:aria-labelledby="`current-lang`"
role="img"
aria-hidden="true"
id="switch-lang-text"
:class="[`fi fi-${$i18n.locale}`]"
></span>

View file

@ -183,9 +183,9 @@ onBeforeMount(() => {
:aria-expanded="menu.isDesktop ? 'true' : menu.isActive ? 'true' : 'false'"
@click="toggleMenu()"
:class="['menu-float-btn', bannerStore.bannerClass]"
aria-describedby="sidebar-menu-toggle"
:aria-labelledby="'float-btn-sidebar'"
>
<span class="sr-only" id="sidebar-menu-toggle">
<span id="float-btn-sidebar" class="sr-only">
{{ $t("dashboard_menu_toggle_sidebar") }}
</span>
<svg
@ -224,9 +224,9 @@ onBeforeMount(() => {
@click="closeMenu()"
class="menu-close-btn"
:class="[menu.isDesktop ? 'hidden' : '']"
aria-describedby="sidebar-menu-close"
:aria-labelledby="'float-btn-close-menu'"
>
<span class="sr-only" id="sidebar-menu-close">
<span id="float-btn-close-menu" class="sr-only">
{{ $t("dashboard_menu_close_sidebar") }}
</span>
<svg
@ -262,12 +262,14 @@ onBeforeMount(() => {
:aria-hidden="'true'"
v-if="menu.darkMode"
:src="logoMenu2"
:alt="$t('dashboard_logo_alt')"
class="menu-logo-dark"
/>
<img
:aria-hidden="'true'"
v-if="!menu.darkMode"
:src="logoMenu"
:alt="$t('dashboard_logo_alt')"
class="menu-logo-light"
/>
</a>

View file

@ -19,44 +19,43 @@ const news = reactive({
});
function loadNews() {
// Check if data, and if case, that data is not older than one hour
// Case it is, refetch
if (sessionStorage.getItem("lastRefetch") !== null) {
const storeStamp = sessionStorage.getItem("lastRefetch");
const nowStamp = Math.round(new Date().getTime() / 1000);
if (+nowStamp > storeStamp) {
sessionStorage.removeItem("lastRefetch");
sessionStorage.removeItem("lastNews");
}
// Check if data, and if case, that data is not older than one hour
// Case it is, refetch
if (sessionStorage.getItem("lastRefetch") !== null) {
const storeStamp = sessionStorage.getItem("lastRefetch");
const nowStamp = Math.round(new Date().getTime() / 1000);
if (+nowStamp > storeStamp) {
sessionStorage.removeItem("lastRefetch");
sessionStorage.removeItem("lastNews");
}
}
// Case we already have the data
if (sessionStorage.getItem("lastNews") !== null)
return (news.posts = JSON.parse(sessionStorage.getItem("lastNews")));
// Try to fetch api data
fetch("https://www.bunkerweb.io/api/posts/0/2")
.then((res) => {
return res.json();
})
.then((res) => {
const reverseData = res.data.reverse();
if (
!sessionStorage.getItem("lastNews") &&
!sessionStorage.getItem("lastRefetch")
) {
sessionStorage.setItem(
"lastRefetch",
Math.round(new Date().getTime() / 1000) + 3600
);
sessionStorage.setItem("lastNews", JSON.stringify(reverseData));
}
// Case we already have the data
if (sessionStorage.getItem("lastNews") !== null)
return news.posts = JSON.parse(sessionStorage.getItem("lastNews"));
// Try to fetch api data
fetch("https://www.bunkerweb.io/api/posts/0/2")
.then((res) => {
return res.json();
})
.then((res) => {
const reverseData = res.data.reverse();
if (
!sessionStorage.getItem("lastNews") &&
!sessionStorage.getItem("lastRefetch")
) {
sessionStorage.setItem(
"lastRefetch",
Math.round(new Date().getTime() / 1000) + 3600,
);
sessionStorage.setItem("lastNews", JSON.stringify(reverseData));
}
news.posts = reverseData;
})
.catch((e) => {});
news.posts = reverseData;
})
.catch((e) => {});
}
onMounted(() => {
loadNews();
});
@ -70,9 +69,9 @@ onMounted(() => {
:aria-expanded="news.isActive ? 'true' : 'false'"
@click="news.isActive = news.isActive ? false : true"
:class="['news-float-btn', bannerStore.bannerClass]"
aria-describedby="sidebar-news-toggle"
:aria-labelledby="'float-btn-news'"
>
<span class="sr-only" id="sidebar-news-toggle">
<span id="float-btn-news" class="sr-only">
{{ $t("dashboard_news_toggle_sidebar") }}
</span>
<svg
@ -102,9 +101,9 @@ onMounted(() => {
aria-controls="sidebar-news"
:aria-expanded="news.isActive ? 'true' : 'false'"
@click="news.isActive = false"
aria-describedby="sidebar-news-close"
:aria-labelledby="'float-btn-close-news'"
>
<span class="sr-only" id="sidebar-news-close">
<span id="float-btn-close-news" class="sr-only">
{{ $t("dashboard_news_close_sidebar") }}
</span>
<svg
@ -136,39 +135,43 @@ onMounted(() => {
<p v-if="news.posts.length === 0" class="news-sidebar-no-posts-content">
{{ $t("dashboard_news_fetch_error") }}
</p>
<a :tabindex="news.isActive ? newsIndex : '-1'" :href="`https://www.bunkerweb.io/blog/post/bunkerweb/${post.slug}?utm_campaign=self&utm_source=ui`" class="news-sidebar-post"
v-for="(post, index) in news.posts" :key="index"
<a
:tabindex="news.isActive ? newsIndex : '-1'"
:href="`https://www.bunkerweb.io/blog/post/bunkerweb/${post.slug}?utm_campaign=self&utm_source=ui`"
class="news-sidebar-post"
v-for="(post, index) in news.posts"
:key="index"
>
<div>
<img aria-hidden="true"
class="news-sidebar-post-img"
:src="post.photo.url"
alt="image"
/>
<span
class="news-sidebar-post-title">{{post.title}}</span>
<img
aria-hidden="true"
class="news-sidebar-post-img"
:src="post.photo.url"
alt="image"
/>
<span class="news-sidebar-post-title">{{ post.title }}</span>
</div>
<div class="h-full">
<div
class="news-sidebar-post-excerpt">
{{post.excerpt}}
</div>
<div class="news-sidebar-post-tags-container">
<a v-for="tag in post.tags"
:href="`https://www.bunlerweb.io/blog/tag/${tag.slug}?utm_campaign=self&utm_source=ui`"
class="news-sidebar-post-tag"
>
{{ tag.name }}
</a>
</div>
<div class="news-sidebar-post-excerpt">
{{ post.excerpt }}
</div>
<div class="news-sidebar-post-tags-container">
<a
v-for="tag in post.tags"
:href="`https://www.bunkerweb.io/blog/tag/${tag.slug}?utm_campaign=self&utm_source=ui`"
class="news-sidebar-post-tag"
>
{{ tag.name }}
</a>
</div>
<div class="news-sidebar-post-date-container">
<span class="news-sidebar-post-date"
>Posted on : {{post.date}}
</span>
</div>
<div class="news-sidebar-post-date-container">
<span class="news-sidebar-post-date"
>Posted on : {{ post.date }}
</span>
</div>
</div>
</a>
</a>
</div>
<!-- end news-->

View file

@ -64,7 +64,7 @@ const props = defineProps({
:class="['input-clipboard-button', copied ? 'copied' : 'not-copied']"
:tabindex="contentIndex"
@click.prevent="copy(valueToCopy)"
:aria-describedby="`${props.id}-clipboard-text`"
:aria-labelledby="`${props.id}-clipboard-text`"
>
<span :id="`${props.id}-clipboard-text`" class="sr-only">
{{ $t("inp_input_clipboard_desc") }}

View file

@ -221,12 +221,15 @@ onMounted(() => {
<div v-if="props.type === 'password'" class="input-pw-container">
<button
:tabindex="contentIndex"
:aria-description="$t('inp_input_password_desc')"
:aria-controls="props.id"
@click.prevent="inp.showInp = inp.showInp ? false : true"
:class="[props.disabled ? 'disabled' : 'enabled']"
class="input-pw-button"
:aria-labelledby="`${props.id}-password-text`"
>
<span :id="`${props.id}-password-text`" class="sr-only">{{
$t("inp_input_password_desc")
}}</span>
<svg
role="img"
aria-hidden="true"

View file

@ -1,4 +1,5 @@
<script setup>
import { v4 as uuidv4 } from "uuid";
/**
@name Icons/Check.vue
@description This component is a svg icon representing a check mark.
@ -22,6 +23,8 @@ const props = defineProps({
default: "info",
},
});
const id = uuidv4();
</script>
<template>
<svg
@ -30,9 +33,10 @@ const props = defineProps({
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
:aria-description="$t('icons_check_desc')"
:aria-labelledby="id"
:class="[props.iconClass, props.iconColor]"
>
<span :id="id" class="sr-only">{{ $t("icons_check_desc") }}</span>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"

View file

@ -1,7 +1,7 @@
<script setup>
import { onMounted } from "vue";
import { defineProps, defineEmits, reactive } from "vue";
import { v4 as uuidv4 } from "uuid";
/**
@name Forms/Error/Field.vue
@description This component is an alert type to send feedback to the user.
@ -29,17 +29,17 @@ const props = defineProps({
id: {
type: [Number, String],
required: false,
default: "",
default: uuidv4(),
},
isFixed : {
isFixed: {
type: Boolean,
required: false,
default : false
default: false,
},
type: {
type: String,
required: false,
default : "info"
default: "info",
},
title: {
type: [Number, String],
@ -62,13 +62,13 @@ const props = defineProps({
tabId: {
type: [String, Number],
required: false,
default: "-1"
default: "-1",
},
});
const alert = reactive({
visible: true,
})
});
onMounted(() => {
if (props.delayToClose > 0) {
@ -80,43 +80,43 @@ onMounted(() => {
</script>
<template>
<div v-if="alert.visible"
<div
v-if="alert.visible"
:class="['feedback-alert-container', props.isFixed ? 'is-fixed' : '']"
:id="props.id || `feedback-alert-${props.message.substring(0, 10)}`"
:role="props.type === 'success' ? 'status' : 'alert'"
:aria-description="$t('dashboard_feedback_alert_desc')"
>
<div
:class="[
props.type,
'feedback-alert-wrap',
]"
>
<div class="feedback-alert-header">
<h5 class="feedback-alert-title">
{{ $t(props.title, props.title) }}
</h5>
<button
:tabindex="props.tabId"
@click="alert.visible = false"
data-close-flash-message
type="button"
class="feedback-alert-btn"
>
<svg
aria-hidden="true"
role="img"
class="feedback-alert-svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
<div :class="[props.type, 'feedback-alert-wrap']">
<div class="feedback-alert-header">
<h5 class="feedback-alert-title">
{{ $t(props.title, props.title) }}
</h5>
<button
:tabindex="props.tabId"
@click="alert.visible = false"
data-close-flash-message
type="button"
class="feedback-alert-btn"
:aria-labelledby="`${props.id}-close`"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<span class="sr-only" :id="`${props.id}-close`">
{{ $t("dashboard_feedback_alert_close") }}
</span>
<svg
aria-hidden="true"
role="img"
class="feedback-alert-svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
</div>
<p class="feedback-alert-text">{{ $t(props.message, props.message) }}</p>
</div>
<p class="feedback-alert-text">{{ $t(props.message, props.message) }}</p>
</div>
</div>
</template>

View file

@ -143,7 +143,7 @@ function setAttrs() {
:tabindex="props.tabId"
:class="[buttonClass]"
:disabled="props.disabled || false"
:aria-describedby="`text-${props.id}`"
:aria-labelledby="`text-${props.id}`"
>
<span
:class="[
@ -152,8 +152,8 @@ function setAttrs() {
'pointer-events-none',
]"
:id="`text-${props.id}`"
>{{ $t(props.text, props.text) }}</span
>
>{{ $t(props.text, props.text) }}
</span>
<Icons
v-if="props.iconName"
:iconName="props.iconName"

View file

@ -138,7 +138,7 @@ watch(popover, () => {
:tabindex="props.tabId"
:aria-controls="`${props.id}-popover-text`"
:aria-expanded="popover.isOpen ? 'true' : 'false'"
:aria-describedby="`${props.id}-popover-text`"
:aria-description="$t(props.text, props.text)"
:is="props.tag"
role="button"
@click.prevent
@ -148,6 +148,7 @@ watch(popover, () => {
@pointerleave="hidePopover()"
:class="['popover-btn', props.popoverClass]"
>
<span class="sr-only">{{ $t(props.text, props.text) }}</span>
<Icons
:iconClass="'popover-svg'"
:iconName="props.iconName"

View file

@ -48,7 +48,7 @@ const statusDesc = computed(() => {
<div :class="[props.statusClass, 'status-svg-container']">
<div
role="img"
:aria-describedby="`instance-status-${props.id}`"
:aria-description="$t(statusDesc[0], statusDesc[1])"
:class="[props.status, 'status-icon']"
></div>
<p :id="`instance-status-${props.id}`" class="sr-only">

View file

@ -181,18 +181,18 @@ onMounted(() => {
:aria-colcount="table.length"
:aria-rowcount="table.rowLength"
:class="['table', props.minWidth, props.tableClass]"
:aria-describedby="table.id"
:aria-description="$t(table.title, table.title)"
>
<span class="sr-only" :id="table.id">
{{ $t(table.title, table.title) }}
</span>
<thead
role="rowgroup"
ref="tableHeader"
class="table-header"
:style="{ paddingRight: table.overflow }"
:aria-rowindex="1"
>
<tr
v-for="(head, id) in props.header"
:aria-colindex="id + 1"
:class="['table-header-item', `col-span-${props.positions[id]}`]"
>
<th role="columnheader">
@ -200,7 +200,12 @@ onMounted(() => {
</th>
</tr>
</thead>
<tbody data-table-body ref="tableBody" class="table-content">
<tbody
data-table-body
role="rowgroup"
ref="tableBody"
class="table-content"
>
<tr
v-for="rowId in table.rowLength"
:key="rowId - 1"
@ -213,6 +218,7 @@ onMounted(() => {
:key="col"
>
<td
:aria-colindex="id + 1"
role="cell"
:class="[
'table-content-item-wrap',

View file

@ -35,6 +35,7 @@
"dashboard_logs": "logs",
"dashboard_feedback_toggle_sidebar": "Toggle feedback sidebar.",
"dashboard_feedback_close_sidebar": "Close feedback sidebar.",
"dashboard_feedback_alert_close" : "Close feedback alert",
"dashboard_feedback_title": "feedback",
"dashboard_feedback_subtitle": "BunkerWeb actions",
"dashboard_menu_toggle_sidebar": "Toggle menu sidebar.",

View file

@ -374,30 +374,6 @@ def job_builder(jobs):
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
{
"filter": "table",
"filterName": "success",
"type": "select",
"value": "all",
"keys": ["success"],
"field": {
"id": "jobs-success",
"value": "all",
"values": ["all", "success", "failed"],
"name": "jobs-success",
"onlyDown": True,
"containerClass": "setting",
"label": "jobs_success",
"popovers": [
{
"text": "jobs_success_desc",
"iconName": "info",
"iconColor": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
{
"filter": "table",
"filterName": "reload",
@ -422,6 +398,30 @@ def job_builder(jobs):
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
{
"filter": "table",
"filterName": "success",
"type": "select",
"value": "all",
"keys": ["success"],
"field": {
"id": "jobs-success",
"value": "all",
"values": ["all", "success", "failed"],
"name": "jobs-success",
"onlyDown": True,
"containerClass": "setting",
"label": "jobs_success",
"popovers": [
{
"text": "jobs_success_desc",
"iconName": "info",
"iconColor": "info",
},
],
"columns": {"pc": 3, "tablet": 4, "mobile": 12},
},
},
],
},
},