Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ and this project adheres to
- 🐛(docs) run migration 0027 without superuser role
- 🐛(backend) prevent admins/owners from overwriting other users comments

### Changed

- ♿️(frontend) improve keyboard tab order in document list #2325

## [v5.1.0] - 2026-05-11

Expand All @@ -37,7 +40,7 @@ and this project adheres to
- 💬(frontend) add missing link in onboarding description #2233
- 🐛(frontend) sanitize pasted and dropped content in document title #2210
- 🐛(frontend) Emoji menu doesn't display above comment box #2229
- 🐛(frontend) Block menu doesn't stay open on 1st line #2229
- 🐛(frontend) Block menu doesn't stay open on 1st line #2229
- 🐛(frontend) The "+" on the first line of a new doc doesn't work #2229
- 🐛(backend) manage race condition between GET and PATCH content #2271
- 🐛(backend) replace document creation table locks with retry strategy #2274
Expand All @@ -46,8 +49,6 @@ and this project adheres to

- 🔒️(frontend) sanitize color during collaboration #2270



## [v5.0.0] - 2026-05-05

### Added
Expand Down Expand Up @@ -144,7 +145,6 @@ and this project adheres to
- 🐛(y-provider) destroy Y.Doc instances after each convert request #2129
- 🐛(backend) remove deleted sub documents in favorite_list endpoint #2083


