Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef1679968d | |||
| a42c35dda0 | |||
| b2192b9594 | |||
| 75c649be39 |
+1
-5
@@ -6,14 +6,10 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- 🚸(frontend) hint min char search users #2064
|
||||
|
||||
### Changed
|
||||
|
||||
- 💄(frontend) improve comments highlights #1961
|
||||
- ♿️(frontend) structure correctly 5xx error alerts #2128
|
||||
- ♿️(frontend) fix empty heading before section titles in HTML export #2125
|
||||
|
||||
## [v4.8.3] - 2026-03-23
|
||||
|
||||
|
||||
@@ -17,41 +17,6 @@ test.describe('Document create member', () => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('it checks search hints', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'select-multi-users', browserName, 1);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const shareModal = page.getByLabel('Share the document');
|
||||
await expect(shareModal.getByText('Document owner')).toBeVisible();
|
||||
|
||||
const inputSearch = page.getByTestId('quick-search-input');
|
||||
await inputSearch.fill('u');
|
||||
await expect(shareModal.getByText('Document owner')).toBeHidden();
|
||||
await expect(
|
||||
shareModal.getByText('Type at least 3 characters to display user names'),
|
||||
).toBeVisible();
|
||||
await inputSearch.fill('user');
|
||||
await expect(
|
||||
shareModal.getByText('Type at least 3 characters to display user names'),
|
||||
).toBeHidden();
|
||||
await expect(shareModal.getByText('Choose a user')).toBeVisible();
|
||||
await inputSearch.fill('anything');
|
||||
await expect(shareModal.getByText('Choose a user')).toBeHidden();
|
||||
await expect(
|
||||
shareModal.getByText(
|
||||
'No results. Type a full email address to invite someone.',
|
||||
),
|
||||
).toBeVisible();
|
||||
await inputSearch.fill('anything@test.com');
|
||||
await expect(
|
||||
shareModal.getByText(
|
||||
'No results. Type a full email address to invite someone.',
|
||||
),
|
||||
).toBeHidden();
|
||||
await expect(shareModal.getByText('Choose the email')).toBeVisible();
|
||||
});
|
||||
|
||||
test('it selects 2 users and 1 invitation', async ({ page, browserName }) => {
|
||||
const inputFill = 'user.test';
|
||||
const responsePromise = page.waitForResponse(
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Box, Text, TextType } from '@/components';
|
||||
import { useHttpErrorMessages } from '@/hooks';
|
||||
|
||||
const AlertStyled = styled(Alert)`
|
||||
& .c__button--tertiary:hover {
|
||||
@@ -17,7 +16,6 @@ interface TextErrorsProps extends TextType {
|
||||
defaultMessage?: string;
|
||||
icon?: ReactNode;
|
||||
canClose?: boolean;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export const TextErrors = ({
|
||||
@@ -25,7 +23,6 @@ export const TextErrors = ({
|
||||
defaultMessage,
|
||||
icon,
|
||||
canClose = false,
|
||||
status,
|
||||
...textProps
|
||||
}: TextErrorsProps) => {
|
||||
return (
|
||||
@@ -38,7 +35,6 @@ export const TextErrors = ({
|
||||
<TextOnlyErrors
|
||||
causes={causes}
|
||||
defaultMessage={defaultMessage}
|
||||
status={status}
|
||||
{...textProps}
|
||||
/>
|
||||
</AlertStyled>
|
||||
@@ -48,39 +44,9 @@ export const TextErrors = ({
|
||||
export const TextOnlyErrors = ({
|
||||
causes,
|
||||
defaultMessage,
|
||||
status,
|
||||
...textProps
|
||||
}: TextErrorsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const httpError = useHttpErrorMessages(status);
|
||||
|
||||
if (httpError) {
|
||||
return (
|
||||
<Box $direction="column" $gap="0.2rem">
|
||||
<Text
|
||||
as="h1"
|
||||
$theme="error"
|
||||
$textAlign="center"
|
||||
$margin="0"
|
||||
$size="1rem"
|
||||
$weight="unset"
|
||||
{...textProps}
|
||||
>
|
||||
{httpError.title}
|
||||
</Text>
|
||||
<Text
|
||||
as="p"
|
||||
$theme="error"
|
||||
$textAlign="center"
|
||||
$margin="0"
|
||||
$size="0.875rem"
|
||||
{...textProps}
|
||||
>
|
||||
{httpError.detail}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box $direction="column" $gap="0.2rem">
|
||||
|
||||
@@ -48,7 +48,7 @@ export const QuickSearchInput = ({
|
||||
$direction="row"
|
||||
$align="center"
|
||||
className="quick-search-input"
|
||||
$gap={spacingsTokens['xxs']}
|
||||
$gap={spacingsTokens['2xs']}
|
||||
$padding={{ horizontal: 'base', vertical: 'xxs' }}
|
||||
>
|
||||
<Icon iconName="search" $variation="secondary" aria-hidden="true" />
|
||||
@@ -62,7 +62,6 @@ export const QuickSearchInput = ({
|
||||
placeholder={placeholder ?? t('Search')}
|
||||
onValueChange={onFilter}
|
||||
maxLength={254}
|
||||
minLength={6}
|
||||
data-testid="quick-search-input"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -18,15 +18,14 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
[cmdk-input] {
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
font-size: 17px;
|
||||
background: white;
|
||||
outline: none;
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
border-radius: var(--c--globals--spacings--0);
|
||||
font-family: var(--c--globals--font--families--base);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
color: var(--c--globals--colors--gray-500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
import { improveHtmlAccessibility } from '../utils_html';
|
||||
|
||||
const parse = (html: string): Document => {
|
||||
const parser = new DOMParser();
|
||||
return parser.parseFromString(html, 'text/html');
|
||||
};
|
||||
|
||||
const bodyHtml = (doc: Document) =>
|
||||
doc.body.innerHTML.replace(/\s+/g, ' ').trim();
|
||||
|
||||
describe('improveHtmlAccessibility', () => {
|
||||
// Headings
|
||||
|
||||
describe('headings', () => {
|
||||
it('converts heading blocks without inner h tag to semantic h1–h6', () => {
|
||||
const doc = parse(`
|
||||
<div data-content-type="heading" data-level="3"><span>Title</span></div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const h3 = doc.querySelector('h3');
|
||||
expect(h3).not.toBeNull();
|
||||
expect(h3!.textContent.trim()).toBe('Title');
|
||||
});
|
||||
|
||||
it('does not nest a second heading when BlockNote already outputs h1–h6', () => {
|
||||
const doc = parse(`
|
||||
<div data-content-type="heading" data-level="2">
|
||||
<h2 class="bn-inline-content">Section</h2>
|
||||
</div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const h2 = doc.querySelectorAll('h2');
|
||||
expect(h2).toHaveLength(1);
|
||||
expect(h2[0].textContent).toBe('Section');
|
||||
expect(h2[0].className).toBe('bn-inline-content');
|
||||
});
|
||||
|
||||
it('uses data-level when inner heading tag has a different level', () => {
|
||||
const doc = parse(`
|
||||
<div data-content-type="heading" data-level="3">
|
||||
<h2 class="bn-inline-content">Mismatch</h2>
|
||||
</div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
expect(doc.querySelector('h3')).not.toBeNull();
|
||||
expect(doc.querySelector('h2')).toBeNull();
|
||||
});
|
||||
|
||||
it('inserts an h1 with the document title when none exists', () => {
|
||||
const doc = parse(`<p>Content</p>`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'My Doc');
|
||||
|
||||
const h1 = doc.querySelector('h1');
|
||||
expect(h1).not.toBeNull();
|
||||
expect(h1!.id).toBe('doc-title');
|
||||
expect(h1!.textContent).toBe('My Doc');
|
||||
});
|
||||
|
||||
it('does not insert h1 when the document already has one', () => {
|
||||
const doc = parse(`
|
||||
<div data-content-type="heading" data-level="1"><span>Existing</span></div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Ignored');
|
||||
|
||||
expect(doc.querySelectorAll('h1')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
// Lists
|
||||
|
||||
describe('lists', () => {
|
||||
it('converts bullet list items to ul > li', () => {
|
||||
const doc = parse(`
|
||||
<div class="bn-block-group">
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="bulletListItem"><span>A</span></div>
|
||||
</div>
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="bulletListItem"><span>B</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const items = doc.querySelectorAll('ul > li');
|
||||
expect(items).toHaveLength(2);
|
||||
expect(items[0].textContent).toBe('A');
|
||||
expect(items[1].textContent).toBe('B');
|
||||
});
|
||||
|
||||
it('converts numbered list items to ol > li', () => {
|
||||
const doc = parse(`
|
||||
<div class="bn-block-group">
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="numberedListItem"><span>1st</span></div>
|
||||
</div>
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="numberedListItem"><span>2nd</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
expect(doc.querySelectorAll('ol > li')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('splits lists when a heading sits between them', () => {
|
||||
const doc = parse(`
|
||||
<div class="bn-block-group">
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="bulletListItem"><span>A</span></div>
|
||||
</div>
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="bulletListItem"><span>B</span></div>
|
||||
</div>
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="heading" data-level="2"><span>Next</span></div>
|
||||
</div>
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="bulletListItem"><span>C</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const uls = doc.querySelectorAll('ul');
|
||||
expect(uls).toHaveLength(2);
|
||||
expect(uls[0].querySelectorAll('li')).toHaveLength(2);
|
||||
expect(uls[1].querySelectorAll('li')).toHaveLength(1);
|
||||
|
||||
const html = bodyHtml(doc);
|
||||
expect(html.indexOf('<ul')).toBeLessThan(html.indexOf('<h2'));
|
||||
expect(html.indexOf('<h2')).toBeLessThan(
|
||||
html.indexOf('<ul', html.indexOf('<ul') + 1),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps consecutive same-type items in a single list', () => {
|
||||
const doc = parse(`
|
||||
<div class="bn-block-group">
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="bulletListItem"><span>A</span></div>
|
||||
</div>
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="bulletListItem"><span>B</span></div>
|
||||
</div>
|
||||
<div class="bn-block-outer">
|
||||
<div data-content-type="bulletListItem"><span>C</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
expect(doc.querySelectorAll('ul')).toHaveLength(1);
|
||||
expect(doc.querySelectorAll('ul > li')).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
// Quotes
|
||||
|
||||
it('converts quote blocks to blockquote', () => {
|
||||
const doc = parse(`
|
||||
<div data-content-type="quote"><span>Wise words</span></div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const bq = doc.querySelector('blockquote');
|
||||
expect(bq).not.toBeNull();
|
||||
expect(bq!.textContent).toBe('Wise words');
|
||||
});
|
||||
|
||||
// Callouts
|
||||
|
||||
it('converts callout blocks to aside with role="note"', () => {
|
||||
const doc = parse(`
|
||||
<div data-content-type="callout"><span>Note</span></div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const aside = doc.querySelector('aside');
|
||||
expect(aside).not.toBeNull();
|
||||
expect(aside!.getAttribute('role')).toBe('note');
|
||||
});
|
||||
|
||||
// Checklists
|
||||
|
||||
it('wraps check list items in a ul with role="list" and adds aria-checked', () => {
|
||||
const doc = parse(`
|
||||
<div>
|
||||
<div data-content-type="checkListItem">
|
||||
<input type="checkbox" />
|
||||
<span>Todo</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const ul = doc.querySelector('ul.checklist');
|
||||
expect(ul).not.toBeNull();
|
||||
expect(ul!.getAttribute('role')).toBe('list');
|
||||
expect(doc.querySelector('input')!.getAttribute('aria-checked')).toBe(
|
||||
'false',
|
||||
);
|
||||
});
|
||||
|
||||
// Code blocks
|
||||
|
||||
it('converts code blocks to pre > code and preserves data attributes', () => {
|
||||
const doc = parse(`
|
||||
<div data-content-type="codeBlock" class="hl-theme" data-language="js"><span>const x = 1;</span></div>
|
||||
`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const pre = doc.querySelector('pre');
|
||||
expect(pre).not.toBeNull();
|
||||
expect(pre!.className).toBe('hl-theme');
|
||||
expect(pre!.getAttribute('data-language')).toBe('js');
|
||||
expect(pre!.querySelector('code')!.textContent.trim()).toBe('const x = 1;');
|
||||
});
|
||||
|
||||
// Images
|
||||
|
||||
it('adds empty alt to images without one', () => {
|
||||
const doc = parse(`<img src="photo.png" />`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
expect(doc.querySelector('img')!.getAttribute('alt')).toBe('');
|
||||
});
|
||||
|
||||
it('does not overwrite an existing alt', () => {
|
||||
const doc = parse(`<img src="photo.png" alt="A photo" />`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
expect(doc.querySelector('img')!.getAttribute('alt')).toBe('A photo');
|
||||
});
|
||||
|
||||
// Article wrapper
|
||||
|
||||
it('wraps body in article with role="document"', () => {
|
||||
const doc = parse(`<p>Hello</p>`);
|
||||
|
||||
improveHtmlAccessibility(doc, 'Doc');
|
||||
|
||||
const article = doc.querySelector('article');
|
||||
expect(article).not.toBeNull();
|
||||
expect(article!.getAttribute('role')).toBe('document');
|
||||
expect(article!.getAttribute('aria-labelledby')).toBe('doc-title');
|
||||
});
|
||||
});
|
||||
@@ -145,8 +145,20 @@ export const improveHtmlAccessibility = (
|
||||
headingBlocks.forEach((block) => {
|
||||
const rawLevel = Number(block.getAttribute('data-level')) || 1;
|
||||
const level = Math.min(Math.max(rawLevel, 1), 6);
|
||||
const heading = parsedDocument.createElement(`h${level}`);
|
||||
moveChildNodes(block, heading);
|
||||
const tag = `h${level}`;
|
||||
|
||||
const existingHeading = block.querySelector('h1, h2, h3, h4, h5, h6');
|
||||
const heading = parsedDocument.createElement(tag);
|
||||
|
||||
if (existingHeading) {
|
||||
if (existingHeading.className) {
|
||||
heading.className = existingHeading.className;
|
||||
}
|
||||
moveChildNodes(existingHeading, heading);
|
||||
} else {
|
||||
moveChildNodes(block, heading);
|
||||
}
|
||||
|
||||
block.replaceWith(heading);
|
||||
});
|
||||
|
||||
@@ -178,10 +190,11 @@ export const improveHtmlAccessibility = (
|
||||
listItem: HTMLElement;
|
||||
contentType: string;
|
||||
level: number;
|
||||
blockOuterIndex: number;
|
||||
}
|
||||
|
||||
const listItemsInfo: ListItemInfo[] = [];
|
||||
allBlockOuters.forEach((blockOuter) => {
|
||||
allBlockOuters.forEach((blockOuter, index) => {
|
||||
const listItem = blockOuter.querySelector<HTMLElement>(listItemSelector);
|
||||
if (listItem) {
|
||||
const contentType = listItem.getAttribute('data-content-type');
|
||||
@@ -192,6 +205,7 @@ export const improveHtmlAccessibility = (
|
||||
listItem,
|
||||
contentType,
|
||||
level,
|
||||
blockOuterIndex: index,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -206,13 +220,19 @@ export const improveHtmlAccessibility = (
|
||||
const isBullet = contentType === 'bulletListItem';
|
||||
const listTag = isBullet ? 'ul' : 'ol';
|
||||
|
||||
// Check if previous item continues the same list (same type and level)
|
||||
// Check if previous item continues the same list (same type, level, and
|
||||
// no non-list block between them in the DOM : e.g. a heading separates lists).
|
||||
const previousInfo = idx > 0 ? listItemsInfo[idx - 1] : null;
|
||||
const isAdjacentBlock = previousInfo && info.blockOuterIndex === previousInfo.blockOuterIndex + 1;
|
||||
const continuesPreviousList =
|
||||
previousInfo &&
|
||||
isAdjacentBlock &&
|
||||
previousInfo.contentType === contentType &&
|
||||
previousInfo.level === level;
|
||||
|
||||
if (previousInfo && !isAdjacentBlock) {
|
||||
listStack.length = 0;
|
||||
}
|
||||
|
||||
// Find or create the appropriate list
|
||||
let targetList: HTMLElement | null = null;
|
||||
|
||||
|
||||
+1
-2
@@ -124,8 +124,7 @@ export const DocShareAddMemberList = ({
|
||||
$scope="surface"
|
||||
$theme="tertiary"
|
||||
$variation=""
|
||||
$border="1px solid var(--c--contextuals--border--surface--primary)"
|
||||
$margin={{ bottom: 'sm' }}
|
||||
$border="1px solid var(--c--contextuals--border--semantic--contextual--primary)"
|
||||
>
|
||||
<Box
|
||||
$direction="row"
|
||||
|
||||
@@ -289,7 +289,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
||||
/>
|
||||
)}
|
||||
{showMemberSection && isRootDoc && (
|
||||
<Box $padding={{ horizontal: 'base', top: 'base' }}>
|
||||
<Box $padding={{ horizontal: 'base' }}>
|
||||
<QuickSearchGroupAccessRequest doc={doc} />
|
||||
<QuickSearchGroupInvitation doc={doc} />
|
||||
<QuickSearchGroupMember doc={doc} />
|
||||
@@ -301,7 +301,6 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
||||
searchUsersRawData={searchUsersQuery.data}
|
||||
onSelect={onSelect}
|
||||
userQuery={userQuery}
|
||||
minLength={API_USERS_SEARCH_QUERY_MIN_LENGTH}
|
||||
/>
|
||||
)}
|
||||
</QuickSearch>
|
||||
@@ -322,35 +321,14 @@ interface QuickSearchInviteInputSectionProps {
|
||||
onSelect: (usr: User) => void;
|
||||
searchUsersRawData: User[] | undefined;
|
||||
userQuery: string;
|
||||
minLength: number;
|
||||
}
|
||||
|
||||
const QuickSearchInviteInputSection = ({
|
||||
onSelect,
|
||||
searchUsersRawData,
|
||||
userQuery,
|
||||
minLength,
|
||||
}: QuickSearchInviteInputSectionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const hint = useMemo(() => {
|
||||
if (userQuery.length < minLength) {
|
||||
return t('Type at least {{minLength}} characters to display user names', {
|
||||
minLength,
|
||||
});
|
||||
}
|
||||
if (isValidEmail(userQuery)) {
|
||||
return t('Choose the email');
|
||||
}
|
||||
if (!searchUsersRawData?.length) {
|
||||
return t('No results. Type a full email address to invite someone.');
|
||||
}
|
||||
|
||||
return t('Choose a user');
|
||||
}, [minLength, searchUsersRawData?.length, t, userQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
announce(hint, 'polite');
|
||||
}, [hint]);
|
||||
|
||||
const searchUserData: QuickSearchData<User> = useMemo(() => {
|
||||
const users = searchUsersRawData || [];
|
||||
@@ -369,7 +347,7 @@ const QuickSearchInviteInputSection = ({
|
||||
);
|
||||
|
||||
return {
|
||||
groupName: hint,
|
||||
groupName: t('Search user result'),
|
||||
elements: users,
|
||||
endActions:
|
||||
isEmail && !hasEmailInUsers
|
||||
@@ -381,12 +359,12 @@ const QuickSearchInviteInputSection = ({
|
||||
]
|
||||
: undefined,
|
||||
};
|
||||
}, [searchUsersRawData, userQuery, hint, onSelect]);
|
||||
}, [onSelect, searchUsersRawData, t, userQuery]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
aria-label={t('List search user result card')}
|
||||
$padding={{ horizontal: 'base', bottom: '3xs', top: 'base' }}
|
||||
$padding={{ horizontal: 'base', bottom: '3xs' }}
|
||||
>
|
||||
<QuickSearchGroup
|
||||
group={searchUserData}
|
||||
|
||||
-1
@@ -58,7 +58,6 @@ export const DocVersionEditor = ({
|
||||
<Box $margin="large" className="--docs--doc-version-editor-error">
|
||||
<TextErrors
|
||||
causes={error.cause}
|
||||
status={error.status}
|
||||
icon={
|
||||
error.status === 502 ? (
|
||||
<Text
|
||||
|
||||
@@ -62,7 +62,6 @@ const VersionListState = ({
|
||||
>
|
||||
<TextErrors
|
||||
causes={error.cause}
|
||||
status={error.status}
|
||||
icon={
|
||||
error.status === 502 ? (
|
||||
<Icon iconName="wifi_off" $theme="danger" />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './useClipboard';
|
||||
export * from './useCmdK';
|
||||
export * from './useDate';
|
||||
export * from './useHttpErrorMessages';
|
||||
export * from './useKeyboardAction';
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useHttpErrorMessages = (status?: number) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const messages: Record<number, { title: string; detail: string }> = {
|
||||
500: {
|
||||
title: t('500 - Internal Server Error'),
|
||||
detail: t('The server met an unexpected condition.'),
|
||||
},
|
||||
502: {
|
||||
title: t('502 - Bad Gateway'),
|
||||
detail: t(
|
||||
'The server received an invalid response. Please check your connection and try again.',
|
||||
),
|
||||
},
|
||||
503: {
|
||||
title: t('503 - Service Unavailable'),
|
||||
detail: t(
|
||||
'The service is temporarily unavailable. Please try again later.',
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
return status ? messages[status] : undefined;
|
||||
};
|
||||
@@ -216,7 +216,6 @@ const DocPage = ({ id }: DocProps) => {
|
||||
<Box $margin="large">
|
||||
<TextErrors
|
||||
causes={error.cause}
|
||||
status={error.status}
|
||||
icon={
|
||||
error.status === 502 ? (
|
||||
<Icon iconName="wifi_off" $theme="danger" $withThemeInherited />
|
||||
|
||||
Reference in New Issue
Block a user