Skip to content
Open
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ and this project adheres to

## [Unreleased]

### Added

- ✨(frontend) ajustable left panel #1456

### Changed

- ♻️(frontend) adapt custom blocks to new implementation #1375
- ♻️(backend) increase user short_name field length
- 🚸(frontend) separate viewers from editors #1509

### Fixed

- 🐛(frontend) fix duplicate document entries in grid #1479
- 🐛(frontend) show full nested doc names with ajustable bar #1456

## [3.8.2] - 2025-10-17

Expand Down
110 changes: 74 additions & 36 deletions src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
keyCloakSignIn,
verifyDocName,
} from './utils-common';
import { writeInEditor } from './utils-editor';
import { getEditor, writeInEditor } from './utils-editor';
import { addNewMember, connectOtherUserToDoc } from './utils-share';
import { createRootSubPage } from './utils-sub-pages';

Expand Down Expand Up @@ -182,15 +182,14 @@ test.describe('Doc Visibility: Restricted', () => {
});

test.describe('Doc Visibility: Public', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
});

test('It checks a public doc in read only mode', async ({
page,
browserName,
}) => {
await page.goto('/');
await keyCloakSignIn(page, browserName);

const [docTitle] = await createDoc(
page,
'Public read only',
Expand All @@ -200,6 +199,8 @@ test.describe('Doc Visibility: Public', () => {

await verifyDocName(page, docTitle);

await writeInEditor({ page, text: 'Hello Public Viewonly' });

await page.getByRole('button', { name: 'Share' }).click();
const selectVisibility = page.getByTestId('doc-visibility');
await selectVisibility.click();
Expand Down Expand Up @@ -241,49 +242,59 @@ test.describe('Doc Visibility: Public', () => {
await expect(page.getByTestId('search-docs-button')).toBeVisible();
await expect(page.getByTestId('new-doc-button')).toBeVisible();

const urlDoc = page.url();

await page
.getByRole('button', {
name: 'Logout',
})
.click();

await expectLoginPage(page);
const docUrl = page.url();

await page.goto(urlDoc);
const { otherPage, cleanup } = await connectOtherUserToDoc({
browserName,
docUrl,
withoutSignIn: true,
});

await expect(page.locator('h2').getByText(docTitle)).toBeVisible();
await expect(page.getByTestId('search-docs-button')).toBeHidden();
await expect(page.getByTestId('new-doc-button')).toBeHidden();
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
const card = page.getByLabel('It is the card information');
await expect(otherPage.locator('h2').getByText(docTitle)).toBeVisible();
await expect(otherPage.getByTestId('search-docs-button')).toBeHidden();
await expect(otherPage.getByTestId('new-doc-button')).toBeHidden();
await expect(
otherPage.getByRole('button', { name: 'Share' }),
).toBeVisible();
const card = otherPage.getByLabel('It is the card information');
await expect(card).toBeVisible();
await expect(card.getByText('Reader')).toBeVisible();

await page.getByRole('button', { name: 'Share' }).click();
const otherEditor = await getEditor({ page: otherPage });
await expect(otherEditor).toHaveAttribute('contenteditable', 'false');
await expect(otherEditor.getByText('Hello Public Viewonly')).toBeVisible();

// Cursor and selection of the anonymous user are not visible
await otherEditor.getByText('Hello Public').selectText();
await expect(
page.getByText(
page.locator('.collaboration-cursor-custom__base'),
).toBeHidden();
await expect(page.locator('.ProseMirror-yjs-selection')).toBeHidden();

await otherPage.getByRole('button', { name: 'Share' }).click();
await expect(
otherPage.getByText(
'You can view this document but need additional access to see its members or modify settings.',
),
).toBeVisible();

await expect(
page.getByRole('button', { name: 'Request access' }),
otherPage.getByRole('button', { name: 'Request access' }),
).toBeHidden();

await cleanup();
});

test('It checks a public doc in editable mode', async ({
page,
browserName,
}) => {
await page.goto('/');
await keyCloakSignIn(page, browserName);

const [docTitle] = await createDoc(page, 'Public editable', browserName, 1);

await verifyDocName(page, docTitle);

await writeInEditor({ page, text: 'Hello Public Editable' });

await page.getByRole('button', { name: 'Share' }).click();
const selectVisibility = page.getByTestId('doc-visibility');
await selectVisibility.click();
Expand Down Expand Up @@ -317,20 +328,47 @@ test.describe('Doc Visibility: Public', () => {
cardContainer.getByText('Public document', { exact: true }),
).toBeVisible();

const urlDoc = page.url();
const docUrl = page.url();

await page
.getByRole('button', {
name: 'Logout',
})
.click();
const { otherPage, cleanup } = await connectOtherUserToDoc({
browserName,
docUrl,
withoutSignIn: true,
docTitle,
});

await expectLoginPage(page);
await expect(otherPage.getByTestId('search-docs-button')).toBeHidden();
await expect(otherPage.getByTestId('new-doc-button')).toBeHidden();

await page.goto(urlDoc);
const otherEditor = await getEditor({ page: otherPage });
await expect(otherEditor).toHaveAttribute('contenteditable', 'true');
await expect(otherEditor.getByText('Hello Public Editable')).toBeVisible();

await verifyDocName(page, docTitle);
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
// We can see the collaboration cursor of the anonymous user
await otherEditor.getByText('Hello Public').selectText();
await expect(
page.locator('.collaboration-cursor-custom__base').getByText('Anonymous'),
).toBeVisible();

await expect(
otherPage.getByRole('button', { name: 'Share' }),
).toBeVisible();
const card = otherPage.getByLabel('It is the card information');
await expect(card).toBeVisible();
await expect(card.getByText('Editor')).toBeVisible();

await otherPage.getByRole('button', { name: 'Share' }).click();
await expect(
otherPage.getByText(
'You can view this document but need additional access to see its members or modify settings.',
),
).toBeVisible();

await expect(
otherPage.getByRole('button', { name: 'Request access' }),
).toBeHidden();

await cleanup();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ export const expectLoginPage = async (page: Page) =>
).toBeVisible({
timeout: 10000,
});

// language helper
export const TestLanguage = {
English: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ import { useTranslation } from 'react-i18next';
import * as Y from 'yjs';

import { Box, TextErrors } from '@/components';
import {
Doc,
useIsCollaborativeEditable,
useProviderStore,
} from '@/docs/doc-management';
import { Doc, useProviderStore } from '@/docs/doc-management';
import { useAuth } from '@/features/auth';

import {
Expand All @@ -32,7 +28,6 @@ import {
useUploadStatus,
} from '../hook';
import { useEditorStore } from '../stores';
import { cssEditor } from '../styles';
import { DocsBlockNoteEditor } from '../types';
import { randomColor } from '../utils';

Expand Down Expand Up @@ -85,25 +80,19 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const { t } = useTranslation();
const { isSynced: isConnectedToCollabServer } = useProviderStore();

const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
const readOnly = !doc.abilities.partial_update || !isEditable || isLoading;
const isDeletedDoc = !!doc.deleted_at;

useSaveDoc(doc.id, provider.document, !readOnly, isConnectedToCollabServer);
useSaveDoc(doc.id, provider.document, isConnectedToCollabServer);
const { i18n } = useTranslation();
const lang = i18n.resolvedLanguage;

const { uploadFile, errorAttachment } = useUploadFile(doc.id);

const collabName = readOnly
? 'Reader'
: user?.full_name || user?.email || t('Anonymous');
const collabName = user?.full_name || user?.email || t('Anonymous');
const showCursorLabels: 'always' | 'activity' | (string & {}) = 'activity';

const editor: DocsBlockNoteEditor = useCreateBlockNote(
{
collaboration: {
provider,
provider: provider,
fragment: provider.document.getXmlFragment('document-store'),
user: {
name: collabName,
Expand All @@ -117,10 +106,6 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
renderCursor: (user: { color: string; name: string }) => {
const cursorElement = document.createElement('span');

if (user.name === 'Reader') {
return cursorElement;
}

cursorElement.classList.add('collaboration-cursor-custom__base');
const caretElement = document.createElement('span');
caretElement.classList.add('collaboration-cursor-custom__caret');
Expand Down Expand Up @@ -181,12 +166,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
}, [setEditor, editor]);

return (
<Box
$padding={{ top: 'md' }}
$background="white"
$css={cssEditor(readOnly, isDeletedDoc)}
className="--docs--editor-container"
>
<>
{errorAttachment && (
<Box $margin={{ bottom: 'big', top: 'none', horizontal: 'large' }}>
<TextErrors
Expand All @@ -201,24 +181,21 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
editor={editor}
formattingToolbar={false}
slashMenu={false}
editable={!readOnly}
theme="light"
>
<BlockNoteSuggestionMenu />
<BlockNoteToolbar />
</BlockNoteView>
</Box>
</>
);
};

interface BlockNoteEditorVersionProps {
interface BlockNoteReaderProps {
initialContent: Y.XmlFragment;
}

export const BlockNoteEditorVersion = ({
initialContent,
}: BlockNoteEditorVersionProps) => {
const readOnly = true;
export const BlockNoteReader = ({ initialContent }: BlockNoteReaderProps) => {
const { setEditor } = useEditorStore();
const editor = useCreateBlockNote(
{
collaboration: {
Expand All @@ -234,9 +211,23 @@ export const BlockNoteEditorVersion = ({
[initialContent],
);

useEffect(() => {
setEditor(editor);

return () => {
setEditor(undefined);
};
}, [setEditor, editor]);

useHeadings(editor);

return (
<Box $css={cssEditor(readOnly, true)} className="--docs--editor-container">
<BlockNoteView editor={editor} editable={!readOnly} theme="light" />
</Box>
<BlockNoteView
editor={editor}
editable={false}
theme="light"
formattingToolbar={false}
slashMenu={false}
/>
);
};
Loading
Loading