## [v4.8.3] - 2026-03-23

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,7 @@ test.describe('Doc grid move', () => {
.click();

await expect(docsGrid.getByText(titleDoc1)).toBeHidden();
await docsGrid
.getByRole('link', { name: `Open document ${titleDoc2}` })
.click();
await docsGrid.getByRole('link', { name: new RegExp(titleDoc2) }).click();

await verifyDocName(page, titleDoc2);

Expand Down Expand Up @@ -383,9 +381,7 @@ test.describe('Doc grid move', () => {
await page.keyboard.press('Enter');

await expect(docsGrid.getByText(titleDoc1)).toBeHidden();
await docsGrid
.getByRole('link', { name: `Open document ${titleDoc2}` })
.click();
await docsGrid.getByRole('link', { name: new RegExp(titleDoc2) }).click();

await verifyDocName(page, titleDoc2);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ test.describe('Doc Trashbin', () => {
await page.getByRole('link', { name: 'All docs' }).click();
const row2Restored = await getGridRow(page, title2);
await expect(row2Restored.getByText(title2)).toBeVisible();
await row2Restored.getByRole('link', { name: /Open document/ }).click();
await row2Restored.getByRole('link', { name: new RegExp(title2) }).click();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Escape title2 before using it in RegExp.

new RegExp(title2) can misbehave for titles containing regex metacharacters, causing false positives/negatives in locator matching.

💡 Proposed fix
+    const escapedTitle2 = title2.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
-    await row2Restored.getByRole('link', { name: new RegExp(title2) }).click();
+    await row2Restored
+      .getByRole('link', { name: new RegExp(escapedTitle2) })
+      .click();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await row2Restored.getByRole('link', { name: new RegExp(title2) }).click();
const escapedTitle2 = title2.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
await row2Restored
.getByRole('link', { name: new RegExp(escapedTitle2) })
.click();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/apps/e2e/__tests__/app-impress/doc-trashbin.spec.ts` at line 87,
The locator uses new RegExp(title2) which breaks for titles containing regex
metacharacters; update the call that uses row2Restored.getByRole('link', { name:
new RegExp(title2) }) to pass an escaped literal pattern instead (e.g., escape
title2 with a utility that replaces all regex metacharacters before constructing
RegExp) or switch to an exact string matcher (pass the plain title2 as the name)
so matching is deterministic; target the row2Restored call and the title2 value
when applying the escape or changing the matcher.


await verifyDocName(page, title2);
await page.getByRole('button', { name: 'Back to homepage' }).click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,42 @@ import { DocsGridActions } from './DocsGridActions';
import { DocsGridItemSharedButton } from './DocsGridItemSharedButton';
import { DocsGridTrashbinActions } from './DocsGridTrashbinActions';

const useDateToDisplay = (doc: Doc, isInTrashbin: boolean) => {
const { data: config } = useConfig();
const { t } = useTranslation();
const { relativeDate, calculateDaysLeft } = useDate();

let dateToDisplay = relativeDate(doc.updated_at);

if (isInTrashbin && config?.TRASHBIN_CUTOFF_DAYS && doc.deleted_at) {
const daysLeft = calculateDaysLeft(
doc.deleted_at,
config.TRASHBIN_CUTOFF_DAYS,
);

dateToDisplay = `${daysLeft} ${t('days', { count: daysLeft })}`;
}

return dateToDisplay;
};

const useDocItemAriaLabel = (doc: Doc, isInTrashbin: boolean) => {
const { t } = useTranslation();
const { untitledDocument } = useTrans();
const dateToDisplay = useDateToDisplay(doc, isInTrashbin);
const title = doc.title || untitledDocument;
const participantCount = Math.max((doc.nb_accesses_direct ?? 1) - 1, 0);

return t(
'{{title}}, updated {{date}}, shared with {{count}} participant(s)',
{
title,
date: dateToDisplay,
count: participantCount,
},
);
};

type DocsGridItemProps = {
doc: Doc;
dragMode?: boolean;
Expand All @@ -26,13 +62,11 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
const searchParams = useSearchParams();
const target = searchParams.get('target');
const isInTrashbin = target === 'trashbin';
const { untitledDocument } = useTrans();

const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { flexLeft, flexRight } = useResponsiveDocGrid();
const { spacingsTokens } = useCunninghamTheme();
const dateToDisplay = useDateToDisplay(doc, isInTrashbin);
const docItemAriaLabel = useDocItemAriaLabel(doc, isInTrashbin);

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
Expand Down Expand Up @@ -60,9 +94,6 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
}
`}
className="--docs--doc-grid-item"
aria-label={t('Open document: {{title}}', {
title: doc.title || untitledDocument,
})}
>
<Box
$flex={flexLeft}
Expand All @@ -79,6 +110,7 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
`}
href={`/docs/${doc.id}`}
onKeyDown={handleKeyDown}
aria-label={docItemAriaLabel}
>
<DocsGridItemTitle doc={doc} withTooltip={!dragMode} />
</StyledLink>
Expand All @@ -91,20 +123,13 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
$justify={isDesktop ? 'space-between' : 'flex-end'}
$gap="32px"
>
<StyledLink
href={`/docs/${doc.id}`}
tabIndex={-1}
aria-label={t('{{title}}, updated {{date}}', {
title: doc.title || untitledDocument,
date: dateToDisplay,
})}
>
<Box aria-hidden="true">
<DocsGridItemDate
doc={doc}
isDesktop={isDesktop}
isInTrashbin={isInTrashbin}
/>
</StyledLink>
</Box>

<Box $direction="row" $align="center" $gap={spacingsTokens.lg}>
{isDesktop && (
Expand Down Expand Up @@ -202,25 +227,6 @@ const IconPublic = ({ isPublic }: { isPublic: boolean }) => {
);
};

const useDateToDisplay = (doc: Doc, isInTrashbin: boolean) => {
const { data: config } = useConfig();
const { t } = useTranslation();
const { relativeDate, calculateDaysLeft } = useDate();

let dateToDisplay = relativeDate(doc.updated_at);

if (isInTrashbin && config?.TRASHBIN_CUTOFF_DAYS && doc.deleted_at) {
const daysLeft = calculateDaysLeft(
doc.deleted_at,
config.TRASHBIN_CUTOFF_DAYS,
);

dateToDisplay = `${daysLeft} ${t('days', { count: daysLeft })}`;
}

return dateToDisplay;
};

export const DocsGridItemDate = ({
doc,
isDesktop,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const DocsGridItemSharedButton = ({ doc, disabled }: Props) => {
className="--docs--doc-grid-item-shared-button"
aria-label={t('Open the sharing settings for the document')}
data-testid={`docs-grid-item-shared-button-${doc.id}`}
tabIndex={-1}
style={{
padding: `0 var(--c--globals--spacings--xxxs) 0 var(--c--globals--spacings--xxxs)`,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const Draggable = <T,>(props: PropsWithChildren<DraggableProps<T>>) => {
ref={setNodeRef}
{...listeners}
{...attributes}
tabIndex={-1}
data-testid={`draggable-doc-${props.id}`}
className="--docs--grid-draggable"
role="none"
Expand Down
12 changes: 8 additions & 4 deletions src/frontend/apps/impress/src/i18n/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,8 @@
"{{count}} result(s) available_one": "{{count}} Ergebnis verfügbar",
"{{count}} result(s) available_other": "{{count}} Ergebnisse verfügbar",
"{{name}} added to invite list. Add more members or press Tab to select role and invite.": "{{name}} wurde zur Einladungsliste hinzugefügt. Füge mehr Mitglieder hinzu oder drücke Tab, um Rollen auszuwählen und einzuladen.",
"{{name}} removed from invite list": "{{name}} aus der Einladungsliste entfernt"
"{{name}} removed from invite list": "{{name}} aus der Einladungsliste entfernt",
"{{title}}, updated {{date}}, shared with {{count}} participant(s)": "{{title}}, aktualisiert {{date}}, geteilt mit {{count}} Teilnehmer(n)"
}
},
"el": {
Expand Down Expand Up @@ -1561,7 +1562,8 @@
"{{count}} result(s) available_other": "{{count}} résultat(s) disponible(s)",
"{{name}} added to invite list. Add more members or press Tab to select role and invite.": "{{name}} a été ajouté à la liste d'invitation. Ajoutez plus de membres ou appuyez sur Tab pour sélectionner le rôle et inviter.",
"{{name}} removed from invite list": "{{name}} a été retiré de la liste d'invitation",
"{{title}}, updated {{date}}": "{{title}}, mise à jour {{date}}"
"{{title}}, updated {{date}}": "{{title}}, mise à jour {{date}}",
"{{title}}, updated {{date}}, shared with {{count}} participant(s)": "{{title}}, mis à jour {{date}}, partagé avec {{count}} participant(s)"
}
},
"it": {
Expand Down Expand Up @@ -2434,7 +2436,8 @@
"{{count}} result(s) available_other": "Результатов: {{count}}",
"{{name}} added to invite list. Add more members or press Tab to select role and invite.": "{{name}} добавлен(а) в список приглашённых. Добавьте других участников или нажмите Tab, чтобы выбрать роль и пригласить.",
"{{name}} removed from invite list": "{{name}} удален(а) из списка приглашений",
"{{title}}, updated {{date}}": "{{title}}, обновлено {{date}}"
"{{title}}, updated {{date}}": "{{title}}, обновлено {{date}}",
"{{title}}, updated {{date}}, shared with {{count}} participant(s)": "{{title}}, обновлено {{date}}, доступ у {{count}} участник(ов)"
}
},
"sl": {
Expand Down Expand Up @@ -2933,7 +2936,8 @@
"{{count}} result(s) available_other": "Результатів: {{count}}",
"{{name}} added to invite list. Add more members or press Tab to select role and invite.": "{{name}} додано до списку запрошень. Додайте більше учасників або натисніть Tab, щоб вибрати роль та надіслати запрошення.",
"{{name}} removed from invite list": "{{name}} видалено зі списку запрошень",
"{{title}}, updated {{date}}": "{{title}}, оновлено {{date}}"
"{{title}}, updated {{date}}": "{{title}}, оновлено {{date}}",
"{{title}}, updated {{date}}, shared with {{count}} participant(s)": "{{title}}, оновлено {{date}}, доступ у {{count}} учасник(ів)"
}
},
"zh": {
Expand Down
Loading