feat: improve accessibility

This commit is contained in:
2026-04-01 15:20:59 +03:00
parent b4a3765dca
commit 7b1f42587b
13 changed files with 104 additions and 33 deletions

View File

@@ -3,12 +3,12 @@
<div class="bbcode-help">
<!-- Built-in BBCodes Section -->
<section class="bbcode-section">
<h3 class="section-title">{{ strings.builtInTitle }}</h3>
<h2 class="section-title">{{ strings.builtInTitle }}</h2>
<p class="section-description">{{ strings.builtInDescription }}</p>
<div class="bbcode-list">
<ul class="bbcode-list">
<!-- Library-provided BBCodes -->
<div v-for="code in builtInCodes" :key="code.tag" class="bbcode-item">
<li v-for="code in builtInCodes" :key="code.tag" class="bbcode-item">
<div class="bbcode-header">
<code class="bbcode-tag">[{{ code.tag }}]</code>
<span class="bbcode-name">{{ code.name }}</span>
@@ -17,10 +17,10 @@
<span class="example-label">{{ strings.example }}:</span>
<code class="example-code">{{ code.example }}</code>
</div>
</div>
</li>
<!-- Database builtin BBCodes -->
<div v-for="code in builtinDbCodes" :key="code.id" class="bbcode-item">
<li v-for="code in builtinDbCodes" :key="code.id" class="bbcode-item">
<div class="bbcode-header">
<code class="bbcode-tag">[{{ code.tag }}]</code>
<span v-if="code.description" class="bbcode-name">{{ code.description }}</span>
@@ -29,13 +29,13 @@
<span class="example-label">{{ strings.example }}:</span>
<code class="example-code">{{ code.example }}</code>
</div>
</div>
</div>
</li>
</ul>
</section>
<!-- Custom BBCodes Section -->
<section v-if="showCustom" class="bbcode-section">
<h3 class="section-title">{{ strings.customTitle }}</h3>
<h2 class="section-title">{{ strings.customTitle }}</h2>
<p class="section-description">{{ strings.customDescription }}</p>
<!-- Loading state -->
@@ -55,8 +55,8 @@
</div>
<!-- Custom codes list -->
<div v-else class="bbcode-list">
<div v-for="code in customCodes" :key="code.id" class="bbcode-item">
<ul v-else class="bbcode-list">
<li v-for="code in customCodes" :key="code.id" class="bbcode-item">
<div class="bbcode-header">
<code class="bbcode-tag">[{{ code.tag }}]</code>
<span v-if="code.description" class="bbcode-name">{{ code.description }}</span>
@@ -65,8 +65,8 @@
<span class="example-label">{{ strings.example }}:</span>
<code class="example-code" :v-html="code.example"></code>
</div>
</div>
</div>
</li>
</ul>
</section>
</div>
</NcDialog>
@@ -313,6 +313,9 @@ export default defineComponent({
display: flex;
flex-direction: column;
gap: 12px;
list-style: none;
padding: 0;
margin: 0;
}
.bbcode-item {

View File

@@ -110,7 +110,7 @@
@update:open="uploadDialog = $event"
size="small"
>
<div class="upload-progress">
<div class="upload-progress" aria-live="polite">
<p class="upload-filename">{{ uploadFileName }}</p>
<template v-if="uploadError">
<p class="upload-error-message">{{ uploadError }}</p>

View File

@@ -3,9 +3,17 @@
class="category-card"
:class="{ unread: isUnread, colored: !!category.color }"
:style="cardStyle"
role="link"
tabindex="0"
>
<div class="category-header">
<span v-if="isUnread" class="unread-indicator" :title="strings.unread"></span>
<span
v-if="isUnread"
class="unread-indicator"
:title="strings.unread"
:aria-label="strings.unread"
role="img"
></span>
<h4 class="category-name">{{ category.name }}</h4>
<div class="category-stats">
<span class="stat">

View File

@@ -8,12 +8,16 @@
@update:model-value="$emit('update:modelValue', $event)"
@submit="$emit('update:modelValue', $event)"
>
<NcButton>
<NcButton
:aria-label="modelValue ? strings.changeColor + ': ' + modelValue : strings.pickColor"
>
<template #icon>
<div
class="color-preview"
:class="{ empty: !modelValue }"
:style="modelValue ? { backgroundColor: modelValue } : {}"
role="img"
:aria-label="modelValue || strings.noColor"
/>
</template>
{{ modelValue || strings.pickColor }}
@@ -53,6 +57,8 @@ export default defineComponent({
return {
strings: {
pickColor: t('forum', 'Pick a color'),
changeColor: t('forum', 'Change color'),
noColor: t('forum', 'No color selected'),
},
}
},

View File

@@ -31,7 +31,7 @@
</template>
{{ initializing ? strings.initializingButton : strings.initializeButton }}
</NcButton>
<NcNoteCard v-if="errorMessage" type="error" class="init-note">
<NcNoteCard v-if="errorMessage" type="error" class="init-note" role="alert">
{{ errorMessage }}
</NcNoteCard>
</div>

View File

@@ -1,7 +1,7 @@
<template>
<div class="deleted-item-list">
<!-- Loading -->
<div v-if="loading" class="center mt-16">
<div v-if="loading" class="center mt-16" aria-live="polite" aria-busy="true">
<NcLoadingIcon :size="32" />
</div>
@@ -27,14 +27,17 @@
<!-- Items -->
<template v-else>
<div class="item-list">
<ul class="item-list">
<!-- Thread items: clickable to open preview -->
<div
<li
v-for="item in items"
:key="item.id"
class="deleted-item-wrapper"
:class="{ clickable: mode === 'threads' }"
:role="mode === 'threads' ? 'button' : undefined"
:tabindex="mode === 'threads' ? 0 : undefined"
@click="mode === 'threads' && $emit('view', item)"
@keydown.enter="mode === 'threads' && $emit('view', item)"
>
<div class="deleted-item-overlay">
<span class="deleted-badge">
@@ -67,8 +70,8 @@
</div>
<ThreadCard v-if="mode === 'threads'" :thread="item" />
<PostCard v-else :post="item" />
</div>
</div>
</li>
</ul>
<Pagination
v-if="maxPages > 1"
@@ -140,6 +143,8 @@ export default defineComponent({
flex-direction: column;
gap: 12px;
margin-bottom: 16px;
list-style: none;
padding: 0;
}
.deleted-item-wrapper {

View File

@@ -2,7 +2,13 @@
<div class="post-card" :class="{ 'first-post': isFirstPost, unread: isUnread }">
<div class="post-header">
<div class="author-info">
<span v-if="isUnread" class="unread-indicator" :title="strings.unread"></span>
<span
v-if="isUnread"
class="unread-indicator"
:title="strings.unread"
:aria-label="strings.unread"
role="img"
></span>
<UserInfo
:user-id="post.author?.userId || post.authorId"
:display-name="post.author?.displayName || post.authorId"

View File

@@ -2,7 +2,7 @@
<NcDialog :name="strings.title" :open="open" @update:open="handleClose" size="large">
<div class="post-history-dialog">
<!-- Loading state -->
<div v-if="loading" class="loading-state">
<div v-if="loading" class="loading-state" aria-live="polite" aria-busy="true">
<NcLoadingIcon :size="32" />
<span class="loading-text">{{ strings.loading }}</span>
</div>

View File

@@ -7,6 +7,8 @@
class="reaction-button"
:class="{ reacted: isReacted(emoji), 'has-count': getCount(emoji) > 0 }"
:title="getReactionTooltip(emoji)"
:aria-label="getReactionTooltip(emoji)"
:aria-pressed="isReacted(emoji)"
@click="handleToggleReaction(emoji)"
>
<span class="emoji">{{ emoji }}</span>
@@ -15,7 +17,11 @@
<!-- Add custom reaction button -->
<LazyEmojiPicker @select="handleSelectEmoji" style="display: inline-block">
<button class="add-reaction-button" :title="strings.addReaction">
<button
class="add-reaction-button"
:title="strings.addReaction"
:aria-label="strings.addReaction"
>
<span class="icon">+</span>
</button>
</LazyEmojiPicker>

View File

@@ -1,5 +1,12 @@
<template>
<div class="search-post-result" :class="{ 'dark-theme': isDarkTheme }" @click="navigateToPost">
<div
class="search-post-result"
:class="{ 'dark-theme': isDarkTheme }"
role="link"
tabindex="0"
@click="navigateToPost"
@keydown.enter="navigateToPost"
>
<div class="result-header">
<div class="thread-context">
<span class="meta-label">{{ strings.inThread }}:</span>

View File

@@ -1,11 +1,30 @@
<template>
<div class="search-thread-result" :class="{ 'dark-theme': isDarkTheme }" @click="$emit('click')">
<div
class="search-thread-result"
:class="{ 'dark-theme': isDarkTheme }"
role="link"
tabindex="0"
@click="$emit('click')"
@keydown.enter="$emit('click')"
>
<div class="result-header">
<h4 class="thread-title">
<span v-if="thread.isPinned" class="badge badge-pinned" :title="strings.pinned">
<span
v-if="thread.isPinned"
class="badge badge-pinned"
:title="strings.pinned"
:aria-label="strings.pinned"
role="img"
>
<PinIcon :size="16" />
</span>
<span v-if="thread.isLocked" class="badge badge-locked" :title="strings.locked">
<span
v-if="thread.isLocked"
class="badge badge-locked"
:title="strings.locked"
:aria-label="strings.locked"
role="img"
>
<LockIcon :size="16" />
</span>
<span v-html="highlightedTitle"></span>
@@ -35,6 +54,7 @@
</span>
<a
v-if="thread.lastReply"
href="#"
class="meta-item last-reply"
@click.prevent.stop="$emit('navigate-last-reply', thread)"
>

View File

@@ -6,7 +6,13 @@
<div class="thread-main">
<div class="thread-header">
<div class="thread-title-row">
<span v-if="isUnread" class="unread-indicator" :title="strings.unread"></span>
<span
v-if="isUnread"
class="unread-indicator"
:title="strings.unread"
:aria-label="strings.unread"
role="img"
></span>
<h4 class="thread-title">
<span v-if="thread.isPinned" class="badge badge-pinned" :title="strings.pinned">
<PinIcon :size="16" />

View File

@@ -11,6 +11,7 @@
v-model="searchQuery"
type="text"
:placeholder="strings.searchPlaceholder"
:aria-label="strings.searchTitle"
class="search-input"
@keydown.enter="performSearch"
/>
@@ -23,7 +24,7 @@
</div>
<!-- Search Options -->
<div class="search-options">
<fieldset class="search-options">
<NcCheckboxRadioSwitch v-model="searchThreads" @update:checked="onOptionsChange">
{{ strings.searchThreads }}
</NcCheckboxRadioSwitch>
@@ -37,7 +38,7 @@
</template>
{{ strings.syntaxHelp }}
</NcButton>
</div>
</fieldset>
<!-- Syntax Help -->
<div v-if="showSyntaxHelp" class="syntax-help">
@@ -53,7 +54,7 @@
</div>
<!-- Loading State -->
<div v-if="loading" class="center mt-16">
<div v-if="loading" class="center mt-16" aria-live="polite">
<NcLoadingIcon :size="32" />
<span class="muted ml-8">{{ strings.searching }}</span>
</div>
@@ -95,7 +96,7 @@
</NcEmptyContent>
<!-- Results -->
<div v-else class="search-results mt-16">
<div v-else class="search-results mt-16" aria-live="polite">
<!-- Thread Results Section -->
<section v-if="searchThreads && threadResults.length > 0" class="results-section">
<h3 class="results-header">
@@ -328,6 +329,9 @@ export default defineComponent({
align-items: center;
gap: 16px;
flex-wrap: wrap;
border: none;
padding: 0;
margin: 0;
}
.syntax-help {