Compare commits

..

7 Commits

Author SHA1 Message Date
Philipinho e4c3d06a08 fixes 2026-05-04 20:55:13 +01:00
Philipinho 8aa45815f4 action label 2026-05-04 20:36:25 +01:00
Philipinho 660eb4a944 fix contrast 2026-05-04 20:35:55 +01:00
Philipinho 0fdd7ef6d6 aria fixes 2026-05-04 20:15:53 +01:00
Philipinho 1586f4be13 enhance a11y 2026-05-04 19:46:52 +01:00
Philipinho 6fdab5fe70 accessibility 2026-04-20 19:45:55 +01:00
Philipinho fadeeaa59d feat(client): improve accessibility with ARIA labels and semantics
- Add aria-label to icon-only ActionIcon triggers across tree, comments,
  share, group, breadcrumbs, templates, AI chat, API keys, and spaces
- Give custom clickable divs/spans (color swatches, status swatches,
  load-more, PDF error, comment selection) role="button", tabIndex,
  and keyboard handlers
- Add listbox/combobox semantics (role, aria-selected, aria-activedescendant)
  to slash menu, emoji menu, mention list, and link editor results
- Add aria-haspopup/aria-expanded to bubble-menu triggers (node, text
  align, color) and status badge
- Add aria-label to Modal.Root/Dialog instances that lack a title prop
  (page history, template preview, trash preview, drawio, comment,
  find-and-replace, page verification)
- Add en-US translations for new aria-label strings
2026-04-18 13:07:16 +01:00
31 changed files with 621 additions and 280 deletions
@@ -870,8 +870,6 @@
"Previous 7 days": "Previous 7 days",
"Previous 30 days": "Previous 30 days",
"Search chats...": "Search chats...",
"Search chats": "Search chats",
"Ask anything... Use @ to mention pages": "Ask anything... Use @ to mention pages",
"Start a new chat to see it here.": "Start a new chat to see it here.",
"Summarize this page": "Summarize this page",
"Toggle AI Chat": "Toggle AI Chat",
@@ -137,8 +137,7 @@ export default function AiChatSidebar() {
<TextInput
className={classes.searchInput}
placeholder={t("Search chats...")}
aria-label={t("Search chats")}
placeholder="Search chats..."
leftSection={<IconSearch size={14} />}
size="xs"
value={search}
@@ -178,7 +178,6 @@ export default function AsideChatPanel() {
href="/ai"
variant="subtle"
color="dark"
aria-label={t("New chat")}
onClick={handleNewChat}
>
<IconPlus size={20} stroke={1.75} />
@@ -186,23 +185,13 @@ export default function AsideChatPanel() {
</Tooltip>
<Tooltip label={t("Open full page")} openDelay={250}>
<ActionIcon
variant="subtle"
color="dark"
aria-label={t("Open full page")}
onClick={handleExpand}
>
<ActionIcon variant="subtle" color="dark" onClick={handleExpand}>
<IconArrowsDiagonal size={18} stroke={1.5} />
</ActionIcon>
</Tooltip>
<Tooltip label={t("Close")} openDelay={250}>
<ActionIcon
variant="subtle"
color="dark"
aria-label={t("Close")}
onClick={handleClose}
>
<ActionIcon variant="subtle" color="dark" onClick={handleClose}>
<IconX size={20} stroke={1.75} />
</ActionIcon>
</Tooltip>
@@ -225,10 +225,6 @@ export default function ChatInput({
}),
],
editorProps: {
attributes: {
"aria-label": placeholder || t("Ask anything... Use @ to mention pages"),
"aria-multiline": "true",
},
handleDOMEvents: {
keydown: (_view, event) => {
if (
@@ -279,8 +275,6 @@ export default function ChatInput({
type="file"
accept={ACCEPTED_FILE_TYPES}
multiple
aria-label={t("Add files")}
tabIndex={-1}
style={{ display: "none" }}
onChange={(e) => handleFileSelect(e.target.files)}
/>
@@ -31,16 +31,7 @@ export default function ChatToolGroup({ toolCalls, isStreaming }: Props) {
<div className={classes.toolGroup}>
<div
className={classes.toolGroupHeader}
role="button"
tabIndex={0}
aria-expanded={expanded}
onClick={() => setExpanded((prev) => !prev)}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
setExpanded((prev) => !prev);
}
}}
>
{activeLabel ? (
<IconLoader2 size={12} className={classes.processingSpinner} />
@@ -98,7 +98,7 @@
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--mantine-color-dimmed);
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
margin-bottom: var(--mantine-spacing-xs);
}
@@ -125,7 +125,7 @@
.suggestionsLabel {
font-size: var(--mantine-font-size-xs);
font-weight: 500;
color: var(--mantine-color-dimmed);
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--mantine-spacing-sm);
@@ -43,7 +43,7 @@
margin-top: 6px;
text-align: center;
font-size: var(--mantine-font-size-xs);
color: var(--mantine-color-dimmed);
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
}
.attachmentChips {
@@ -36,7 +36,7 @@
padding: 4px var(--mantine-spacing-xs);
font-size: var(--mantine-font-size-xs);
font-weight: 600;
color: var(--mantine-color-dimmed);
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
user-select: none;
}
@@ -104,7 +104,7 @@
.chatItemDate {
font-size: var(--mantine-font-size-xs);
color: var(--mantine-color-dimmed);
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
white-space: nowrap;
transition: opacity 150ms;
}
@@ -16,7 +16,7 @@
.subtitle {
font-size: var(--mantine-font-size-sm);
color: var(--mantine-color-dimmed);
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
text-align: center;
margin-top: 6px;
margin-bottom: var(--mantine-spacing-lg);
+3 -1
View File
@@ -63,6 +63,8 @@
"@nestjs/throttler": "^6.5.0",
"@nestjs/websockets": "^11.1.19",
"@node-saml/passport-saml": "^5.1.0",
"@react-email/components": "1.0.10",
"@react-email/render": "2.0.4",
"@socket.io/redis-adapter": "^8.3.0",
"ai": "^6.0.134",
"ai-sdk-ollama": "^3.8.1",
@@ -106,7 +108,6 @@
"postgres": "^3.4.8",
"postmark": "^4.0.7",
"react": "^18.3.1",
"react-email": "6.0.8",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2",
"sanitize-filename": "1.6.3",
@@ -145,6 +146,7 @@
"jest": "^30.3.0",
"kysely-codegen": "^0.20.0",
"prettier": "^3.8.1",
"react-email": "5.2.10",
"source-map-support": "^0.5.21",
"supertest": "^7.2.2",
"ts-jest": "^29.4.6",
+3 -3
View File
@@ -62,14 +62,14 @@ function applyMarkToYFragment(
) {
let pos = 0;
const processItem = (item: any, parentNodeName?: string): boolean => {
const processItem = (item: any): boolean => {
if (pos >= to) return false;
if (item instanceof Y.XmlText) {
const textLength = item.length;
const itemEnd = pos + textLength;
if (itemEnd > from && pos < to && parentNodeName !== 'codeBlock') {
if (itemEnd > from && pos < to) {
const formatFrom = Math.max(0, from - pos);
const formatTo = Math.min(textLength, to - pos);
const formatLength = formatTo - formatFrom;
@@ -82,7 +82,7 @@ function applyMarkToYFragment(
} else if (item instanceof Y.XmlElement) {
pos++; // Opening tag
for (let i = 0; i < item.length; i++) {
if (!processItem(item.get(i), item.nodeName)) return false;
if (!processItem(item.get(i))) return false;
}
pos++; // Closing tag
}
@@ -112,10 +112,7 @@ export class EnvironmentService {
}
getAwsS3ForcePathStyle(): boolean {
const forcePathStyle = this.configService
.get<string>('AWS_S3_FORCE_PATH_STYLE', 'false')
.toLowerCase();
return forcePathStyle === 'true';
return this.configService.get<boolean>('AWS_S3_FORCE_PATH_STYLE');
}
getAwsS3Url(): string {
@@ -6,7 +6,7 @@ import { EnvironmentService } from '../environment/environment.service';
import { InjectQueue } from '@nestjs/bullmq';
import { QueueName, QueueJob } from '../queue/constants';
import { Queue } from 'bullmq';
import { render } from 'react-email';
import { render } from '@react-email/render';
@Injectable()
export class MailService {
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Button, Link, Section, Text } from 'react-email';
import { Button, Link, Section, Text } from '@react-email/components';
import * as React from 'react';
import { button, content, paragraph } from '../css/styles';
import { MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Link, Section, Text } from 'react-email';
import { Link, Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, link, paragraph } from '../css/styles';
import { getGreetingName, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Link, Section, Text } from 'react-email';
import { Link, Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, link, paragraph } from '../css/styles';
import { EmailButton, getGreetingName, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -1,4 +1,4 @@
import { Section, Text } from 'react-email';
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
@@ -7,7 +7,7 @@ import {
Row,
Section,
Text,
} from 'react-email';
} from '@react-email/components';
import * as React from 'react';
interface MailBodyProps {
+587 -216
View File
File diff suppressed because it is too large Load Diff