✏️(frontend) change all occurences of pad to doc

The initial doc was named pad in reference to
the "NotePad de l'Etat", but we renamed it
doc to clearly dissociate the 2 sites.
Doc is also a small reference of google doc.
This commit is contained in:
Anthony LC
2024-06-25 14:49:53 +02:00
committed by Anthony LC
parent 5b3c91f6dd
commit 5454da5c70
102 changed files with 563 additions and 561 deletions

View File

@@ -32,6 +32,8 @@ and this project adheres to
- ⚡️(e2e) unique login between tests (#80) - ⚡️(e2e) unique login between tests (#80)
- ⚡️(CI) improve e2e job (#86) - ⚡️(CI) improve e2e job (#86)
- ♻️(frontend) improve the error and message info ui (#93) - ♻️(frontend) improve the error and message info ui (#93)
- ♻️(frontend) improve the error and message info ui (#93)
- ✏️(frontend) change all occurences of pad to doc (#99)
## Fixed ## Fixed

View File

@@ -26,9 +26,9 @@ export const randomName = (name: string, browserName: string, length: number) =>
return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`; return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`;
}); });
export const createPad = async ( export const createDoc = async (
page: Page, page: Page,
padName: string, docName: string,
browserName: string, browserName: string,
length: number, length: number,
isPublic: boolean = false, isPublic: boolean = false,
@@ -38,11 +38,11 @@ export const createPad = async (
name: 'Create the document', name: 'Create the document',
}); });
const randomPads = randomName(padName, browserName, length); const randomDocs = randomName(docName, browserName, length);
for (let i = 0; i < randomPads.length; i++) { for (let i = 0; i < randomDocs.length; i++) {
await panel.getByRole('button', { name: 'Add a document' }).click(); await panel.getByRole('button', { name: 'Add a document' }).click();
await page.getByText('Document name').fill(randomPads[i]); await page.getByText('Document name').fill(randomDocs[i]);
if (isPublic) { if (isPublic) {
await page.getByText('Is it public ?').click(); await page.getByText('Is it public ?').click();
@@ -50,10 +50,10 @@ export const createPad = async (
await expect(buttonCreate).toBeEnabled(); await expect(buttonCreate).toBeEnabled();
await buttonCreate.click(); await buttonCreate.click();
await expect(panel.locator('li').getByText(randomPads[i])).toBeVisible(); await expect(panel.locator('li').getByText(randomDocs[i])).toBeVisible();
} }
return randomPads; return randomDocs;
}; };
export const createTemplate = async ( export const createTemplate = async (

View File

@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { createPad, randomName } from './common'; import { createDoc, randomName } from './common';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('/'); await page.goto('/');
@@ -12,7 +12,7 @@ test.describe('Document add users', () => {
(response) => (response) =>
response.url().includes('/users/?q=user') && response.status() === 200, response.url().includes('/users/?q=user') && response.status() === 200,
); );
await createPad(page, 'select-multi-users', browserName, 1); await createDoc(page, 'select-multi-users', browserName, 1);
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Add members' }).click(); await page.getByRole('button', { name: 'Add members' }).click();
@@ -73,7 +73,7 @@ test.describe('Document add users', () => {
response.url().includes('/users/?q=user') && response.status() === 200, response.url().includes('/users/?q=user') && response.status() === 200,
); );
await createPad(page, 'user-invitation', browserName, 1); await createDoc(page, 'user-invitation', browserName, 1);
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Add members' }).click(); await page.getByRole('button', { name: 'Add members' }).click();
@@ -125,7 +125,7 @@ test.describe('Document add users', () => {
response.url().includes('/users/?q=user') && response.status() === 200, response.url().includes('/users/?q=user') && response.status() === 200,
); );
await createPad(page, 'user-twice', browserName, 1); await createDoc(page, 'user-twice', browserName, 1);
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Add members' }).click(); await page.getByRole('button', { name: 'Add members' }).click();
@@ -165,7 +165,7 @@ test.describe('Document add users', () => {
page, page,
browserName, browserName,
}) => { }) => {
await createPad(page, 'invitation-twice', browserName, 1); await createDoc(page, 'invitation-twice', browserName, 1);
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Add members' }).click(); await page.getByRole('button', { name: 'Add members' }).click();

View File

@@ -4,8 +4,8 @@ test.beforeEach(async ({ page }) => {
await page.goto('/'); await page.goto('/');
}); });
test.describe('Pad Create', () => { test.describe('Doc Create', () => {
test('checks all the create pad elements are visible', async ({ page }) => { test('checks all the create doc elements are visible', async ({ page }) => {
const buttonCreateHomepage = page.getByRole('button', { const buttonCreateHomepage = page.getByRole('button', {
name: 'Create a new document', name: 'Create a new document',
}); });
@@ -58,7 +58,7 @@ test.describe('Pad Create', () => {
await expect(buttonCreateHomepage).toBeVisible(); await expect(buttonCreateHomepage).toBeVisible();
}); });
test('checks the routing on new pad created', async ({ test('checks the routing on new doc created', async ({
page, page,
browserName, browserName,
}) => { }) => {
@@ -66,21 +66,21 @@ test.describe('Pad Create', () => {
await panel.getByRole('button', { name: 'Add a document' }).click(); await panel.getByRole('button', { name: 'Add a document' }).click();
const padName = `My routing pad ${browserName}-${Math.floor(Math.random() * 1000)}`; const docName = `My routing doc ${browserName}-${Math.floor(Math.random() * 1000)}`;
await page.getByText('Document name').fill(padName); await page.getByText('Document name').fill(docName);
await page.getByRole('button', { name: 'Create the document' }).click(); await page.getByRole('button', { name: 'Create the document' }).click();
const elPad = page.locator('h2').getByText(padName); const elDoc = page.locator('h2').getByText(docName);
await expect(elPad).toBeVisible(); await expect(elDoc).toBeVisible();
await panel.getByRole('button', { name: 'Add a document' }).click(); await panel.getByRole('button', { name: 'Add a document' }).click();
await expect(elPad).toBeHidden(); await expect(elDoc).toBeHidden();
await panel.locator('li').getByText(padName).click(); await panel.locator('li').getByText(docName).click();
await expect(elPad).toBeVisible(); await expect(elDoc).toBeVisible();
}); });
test('checks alias pads url with homepage', async ({ page }) => { test('checks alias docs url with homepage', async ({ page }) => {
await expect(page).toHaveURL('/'); await expect(page).toHaveURL('/');
const buttonCreateHomepage = page.getByRole('button', { const buttonCreateHomepage = page.getByRole('button', {
@@ -98,7 +98,7 @@ test.describe('Pad Create', () => {
// eslint-disable-next-line playwright/no-wait-for-timeout // eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(300); await page.waitForTimeout(300);
await page.goto('/docs/some-unknown-pad'); await page.goto('/docs/some-unknown-doc');
await expect( await expect(
page.getByText( page.getByText(
'It seems that the page you are looking for does not exist or cannot be displayed correctly.', 'It seems that the page you are looking for does not exist or cannot be displayed correctly.',
@@ -108,8 +108,8 @@ test.describe('Pad Create', () => {
}); });
}); });
test('checks that the pad is public', async ({ page, browserName }) => { test('checks that the doc is public', async ({ page, browserName }) => {
const responsePromisePad = page.waitForResponse( const responsePromiseDoc = page.waitForResponse(
(response) => (response) =>
response.url().includes('/documents/') && response.status() === 201, response.url().includes('/documents/') && response.status() === 201,
); );
@@ -118,13 +118,13 @@ test.describe('Pad Create', () => {
await panel.getByRole('button', { name: 'Add a document' }).click(); await panel.getByRole('button', { name: 'Add a document' }).click();
const padName = `My routing pad ${browserName}-${Math.floor(Math.random() * 1000)}`; const docName = `My routing doc ${browserName}-${Math.floor(Math.random() * 1000)}`;
await page.getByText('Document name').fill(padName); await page.getByText('Document name').fill(docName);
await page.getByText('Is it public ?').click(); await page.getByText('Is it public ?').click();
await page.getByRole('button', { name: 'Create the document' }).click(); await page.getByRole('button', { name: 'Create the document' }).click();
const responsePad = await responsePromisePad; const responseDoc = await responsePromiseDoc;
const is_public = (await responsePad.json()).is_public; const is_public = (await responseDoc.json()).is_public;
expect(is_public).toBeTruthy(); expect(is_public).toBeTruthy();
}); });
}); });

View File

@@ -1,26 +1,26 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { createPad } from './common'; import { createDoc } from './common';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('/'); await page.goto('/');
}); });
test.describe('Pad Editor', () => { test.describe('Doc Editor', () => {
test('checks the Pad Editor interact correctly', async ({ test('checks the Doc Editor interact correctly', async ({
page, page,
browserName, browserName,
}) => { }) => {
const randomPad = await createPad(page, 'pad-editor', browserName, 1); const randomDoc = await createDoc(page, 'doc-editor', browserName, 1);
await expect(page.locator('h2').getByText(randomPad[0])).toBeVisible(); await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click(); await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World'); await page.locator('.ProseMirror.bn-editor').fill('Hello World');
await expect(page.getByText('Hello World')).toBeVisible(); await expect(page.getByText('Hello World')).toBeVisible();
}); });
test('checks the Pad is connected to the webrtc server', async ({ test('checks the Doc is connected to the webrtc server', async ({
page, page,
browserName, browserName,
}) => { }) => {
@@ -28,8 +28,8 @@ test.describe('Pad Editor', () => {
return webSocket.url().includes('ws://localhost:4444/'); return webSocket.url().includes('ws://localhost:4444/');
}); });
const randomPad = await createPad(page, 'pad-editor', browserName, 1); const randomDoc = await createDoc(page, 'doc-editor', browserName, 1);
await expect(page.locator('h2').getByText(randomPad[0])).toBeVisible(); await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible();
const webSocket = await webSocketPromise; const webSocket = await webSocketPromise;
expect(webSocket.url()).toContain('ws://localhost:4444/'); expect(webSocket.url()).toContain('ws://localhost:4444/');
@@ -52,9 +52,9 @@ test.describe('Pad Editor', () => {
page, page,
browserName, browserName,
}) => { }) => {
const randomPad = await createPad(page, 'pad-markdown', browserName, 1); const randomDoc = await createDoc(page, 'doc-markdown', browserName, 1);
await expect(page.locator('h2').getByText(randomPad[0])).toBeVisible(); await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click(); await page.locator('.ProseMirror.bn-editor').click();
await page await page
@@ -74,57 +74,57 @@ test.describe('Pad Editor', () => {
).toHaveAttribute('href', 'http://test-markdown.html'); ).toHaveAttribute('href', 'http://test-markdown.html');
}); });
test('it renders correctly when we switch from one pad to another', async ({ test('it renders correctly when we switch from one doc to another', async ({
page, page,
browserName, browserName,
}) => { }) => {
const [firstPad, secondPad] = await createPad( const [firstDoc, secondDoc] = await createDoc(
page, page,
'pad-multiple', 'doc-multiple',
browserName, browserName,
2, 2,
); );
const panel = page.getByLabel('Documents panel').first(); const panel = page.getByLabel('Documents panel').first();
// Check the first pad // Check the first doc
await panel.getByText(firstPad).click(); await panel.getByText(firstDoc).click();
await expect(page.locator('h2').getByText(firstPad)).toBeVisible(); await expect(page.locator('h2').getByText(firstDoc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click(); await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World Pad 1'); await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 1');
await expect(page.getByText('Hello World Pad 1')).toBeVisible(); await expect(page.getByText('Hello World Doc 1')).toBeVisible();
// Check the second pad // Check the second doc
await panel.getByText(secondPad).click(); await panel.getByText(secondDoc).click();
await expect(page.locator('h2').getByText(secondPad)).toBeVisible(); await expect(page.locator('h2').getByText(secondDoc)).toBeVisible();
await expect(page.getByText('Hello World Pad 1')).toBeHidden(); await expect(page.getByText('Hello World Doc 1')).toBeHidden();
await page.locator('.ProseMirror.bn-editor').click(); await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World Pad 2'); await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 2');
await expect(page.getByText('Hello World Pad 2')).toBeVisible(); await expect(page.getByText('Hello World Doc 2')).toBeVisible();
// Check the first pad again // Check the first doc again
await panel.getByText(firstPad).click(); await panel.getByText(firstDoc).click();
await expect(page.locator('h2').getByText(firstPad)).toBeVisible(); await expect(page.locator('h2').getByText(firstDoc)).toBeVisible();
await expect(page.getByText('Hello World Pad 2')).toBeHidden(); await expect(page.getByText('Hello World Doc 2')).toBeHidden();
await expect(page.getByText('Hello World Pad 1')).toBeVisible(); await expect(page.getByText('Hello World Doc 1')).toBeVisible();
}); });
test('it saves the doc when we change pages', async ({ test('it saves the doc when we change pages', async ({
page, page,
browserName, browserName,
}) => { }) => {
const [pad] = await createPad(page, 'pad-save-page', browserName, 1); const [doc] = await createDoc(page, 'doc-save-page', browserName, 1);
const panel = page.getByLabel('Documents panel').first(); const panel = page.getByLabel('Documents panel').first();
// Check the first pad // Check the first doc
await panel.getByText(pad).click(); await panel.getByText(doc).click();
await expect(page.locator('h2').getByText(pad)).toBeVisible(); await expect(page.locator('h2').getByText(doc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click(); await page.locator('.ProseMirror.bn-editor').click();
await page await page
.locator('.ProseMirror.bn-editor') .locator('.ProseMirror.bn-editor')
.fill('Hello World Pad persisted 1'); .fill('Hello World Doc persisted 1');
await expect(page.getByText('Hello World Pad persisted 1')).toBeVisible(); await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible();
await panel await panel
.getByRole('button', { .getByRole('button', {
@@ -142,33 +142,33 @@ test.describe('Pad Editor', () => {
await page.goto('/'); await page.goto('/');
await panel.getByText(pad).click(); await panel.getByText(doc).click();
await expect(page.getByText('Hello World Pad persisted 1')).toBeVisible(); await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible();
}); });
test('it saves the doc when we quit pages', async ({ page, browserName }) => { test('it saves the doc when we quit pages', async ({ page, browserName }) => {
// eslint-disable-next-line playwright/no-skipped-test // eslint-disable-next-line playwright/no-skipped-test
test.skip(browserName === 'webkit', 'This test is very flaky with webkit'); test.skip(browserName === 'webkit', 'This test is very flaky with webkit');
const [pad] = await createPad(page, 'pad-save-quit', browserName, 1); const [doc] = await createDoc(page, 'doc-save-quit', browserName, 1);
const panel = page.getByLabel('Documents panel').first(); const panel = page.getByLabel('Documents panel').first();
// Check the first pad // Check the first doc
await panel.getByText(pad).click(); await panel.getByText(doc).click();
await expect(page.locator('h2').getByText(pad)).toBeVisible(); await expect(page.locator('h2').getByText(doc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click(); await page.locator('.ProseMirror.bn-editor').click();
await page await page
.locator('.ProseMirror.bn-editor') .locator('.ProseMirror.bn-editor')
.fill('Hello World Pad persisted 2'); .fill('Hello World Doc persisted 2');
await expect(page.getByText('Hello World Pad persisted 2')).toBeVisible(); await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible();
await page.goto('/'); await page.goto('/');
await panel.getByText(pad).click(); await panel.getByText(doc).click();
await expect(page.getByText('Hello World Pad persisted 2')).toBeVisible(); await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible();
}); });
test('it cannot edit if viewer', async ({ page, browserName }) => { test('it cannot edit if viewer', async ({ page, browserName }) => {
@@ -202,7 +202,7 @@ test.describe('Pad Editor', () => {
} }
}); });
await createPad(page, 'pad-right-edit', browserName, 1); await createDoc(page, 'doc-right-edit', browserName, 1);
await expect( await expect(
page.getByText( page.getByText(

View File

@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { addNewMember, createPad } from './common'; import { addNewMember, createDoc } from './common';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('/'); await page.goto('/');
@@ -11,7 +11,7 @@ test.describe('Members Delete', () => {
page, page,
browserName, browserName,
}) => { }) => {
await createPad(page, 'member-delete-1', browserName, 1); await createDoc(page, 'member-delete-1', browserName, 1);
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Manage members' }).click(); await page.getByRole('button', { name: 'Manage members' }).click();
@@ -37,7 +37,7 @@ test.describe('Members Delete', () => {
page, page,
browserName, browserName,
}) => { }) => {
await createPad(page, 'member-delete-2', browserName, 1); await createDoc(page, 'member-delete-2', browserName, 1);
await addNewMember(page, 0, 'Owner'); await addNewMember(page, 0, 'Owner');
@@ -64,7 +64,7 @@ test.describe('Members Delete', () => {
}); });
test('it cannot delete owner member', async ({ page, browserName }) => { test('it cannot delete owner member', async ({ page, browserName }) => {
await createPad(page, 'member-delete-3', browserName, 1); await createDoc(page, 'member-delete-3', browserName, 1);
const username = await addNewMember(page, 0, 'Owner'); const username = await addNewMember(page, 0, 'Owner');
@@ -88,7 +88,7 @@ test.describe('Members Delete', () => {
}); });
test('it deletes admin member', async ({ page, browserName }) => { test('it deletes admin member', async ({ page, browserName }) => {
await createPad(page, 'member-delete-4', browserName, 1); await createDoc(page, 'member-delete-4', browserName, 1);
const username = await addNewMember(page, 0, 'Admin'); const username = await addNewMember(page, 0, 'Admin');
@@ -116,7 +116,7 @@ test.describe('Members Delete', () => {
page, page,
browserName, browserName,
}) => { }) => {
await createPad(page, 'member-delete-5', browserName, 1); await createDoc(page, 'member-delete-5', browserName, 1);
const username = await addNewMember(page, 0, 'Owner'); const username = await addNewMember(page, 0, 'Owner');
@@ -146,7 +146,7 @@ test.describe('Members Delete', () => {
}); });
test('it deletes admin member when admin', async ({ page, browserName }) => { test('it deletes admin member when admin', async ({ page, browserName }) => {
await createPad(page, 'member-delete-6', browserName, 1); await createDoc(page, 'member-delete-6', browserName, 1);
// To not be the only owner // To not be the only owner
await addNewMember(page, 0, 'Owner'); await addNewMember(page, 0, 'Owner');

View File

@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { createPad } from './common'; import { createDoc } from './common';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('/'); await page.goto('/');
@@ -8,7 +8,7 @@ test.beforeEach(async ({ page }) => {
test.describe('Document grid members', () => { test.describe('Document grid members', () => {
test('it display the grid', async ({ page, browserName }) => { test('it display the grid', async ({ page, browserName }) => {
await createPad(page, 'grid-display', browserName, 1); await createDoc(page, 'grid-display', browserName, 1);
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Manage members' }).click(); await page.getByRole('button', { name: 'Manage members' }).click();
@@ -91,7 +91,7 @@ test.describe('Document grid members', () => {
}, },
); );
await createPad(page, 'grid-no-member', browserName, 1); await createDoc(page, 'grid-no-member', browserName, 1);
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Manage members' }).click(); await page.getByRole('button', { name: 'Manage members' }).click();

View File

@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
import { waitForElementCount } from '../helpers'; import { waitForElementCount } from '../helpers';
import { createPad } from './common'; import { createDoc } from './common';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('/'); await page.goto('/');
@@ -146,16 +146,16 @@ test.describe('Documents Panel', () => {
test('checks the hover and selected state', async ({ page, browserName }) => { test('checks the hover and selected state', async ({ page, browserName }) => {
const panel = page.getByLabel('Documents panel').first(); const panel = page.getByLabel('Documents panel').first();
await createPad(page, 'pad-hover', browserName, 2); await createDoc(page, 'doc-hover', browserName, 2);
const selectedPad = panel.locator('li').nth(0); const selectedDoc = panel.locator('li').nth(0);
await expect(selectedPad).toHaveCSS( await expect(selectedDoc).toHaveCSS(
'background-color', 'background-color',
'rgb(202, 202, 251)', 'rgb(202, 202, 251)',
); );
const hoverPad = panel.locator('li').nth(1); const hoverDoc = panel.locator('li').nth(1);
await hoverPad.hover(); await hoverDoc.hover();
await expect(hoverPad).toHaveCSS('background-color', 'rgb(227, 227, 253)'); await expect(hoverDoc).toHaveCSS('background-color', 'rgb(227, 227, 253)');
}); });
}); });

View File

@@ -2,24 +2,24 @@ import { expect, test } from '@playwright/test';
import cs from 'convert-stream'; import cs from 'convert-stream';
import pdf from 'pdf-parse'; import pdf from 'pdf-parse';
import { createPad } from './common'; import { createDoc } from './common';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('/'); await page.goto('/');
}); });
test.describe('Pad Tools', () => { test.describe('Doc Tools', () => {
test('it converts the pad to pdf with a template integrated', async ({ test('it converts the doc to pdf with a template integrated', async ({
page, page,
browserName, browserName,
}) => { }) => {
const [randomPad] = await createPad(page, 'pad-editor', browserName, 1); const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
const downloadPromise = page.waitForEvent('download', (download) => { const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomPad}.pdf`); return download.suggestedFilename().includes(`${randomDoc}.pdf`);
}); });
await expect(page.locator('h2').getByText(randomPad)).toBeVisible(); await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click(); await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World'); await page.locator('.ProseMirror.bn-editor').fill('Hello World');
@@ -38,19 +38,19 @@ test.describe('Pad Tools', () => {
.click(); .click();
const download = await downloadPromise; const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomPad}.pdf`); expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
const pdfBuffer = await cs.toBuffer(await download.createReadStream()); const pdfBuffer = await cs.toBuffer(await download.createReadStream());
const pdfText = (await pdf(pdfBuffer)).text; const pdfText = (await pdf(pdfBuffer)).text;
expect(pdfText).toContain('Hello World'); // This is the pad text expect(pdfText).toContain('Hello World'); // This is the doc text
}); });
test('it converts the blocknote json in correct html for the pdf', async ({ test('it converts the blocknote json in correct html for the pdf', async ({
page, page,
browserName, browserName,
}) => { }) => {
const [randomPad] = await createPad(page, 'pad-editor', browserName, 1); const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
let body = ''; let body = '';
await page.route('**/templates/*/generate-document/', async (route) => { await page.route('**/templates/*/generate-document/', async (route) => {
@@ -60,7 +60,7 @@ test.describe('Pad Tools', () => {
await route.continue(); await route.continue();
}); });
await expect(page.locator('h2').getByText(randomPad)).toBeVisible(); await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
await page.locator('.bn-block-outer').last().fill('Hello World'); await page.locator('.bn-block-outer').last().fill('Hello World');
await page.locator('.bn-block-outer').last().click(); await page.locator('.bn-block-outer').last().click();
@@ -85,15 +85,15 @@ test.describe('Pad Tools', () => {
expect(body).toContain('<br/><br/>'); expect(body).toContain('<br/><br/>');
}); });
test('it updates the pad', async ({ page, browserName }) => { test('it updates the doc', async ({ page, browserName }) => {
const [randomPad] = await createPad( const [randomDoc] = await createDoc(
page, page,
'pad-update', 'doc-update',
browserName, browserName,
1, 1,
true, true,
); );
await expect(page.locator('h2').getByText(randomPad)).toBeVisible(); await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page await page
@@ -103,14 +103,14 @@ test.describe('Pad Tools', () => {
.click(); .click();
await expect( await expect(
page.locator('h2').getByText(`Update document "${randomPad}"`), page.locator('h2').getByText(`Update document "${randomDoc}"`),
).toBeVisible(); ).toBeVisible();
await expect( await expect(
page.getByRole('checkbox', { name: 'Is it public ?' }), page.getByRole('checkbox', { name: 'Is it public ?' }),
).toBeChecked(); ).toBeChecked();
await page.getByText('Document name').fill(`${randomPad}-updated`); await page.getByText('Document name').fill(`${randomDoc}-updated`);
await page.getByText('Is it public ?').click(); await page.getByText('Is it public ?').click();
await page await page
@@ -125,7 +125,7 @@ test.describe('Pad Tools', () => {
const panel = page.getByLabel('Documents panel').first(); const panel = page.getByLabel('Documents panel').first();
await expect( await expect(
panel.locator('li').getByText(`${randomPad}-updated`), panel.locator('li').getByText(`${randomDoc}-updated`),
).toBeVisible(); ).toBeVisible();
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
@@ -140,9 +140,9 @@ test.describe('Pad Tools', () => {
).not.toBeChecked(); ).not.toBeChecked();
}); });
test('it deletes the pad', async ({ page, browserName }) => { test('it deletes the doc', async ({ page, browserName }) => {
const [randomPad] = await createPad(page, 'pad-delete', browserName, 1); const [randomDoc] = await createDoc(page, 'doc-delete', browserName, 1);
await expect(page.locator('h2').getByText(randomPad)).toBeVisible(); await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
await page.getByLabel('Open the document options').click(); await page.getByLabel('Open the document options').click();
await page await page
@@ -152,7 +152,7 @@ test.describe('Pad Tools', () => {
.click(); .click();
await expect( await expect(
page.locator('h2').getByText(`Deleting the document "${randomPad}"`), page.locator('h2').getByText(`Deleting the document "${randomDoc}"`),
).toBeVisible(); ).toBeVisible();
await page await page
@@ -170,7 +170,7 @@ test.describe('Pad Tools', () => {
).toBeVisible(); ).toBeVisible();
const panel = page.getByLabel('Documents panel').first(); const panel = page.getByLabel('Documents panel').first();
await expect(panel.locator('li').getByText(randomPad)).toBeHidden(); await expect(panel.locator('li').getByText(randomDoc)).toBeHidden();
}); });
test('it checks the options available if administrator', async ({ test('it checks the options available if administrator', async ({
@@ -207,7 +207,7 @@ test.describe('Pad Tools', () => {
} }
}); });
await createPad(page, 'pad-tools-right-admin', browserName, 1); await createDoc(page, 'doc-tools-right-admin', browserName, 1);
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible(); await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
@@ -261,7 +261,7 @@ test.describe('Pad Tools', () => {
} }
}); });
await createPad(page, 'pad-tools-right-editor', browserName, 1); await createDoc(page, 'doc-tools-right-editor', browserName, 1);
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible(); await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
@@ -315,7 +315,7 @@ test.describe('Pad Tools', () => {
} }
}); });
await createPad(page, 'pad-tools-right-reader', browserName, 1); await createDoc(page, 'doc-tools-right-reader', browserName, 1);
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible(); await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();

View File

@@ -6,10 +6,10 @@ export const baseApiUrl = (apiVersion: string = '1.0') => {
return `${origin}/api/v${apiVersion}/`; return `${origin}/api/v${apiVersion}/`;
}; };
export const signalingUrl = (padId: string) => { export const signalingUrl = (docId: string) => {
const base = const base =
process.env.NEXT_PUBLIC_SIGNALING_URL || process.env.NEXT_PUBLIC_SIGNALING_URL ||
(typeof window !== 'undefined' ? `wss://${window.location.host}/ws` : ''); (typeof window !== 'undefined' ? `wss://${window.location.host}/ws` : '');
return `${base}/${padId}`; return `${base}/${docId}`;
}; };

View File

@@ -7,41 +7,41 @@ import { WebrtcProvider } from 'y-webrtc';
import { Box } from '@/components'; import { Box } from '@/components';
import { useAuthStore } from '@/core/auth'; import { useAuthStore } from '@/core/auth';
import { Pad } from '@/features/pads/pad-management'; import { Doc } from '@/features/docs/doc-management';
import useSavePad from '../hook/useSavePad'; import useSaveDoc from '../hook/useSaveDoc';
import { usePadStore } from '../stores'; import { useDocStore } from '../stores';
import { randomColor } from '../utils'; import { randomColor } from '../utils';
import { BlockNoteToolbar } from './BlockNoteToolbar'; import { BlockNoteToolbar } from './BlockNoteToolbar';
interface BlockNoteEditorProps { interface BlockNoteEditorProps {
pad: Pad; doc: Doc;
} }
export const BlockNoteEditor = ({ pad }: BlockNoteEditorProps) => { export const BlockNoteEditor = ({ doc }: BlockNoteEditorProps) => {
const { createProvider, padsStore } = usePadStore(); const { createProvider, docsStore } = useDocStore();
const provider = padsStore?.[pad.id]?.provider; const provider = docsStore?.[doc.id]?.provider;
if (!provider) { if (!provider) {
createProvider(pad.id, pad.content); createProvider(doc.id, doc.content);
return null; return null;
} }
return <BlockNoteContent pad={pad} provider={provider} />; return <BlockNoteContent doc={doc} provider={provider} />;
}; };
interface BlockNoteContentProps { interface BlockNoteContentProps {
pad: Pad; doc: Doc;
provider: WebrtcProvider; provider: WebrtcProvider;
} }
export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => { export const BlockNoteContent = ({ doc, provider }: BlockNoteContentProps) => {
const { userData } = useAuthStore(); const { userData } = useAuthStore();
const { setEditor, padsStore } = usePadStore(); const { setEditor, docsStore } = useDocStore();
useSavePad(pad.id, provider.doc, pad.abilities.partial_update); useSaveDoc(doc.id, provider.doc, doc.abilities.partial_update);
const storedEditor = padsStore?.[pad.id]?.editor; const storedEditor = docsStore?.[doc.id]?.editor;
const editor = useMemo(() => { const editor = useMemo(() => {
if (storedEditor) { if (storedEditor) {
return storedEditor; return storedEditor;
@@ -60,8 +60,8 @@ export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => {
}, [provider, storedEditor, userData?.email]); }, [provider, storedEditor, userData?.email]);
useEffect(() => { useEffect(() => {
setEditor(pad.id, editor); setEditor(doc.id, editor);
}, [setEditor, pad.id, editor]); }, [setEditor, doc.id, editor]);
return ( return (
<Box <Box
@@ -77,7 +77,7 @@ export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => {
<BlockNoteView <BlockNoteView
editor={editor} editor={editor}
formattingToolbar={false} formattingToolbar={false}
editable={pad.abilities.partial_update} editable={doc.abilities.partial_update}
> >
<BlockNoteToolbar /> <BlockNoteToolbar />
</BlockNoteView> </BlockNoteView>

View File

@@ -2,16 +2,16 @@ import { Alert, VariantType } from '@openfun/cunningham-react';
import React from 'react'; import React from 'react';
import { Box, Card, Text } from '@/components'; import { Box, Card, Text } from '@/components';
import { Pad } from '@/features/pads/pad-management'; import { Doc } from '@/features/docs/doc-management';
import { PadToolBox } from '@/features/pads/pad-tools'; import { DocToolBox } from '@/features/docs/doc-tools';
import { BlockNoteEditor } from './BlockNoteEditor'; import { BlockNoteEditor } from './BlockNoteEditor';
interface PadEditorProps { interface DocEditorProps {
pad: Pad; doc: Doc;
} }
export const PadEditor = ({ pad }: PadEditorProps) => { export const DocEditor = ({ doc }: DocEditorProps) => {
return ( return (
<> <>
<Box <Box
@@ -21,11 +21,11 @@ export const PadEditor = ({ pad }: PadEditorProps) => {
$position="relative" $position="relative"
> >
<Text as="h2" $align="center" $margin="auto"> <Text as="h2" $align="center" $margin="auto">
{pad.title} {doc.title}
</Text> </Text>
<PadToolBox pad={pad} /> <DocToolBox doc={doc} />
</Box> </Box>
{!pad.abilities.partial_update && ( {!doc.abilities.partial_update && (
<Box className="m-b" $css="margin-top:0;"> <Box className="m-b" $css="margin-top:0;">
<Alert <Alert
type={VariantType.WARNING} type={VariantType.WARNING}
@@ -38,7 +38,7 @@ export const PadEditor = ({ pad }: PadEditorProps) => {
$css="flex:1;" $css="flex:1;"
$overflow="auto" $overflow="auto"
> >
<BlockNoteEditor pad={pad} /> <BlockNoteEditor doc={doc} />
</Card> </Card>
</> </>
); );

View File

@@ -0,0 +1 @@
export * from './DocEditor';

View File

@@ -2,12 +2,12 @@ import { useRouter } from 'next/router';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import * as Y from 'yjs'; import * as Y from 'yjs';
import { useUpdatePad } from '@/features/pads/pad-management/'; import { useUpdateDoc } from '@/features/docs/doc-management/';
import { toBase64 } from '../utils'; import { toBase64 } from '../utils';
const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => { const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => {
const { mutate: updatePad } = useUpdatePad(); const { mutate: updateDoc } = useUpdateDoc();
const [initialDoc, setInitialDoc] = useState<string>( const [initialDoc, setInitialDoc] = useState<string>(
toBase64(Y.encodeStateAsUpdate(doc)), toBase64(Y.encodeStateAsUpdate(doc)),
); );
@@ -40,7 +40,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
}; };
}, [doc]); }, [doc]);
const savePad = useCallback(() => { const saveDoc = useCallback(() => {
const newDoc = toBase64(Y.encodeStateAsUpdate(doc)); const newDoc = toBase64(Y.encodeStateAsUpdate(doc));
/** /**
@@ -52,11 +52,11 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
setInitialDoc(newDoc); setInitialDoc(newDoc);
updatePad({ updateDoc({
id: padId, id: docId,
content: newDoc, content: newDoc,
}); });
}, [initialDoc, padId, doc, updatePad, canSave]); }, [initialDoc, docId, doc, updateDoc, canSave]);
const timeout = useRef<NodeJS.Timeout>(); const timeout = useRef<NodeJS.Timeout>();
const router = useRouter(); const router = useRouter();
@@ -67,7 +67,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
} }
const onSave = () => { const onSave = () => {
savePad(); saveDoc();
}; };
// Save every minute // Save every minute
@@ -82,7 +82,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
removeEventListener('beforeunload', onSave); removeEventListener('beforeunload', onSave);
router.events.off('routeChangeStart', onSave); router.events.off('routeChangeStart', onSave);
}; };
}, [router.events, savePad]); }, [router.events, saveDoc]);
}; };
export default useSavePad; export default useSaveDoc;

View File

@@ -0,0 +1 @@
export * from './useDocStore';

View File

@@ -0,0 +1,66 @@
import { BlockNoteEditor } from '@blocknote/core';
import { WebrtcProvider } from 'y-webrtc';
import * as Y from 'yjs';
import { create } from 'zustand';
import { signalingUrl } from '@/core';
import { Base64, Doc } from '@/features/docs/doc-management';
export interface DocStore {
docsStore: {
[docId: Doc['id']]: {
provider: WebrtcProvider;
editor?: BlockNoteEditor;
};
};
createProvider: (docId: Doc['id'], initialDoc: Base64) => WebrtcProvider;
setEditor: (docId: Doc['id'], editor: BlockNoteEditor) => void;
}
const initialState = {
docsStore: {},
};
export const useDocStore = create<DocStore>((set) => ({
docsStore: initialState.docsStore,
createProvider: (docId: string, initialDoc: Base64) => {
const doc = new Y.Doc({
guid: docId,
});
if (initialDoc) {
Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64'));
}
const provider = new WebrtcProvider(docId, doc, {
signaling: [signalingUrl(docId)],
maxConns: 5,
});
set(({ docsStore }) => {
return {
docsStore: {
...docsStore,
[docId]: {
provider,
},
},
};
});
return provider;
},
setEditor: (docId, editor) => {
set(({ docsStore }) => {
return {
docsStore: {
...docsStore,
[docId]: {
...docsStore[docId],
editor,
},
},
};
});
},
}));

View File

@@ -0,0 +1,3 @@
export * from './useDoc';
export * from './useDocs';
export * from './useUpdateDoc';

View File

@@ -0,0 +1,42 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Doc, KEY_LIST_DOC } from '@/features/docs';
type CreateDocParam = Pick<Doc, 'title' | 'is_public'>;
export const createDoc = async ({
title,
is_public,
}: CreateDocParam): Promise<Doc> => {
const response = await fetchAPI(`documents/`, {
method: 'POST',
body: JSON.stringify({
title,
is_public,
}),
});
if (!response.ok) {
throw new APIError('Failed to create the doc', await errorCauses(response));
}
return response.json() as Promise<Doc>;
};
interface CreateDocProps {
onSuccess: (data: Doc) => void;
}
export function useCreateDoc({ onSuccess }: CreateDocProps) {
const queryClient = useQueryClient();
return useMutation<Doc, APIError, CreateDocParam>({
mutationFn: createDoc,
onSuccess: (data) => {
void queryClient.invalidateQueries({
queryKey: [KEY_LIST_DOC],
});
onSuccess(data);
},
});
}

View File

@@ -0,0 +1,32 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Doc } from '../types';
export type DocParams = {
id: string;
};
export const getDoc = async ({ id }: DocParams): Promise<Doc> => {
const response = await fetchAPI(`documents/${id}/`);
if (!response.ok) {
throw new APIError('Failed to get the doc', await errorCauses(response));
}
return response.json() as Promise<Doc>;
};
export const KEY_DOC = 'doc';
export function useDoc(
param: DocParams,
queryConfig?: UseQueryOptions<Doc, APIError, Doc>,
) {
return useQuery<Doc, APIError, Doc>({
queryKey: [KEY_DOC, param],
queryFn: () => getDoc(param),
...queryConfig,
});
}

View File

@@ -6,59 +6,59 @@ import {
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import { APIError, APIList, errorCauses, fetchAPI } from '@/api'; import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
import { Pad } from '@/features/pads/pad-management'; import { Doc } from '@/features/docs/doc-management';
export enum PadsOrdering { export enum DocsOrdering {
BY_CREATED_ON = 'created_at', BY_CREATED_ON = 'created_at',
BY_CREATED_ON_DESC = '-created_at', BY_CREATED_ON_DESC = '-created_at',
} }
export type PadsParams = { export type DocsParams = {
ordering: PadsOrdering; ordering: DocsOrdering;
}; };
type PadsAPIParams = PadsParams & { type DocsAPIParams = DocsParams & {
page: number; page: number;
}; };
type PadsResponse = APIList<Pad>; type DocsResponse = APIList<Doc>;
export const getPads = async ({ export const getDocs = async ({
ordering, ordering,
page, page,
}: PadsAPIParams): Promise<PadsResponse> => { }: DocsAPIParams): Promise<DocsResponse> => {
const orderingQuery = ordering ? `&ordering=${ordering}` : ''; const orderingQuery = ordering ? `&ordering=${ordering}` : '';
const response = await fetchAPI(`documents/?page=${page}${orderingQuery}`); const response = await fetchAPI(`documents/?page=${page}${orderingQuery}`);
if (!response.ok) { if (!response.ok) {
throw new APIError('Failed to get the pads', await errorCauses(response)); throw new APIError('Failed to get the docs', await errorCauses(response));
} }
return response.json() as Promise<PadsResponse>; return response.json() as Promise<DocsResponse>;
}; };
export const KEY_LIST_PAD = 'pads'; export const KEY_LIST_DOC = 'docs';
export function usePads( export function useDocs(
param: PadsParams, param: DocsParams,
queryConfig?: DefinedInitialDataInfiniteOptions< queryConfig?: DefinedInitialDataInfiniteOptions<
PadsResponse, DocsResponse,
APIError, APIError,
InfiniteData<PadsResponse>, InfiniteData<DocsResponse>,
QueryKey, QueryKey,
number number
>, >,
) { ) {
return useInfiniteQuery< return useInfiniteQuery<
PadsResponse, DocsResponse,
APIError, APIError,
InfiniteData<PadsResponse>, InfiniteData<DocsResponse>,
QueryKey, QueryKey,
number number
>({ >({
initialPageParam: 1, initialPageParam: 1,
queryKey: [KEY_LIST_PAD, param], queryKey: [KEY_LIST_DOC, param],
queryFn: ({ pageParam }) => queryFn: ({ pageParam }) =>
getPads({ getDocs({
...param, ...param,
page: pageParam, page: pageParam,
}), }),

View File

@@ -6,32 +6,32 @@ import {
import { APIError, errorCauses, fetchAPI } from '@/api'; import { APIError, errorCauses, fetchAPI } from '@/api';
import { KEY_LIST_PAD } from './usePads'; import { KEY_LIST_DOC } from './useDocs';
interface RemovePadProps { interface RemoveDocProps {
padId: string; docId: string;
} }
export const removePad = async ({ padId }: RemovePadProps): Promise<void> => { export const removeDoc = async ({ docId }: RemoveDocProps): Promise<void> => {
const response = await fetchAPI(`documents/${padId}/`, { const response = await fetchAPI(`documents/${docId}/`, {
method: 'DELETE', method: 'DELETE',
}); });
if (!response.ok) { if (!response.ok) {
throw new APIError('Failed to delete the pad', await errorCauses(response)); throw new APIError('Failed to delete the doc', await errorCauses(response));
} }
}; };
type UseRemovePadOptions = UseMutationOptions<void, APIError, RemovePadProps>; type UseRemoveDocOptions = UseMutationOptions<void, APIError, RemoveDocProps>;
export const useRemovePad = (options?: UseRemovePadOptions) => { export const useRemoveDoc = (options?: UseRemoveDocOptions) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<void, APIError, RemovePadProps>({ return useMutation<void, APIError, RemoveDocProps>({
mutationFn: removePad, mutationFn: removeDoc,
...options, ...options,
onSuccess: (data, variables, context) => { onSuccess: (data, variables, context) => {
void queryClient.invalidateQueries({ void queryClient.invalidateQueries({
queryKey: [KEY_LIST_PAD], queryKey: [KEY_LIST_DOC],
}); });
if (options?.onSuccess) { if (options?.onSuccess) {
options.onSuccess(data, variables, context); options.onSuccess(data, variables, context);

View File

@@ -1,15 +1,15 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api'; import { APIError, errorCauses, fetchAPI } from '@/api';
import { Pad } from '@/features/pads'; import { Doc } from '@/features/docs';
export type UpdatePadParams = Pick<Pad, 'id'> & export type UpdateDocParams = Pick<Doc, 'id'> &
Partial<Pick<Pad, 'content' | 'title' | 'is_public'>>; Partial<Pick<Doc, 'content' | 'title' | 'is_public'>>;
export const updatePad = async ({ export const updateDoc = async ({
id, id,
...params ...params
}: UpdatePadParams): Promise<Pad> => { }: UpdateDocParams): Promise<Doc> => {
const response = await fetchAPI(`documents/${id}/`, { const response = await fetchAPI(`documents/${id}/`, {
method: 'PATCH', method: 'PATCH',
body: JSON.stringify({ body: JSON.stringify({
@@ -18,24 +18,24 @@ export const updatePad = async ({
}); });
if (!response.ok) { if (!response.ok) {
throw new APIError('Failed to update the pad', await errorCauses(response)); throw new APIError('Failed to update the doc', await errorCauses(response));
} }
return response.json() as Promise<Pad>; return response.json() as Promise<Doc>;
}; };
interface UpdatePadProps { interface UpdateDocProps {
onSuccess?: (data: Pad) => void; onSuccess?: (data: Doc) => void;
listInvalideQueries?: string[]; listInvalideQueries?: string[];
} }
export function useUpdatePad({ export function useUpdateDoc({
onSuccess, onSuccess,
listInvalideQueries, listInvalideQueries,
}: UpdatePadProps = {}) { }: UpdateDocProps = {}) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<Pad, APIError, UpdatePadParams>({ return useMutation<Doc, APIError, UpdateDocParams>({
mutationFn: updatePad, mutationFn: updateDoc,
onSuccess: (data) => { onSuccess: (data) => {
listInvalideQueries?.forEach((queryKey) => { listInvalideQueries?.forEach((queryKey) => {
void queryClient.invalidateQueries({ void queryClient.invalidateQueries({

View File

@@ -7,25 +7,25 @@ import IconGroup from '@/assets/icons/icon-group2.svg';
import { Box, Card, StyledLink, Text } from '@/components'; import { Box, Card, StyledLink, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { useCreatePad } from '../api/useCreatePad'; import { useCreateDoc } from '../api/useCreateDoc';
import { InputPadName } from './InputPadName'; import { InputDocName } from './InputDocName';
export const CardCreatePad = () => { export const CardCreateDoc = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const { const {
mutate: createPad, mutate: createDoc,
isError, isError,
isPending, isPending,
error, error,
} = useCreatePad({ } = useCreateDoc({
onSuccess: (pad) => { onSuccess: (doc) => {
router.push(`/docs/${pad.id}`); router.push(`/docs/${doc.id}`);
}, },
}); });
const [padName, setPadName] = useState(''); const [docName, setDocName] = useState('');
const [padPublic, setPadPublic] = useState(false); const [docPublic, setDocPublic] = useState(false);
const { colorsTokens } = useCunninghamTheme(); const { colorsTokens } = useCunninghamTheme();
return ( return (
@@ -49,14 +49,14 @@ export const CardCreatePad = () => {
{t('Name the document')} {t('Name the document')}
</Text> </Text>
</Box> </Box>
<InputPadName <InputDocName
label={t('Document name')} label={t('Document name')}
{...{ error, isError, isPending, setPadName }} {...{ error, isError, isPending, setDocName }}
/> />
<Switch <Switch
label={t('Is it public ?')} label={t('Is it public ?')}
labelSide="right" labelSide="right"
onChange={() => setPadPublic(!padPublic)} onChange={() => setDocPublic(!docPublic)}
/> />
</Box> </Box>
<Box $justify="space-between" $direction="row" $align="center"> <Box $justify="space-between" $direction="row" $align="center">
@@ -64,8 +64,8 @@ export const CardCreatePad = () => {
<Button color="secondary">{t('Cancel')}</Button> <Button color="secondary">{t('Cancel')}</Button>
</StyledLink> </StyledLink>
<Button <Button
onClick={() => createPad({ title: padName, is_public: padPublic })} onClick={() => createDoc({ title: docName, is_public: docPublic })}
disabled={!padName} disabled={!docName}
> >
{t('Create the document')} {t('Create the document')}
</Button> </Button>

View File

@@ -4,23 +4,23 @@ import { useEffect, useState } from 'react';
import { APIError } from '@/api'; import { APIError } from '@/api';
import { Box, TextErrors } from '@/components'; import { Box, TextErrors } from '@/components';
interface InputPadNameProps { interface InputDocNameProps {
error: APIError | null; error: APIError | null;
isError: boolean; isError: boolean;
isPending: boolean; isPending: boolean;
label: string; label: string;
setPadName: (newPadName: string) => void; setDocName: (newDocName: string) => void;
defaultValue?: string; defaultValue?: string;
} }
export const InputPadName = ({ export const InputDocName = ({
defaultValue, defaultValue,
error, error,
isError, isError,
isPending, isPending,
label, label,
setPadName, setDocName,
}: InputPadNameProps) => { }: InputDocNameProps) => {
const [isInputError, setIsInputError] = useState(isError); const [isInputError, setIsInputError] = useState(isError);
useEffect(() => { useEffect(() => {
@@ -37,7 +37,7 @@ export const InputPadName = ({
label={label} label={label}
defaultValue={defaultValue} defaultValue={defaultValue}
onChange={(e) => { onChange={(e) => {
setPadName(e.target.value); setDocName(e.target.value);
setIsInputError(false); setIsInputError(false);
}} }}
rightIcon={<span className="material-icons">edit</span>} rightIcon={<span className="material-icons">edit</span>}

View File

@@ -12,26 +12,26 @@ import { useRouter } from 'next/navigation';
import { Box, Text, TextErrors } from '@/components'; import { Box, Text, TextErrors } from '@/components';
import useCunninghamTheme from '@/cunningham/useCunninghamTheme'; import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
import { useRemovePad } from '../api/useRemovePad'; import { useRemoveDoc } from '../api/useRemoveDoc';
import IconPad from '../assets/icon-pad.svg'; import IconDoc from '../assets/icon-doc.svg';
import IconRemove from '../assets/icon-trash.svg'; import IconRemove from '../assets/icon-trash.svg';
import { Pad } from '../types'; import { Doc } from '../types';
interface ModalRemovePadProps { interface ModalRemoveDocProps {
onClose: () => void; onClose: () => void;
pad: Pad; doc: Doc;
} }
export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => { export const ModalRemoveDoc = ({ onClose, doc }: ModalRemoveDocProps) => {
const { colorsTokens } = useCunninghamTheme(); const { colorsTokens } = useCunninghamTheme();
const { toast } = useToastProvider(); const { toast } = useToastProvider();
const router = useRouter(); const router = useRouter();
const { const {
mutate: removePad, mutate: removeDoc,
isError, isError,
error, error,
} = useRemovePad({ } = useRemoveDoc({
onSuccess: () => { onSuccess: () => {
toast(t('The document has been deleted.'), VariantType.SUCCESS, { toast(t('The document has been deleted.'), VariantType.SUCCESS, {
duration: 4000, duration: 4000,
@@ -62,8 +62,8 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
color="primary" color="primary"
fullWidth fullWidth
onClick={() => onClick={() =>
removePad({ removeDoc({
padId: pad.id, docId: doc.id,
}) })
} }
> >
@@ -75,7 +75,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
<Box $align="center" $gap="1rem"> <Box $align="center" $gap="1rem">
<IconRemove width={48} color={colorsTokens()['primary-text']} /> <IconRemove width={48} color={colorsTokens()['primary-text']} />
<Text as="h2" $size="h3" $margin="none"> <Text as="h2" $size="h3" $margin="none">
{t('Deleting the document "{{title}}"', { title: pad.title })} {t('Deleting the document "{{title}}"', { title: doc.title })}
</Text> </Text>
</Box> </Box>
} }
@@ -88,7 +88,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
<Alert canClose={false} type={VariantType.WARNING}> <Alert canClose={false} type={VariantType.WARNING}>
<Text> <Text>
{t('Are you sure you want to delete the document "{{title}}"?', { {t('Are you sure you want to delete the document "{{title}}"?', {
title: pad.title, title: doc.title,
})} })}
</Text> </Text>
</Alert> </Alert>
@@ -106,7 +106,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
$align="center" $align="center"
$radius="2px" $radius="2px"
> >
<IconPad <IconDoc
className="p-t" className="p-t"
aria-label={t(`Document icon`)} aria-label={t(`Document icon`)}
color={colorsTokens()['primary-500']} color={colorsTokens()['primary-500']}
@@ -118,7 +118,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
}} }}
/> />
<Text $theme="primary" $weight="bold" $size="l"> <Text $theme="primary" $weight="bold" $size="l">
{pad.title} {doc.title}
</Text> </Text>
</Text> </Text>
</Box> </Box>

View File

@@ -13,38 +13,38 @@ import { useTranslation } from 'react-i18next';
import { Box, Text } from '@/components'; import { Box, Text } from '@/components';
import useCunninghamTheme from '@/cunningham/useCunninghamTheme'; import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
import { KEY_LIST_PAD, KEY_PAD } from '../api'; import { KEY_DOC, KEY_LIST_DOC } from '../api';
import { useUpdatePad } from '../api/useUpdatePad'; import { useUpdateDoc } from '../api/useUpdateDoc';
import IconEdit from '../assets/icon-edit.svg'; import IconEdit from '../assets/icon-edit.svg';
import { Pad } from '../types'; import { Doc } from '../types';
import { InputPadName } from './InputPadName'; import { InputDocName } from './InputDocName';
interface ModalUpdatePadProps { interface ModalUpdateDocProps {
onClose: () => void; onClose: () => void;
pad: Pad; doc: Doc;
} }
export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => { export const ModalUpdateDoc = ({ onClose, doc }: ModalUpdateDocProps) => {
const { colorsTokens } = useCunninghamTheme(); const { colorsTokens } = useCunninghamTheme();
const [title, setTitle] = useState(pad.title); const [title, setTitle] = useState(doc.title);
const { toast } = useToastProvider(); const { toast } = useToastProvider();
const [padPublic, setPadPublic] = useState(pad.is_public); const [docPublic, setDocPublic] = useState(doc.is_public);
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
mutate: updatePad, mutate: updateDoc,
isError, isError,
isPending, isPending,
error, error,
} = useUpdatePad({ } = useUpdateDoc({
onSuccess: () => { onSuccess: () => {
toast(t('The document has been updated.'), VariantType.SUCCESS, { toast(t('The document has been updated.'), VariantType.SUCCESS, {
duration: 4000, duration: 4000,
}); });
onClose(); onClose();
}, },
listInvalideQueries: [KEY_PAD, KEY_LIST_PAD], listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
}); });
return ( return (
@@ -69,10 +69,10 @@ export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
color="primary" color="primary"
fullWidth fullWidth
onClick={() => onClick={() =>
updatePad({ updateDoc({
title, title,
id: pad.id, id: doc.id,
is_public: padPublic, is_public: docPublic,
}) })
} }
> >
@@ -85,7 +85,7 @@ export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
<IconEdit width={48} color={colorsTokens()['primary-text']} /> <IconEdit width={48} color={colorsTokens()['primary-text']} />
<Text as="h2" $size="h3" $margin="none"> <Text as="h2" $size="h3" $margin="none">
{t('Update document "{{documentTitle}}"', { {t('Update document "{{documentTitle}}"', {
documentTitle: pad.title, documentTitle: doc.title,
})} })}
</Text> </Text>
</Box> </Box>
@@ -101,16 +101,16 @@ export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
</Alert> </Alert>
<Box $gap="1rem"> <Box $gap="1rem">
<InputPadName <InputDocName
label={t('Document name')} label={t('Document name')}
defaultValue={title} defaultValue={title}
{...{ error, isError, isPending, setPadName: setTitle }} {...{ error, isError, isPending, setDocName: setTitle }}
/> />
<Switch <Switch
label={t('Is it public ?')} label={t('Is it public ?')}
labelSide="right" labelSide="right"
defaultChecked={padPublic} defaultChecked={docPublic}
onChange={() => setPadPublic(!padPublic)} onChange={() => setDocPublic(!docPublic)}
/> />
</Box> </Box>
</Box> </Box>

View File

@@ -0,0 +1,3 @@
export * from './CardCreateDoc';
export * from './ModalRemoveDoc';
export * from './ModalUpdateDoc';

View File

@@ -22,7 +22,7 @@ export enum Role {
export type Base64 = string; export type Base64 = string;
export interface Pad { export interface Doc {
id: string; id: string;
title: string; title: string;
content: Base64; content: Base64;

View File

@@ -1,6 +1,6 @@
import { Pad, Role } from './types'; import { Doc, Role } from './types';
export const currentDocRole = (doc: Pad): Role => { export const currentDocRole = (doc: Doc): Role => {
return doc.abilities.destroy return doc.abilities.destroy
? Role.OWNER ? Role.OWNER
: doc.abilities.manage_accesses : doc.abilities.manage_accesses

View File

@@ -3,24 +3,24 @@ import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Box, DropButton, IconOptions, Text } from '@/components'; import { Box, DropButton, IconOptions, Text } from '@/components';
import { ModalAddMembers } from '@/features/pads/members/members-add';
import { ModalGridMembers } from '@/features/pads/members/members-grid/';
import { import {
ModalRemovePad, Doc,
ModalUpdatePad, ModalRemoveDoc,
Pad, ModalUpdateDoc,
currentDocRole, currentDocRole,
} from '@/features/pads/pad-management'; } from '@/features/docs/doc-management';
import { ModalAddMembers } from '@/features/docs/members/members-add';
import { ModalGridMembers } from '@/features/docs/members/members-grid/';
import { TemplatesOrdering, useTemplates } from '../api/useTemplates'; import { TemplatesOrdering, useTemplates } from '../api/useTemplates';
import { ModalPDF } from './ModalPDF'; import { ModalPDF } from './ModalPDF';
interface PadToolBoxProps { interface DocToolBoxProps {
pad: Pad; doc: Doc;
} }
export const PadToolBox = ({ pad }: PadToolBoxProps) => { export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { data: templates } = useTemplates({ const { data: templates } = useTemplates({
ordering: TemplatesOrdering.BY_CREATED_ON_DESC, ordering: TemplatesOrdering.BY_CREATED_ON_DESC,
@@ -62,7 +62,7 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
isOpen={isDropOpen} isOpen={isDropOpen}
> >
<Box> <Box>
{pad.abilities.manage_accesses && ( {doc.abilities.manage_accesses && (
<> <>
<Button <Button
onClick={() => { onClick={() => {
@@ -88,7 +88,7 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
</Button> </Button>
</> </>
)} )}
{pad.abilities.partial_update && ( {doc.abilities.partial_update && (
<Button <Button
onClick={() => { onClick={() => {
setIsModalUpdateOpen(true); setIsModalUpdateOpen(true);
@@ -101,7 +101,7 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
<Text $theme="primary">{t('Update document')}</Text> <Text $theme="primary">{t('Update document')}</Text>
</Button> </Button>
)} )}
{pad.abilities.destroy && ( {doc.abilities.destroy && (
<Button <Button
onClick={() => { onClick={() => {
setIsModalRemoveOpen(true); setIsModalRemoveOpen(true);
@@ -130,28 +130,28 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
{isModalGridMembersOpen && ( {isModalGridMembersOpen && (
<ModalGridMembers <ModalGridMembers
onClose={() => setIsModalGridMembersOpen(false)} onClose={() => setIsModalGridMembersOpen(false)}
doc={pad} doc={doc}
/> />
)} )}
{isModalAddMembersOpen && ( {isModalAddMembersOpen && (
<ModalAddMembers <ModalAddMembers
onClose={() => setIsModalAddMembersOpen(false)} onClose={() => setIsModalAddMembersOpen(false)}
doc={pad} doc={doc}
currentRole={currentDocRole(pad)} currentRole={currentDocRole(doc)}
/> />
)} )}
{isModalPDFOpen && ( {isModalPDFOpen && (
<ModalPDF <ModalPDF
onClose={() => setIsModalPDFOpen(false)} onClose={() => setIsModalPDFOpen(false)}
templateOptions={templateOptions} templateOptions={templateOptions}
pad={pad} doc={doc}
/> />
)} )}
{isModalUpdateOpen && ( {isModalUpdateOpen && (
<ModalUpdatePad onClose={() => setIsModalUpdateOpen(false)} pad={pad} /> <ModalUpdateDoc onClose={() => setIsModalUpdateOpen(false)} doc={doc} />
)} )}
{isModalRemoveOpen && ( {isModalRemoveOpen && (
<ModalRemovePad onClose={() => setIsModalRemoveOpen(false)} pad={pad} /> <ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
)} )}
</Box> </Box>
); );

View File

@@ -12,8 +12,8 @@ import { t } from 'i18next';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Box, Text } from '@/components'; import { Box, Text } from '@/components';
import { usePadStore } from '@/features/pads/pad-editor/'; import { useDocStore } from '@/features/docs/doc-editor/';
import { Pad } from '@/features/pads/pad-management'; import { Doc } from '@/features/docs/doc-management';
import { useCreatePdf } from '../api/useCreatePdf'; import { useCreatePdf } from '../api/useCreatePdf';
import { adaptBlockNoteHTML, downloadFile } from '../utils'; import { adaptBlockNoteHTML, downloadFile } from '../utils';
@@ -24,12 +24,12 @@ interface ModalPDFProps {
label: string; label: string;
value: string; value: string;
}[]; }[];
pad: Pad; doc: Doc;
} }
export const ModalPDF = ({ onClose, templateOptions, pad }: ModalPDFProps) => { export const ModalPDF = ({ onClose, templateOptions, doc }: ModalPDFProps) => {
const { toast } = useToastProvider(); const { toast } = useToastProvider();
const { padsStore } = usePadStore(); const { docsStore } = useDocStore();
const { const {
mutate: createPdf, mutate: createPdf,
data: pdf, data: pdf,
@@ -59,7 +59,7 @@ export const ModalPDF = ({ onClose, templateOptions, pad }: ModalPDFProps) => {
} }
// normalize title // normalize title
const title = pad.title const title = doc.title
.toLowerCase() .toLowerCase()
.normalize('NFD') .normalize('NFD')
.replace(/[\u0300-\u036f]/g, '') .replace(/[\u0300-\u036f]/g, '')
@@ -78,7 +78,7 @@ export const ModalPDF = ({ onClose, templateOptions, pad }: ModalPDFProps) => {
return; return;
} }
const editor = padsStore[pad.id].editor; const editor = docsStore[doc.id].editor;
if (!editor) { if (!editor) {
toast(t('No editor found'), VariantType.ERROR); toast(t('No editor found'), VariantType.ERROR);

View File

@@ -0,0 +1 @@
export * from './DocToolBox';

View File

@@ -1,4 +1,4 @@
import { Access } from '../pad-management'; import { Access } from '../doc-management';
export interface Template { export interface Template {
id: string; id: string;

View File

@@ -5,7 +5,7 @@ import fetchMock from 'fetch-mock';
import { AppWrapper } from '@/tests/utils'; import { AppWrapper } from '@/tests/utils';
import { PadList } from '../components/PadList'; import { DocList } from '../components/DocList';
import { Panel } from '../components/Panel'; import { Panel } from '../components/Panel';
window.HTMLElement.prototype.scroll = function () {}; window.HTMLElement.prototype.scroll = function () {};
@@ -17,18 +17,18 @@ jest.mock('next/router', () => ({
}), }),
})); }));
describe('PanelPads', () => { describe('PanelDocs', () => {
afterEach(() => { afterEach(() => {
fetchMock.restore(); fetchMock.restore();
}); });
it('renders with no pad to display', async () => { it('renders with no doc to display', async () => {
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, { fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
count: 0, count: 0,
results: [], results: [],
}); });
render(<PadList />, { wrapper: AppWrapper }); render(<DocList />, { wrapper: AppWrapper });
expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByRole('status')).toBeInTheDocument();
@@ -39,7 +39,7 @@ describe('PanelPads', () => {
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
it('renders an empty pad', async () => { it('renders an empty doc', async () => {
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, { fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
count: 1, count: 1,
results: [ results: [
@@ -51,14 +51,14 @@ describe('PanelPads', () => {
], ],
}); });
render(<PadList />, { wrapper: AppWrapper }); render(<DocList />, { wrapper: AppWrapper });
expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByRole('status')).toBeInTheDocument();
expect(await screen.findByLabelText('Empty pads icon')).toBeInTheDocument(); expect(await screen.findByLabelText('Empty docs icon')).toBeInTheDocument();
}); });
it('renders a pad with only 1 member', async () => { it('renders a doc with only 1 member', async () => {
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, { fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
count: 1, count: 1,
results: [ results: [
@@ -75,20 +75,20 @@ describe('PanelPads', () => {
], ],
}); });
render(<PadList />, { wrapper: AppWrapper }); render(<DocList />, { wrapper: AppWrapper });
expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByRole('status')).toBeInTheDocument();
expect(await screen.findByLabelText('Empty pads icon')).toBeInTheDocument(); expect(await screen.findByLabelText('Empty docs icon')).toBeInTheDocument();
}); });
it('renders a non-empty pad', async () => { it('renders a non-empty doc', async () => {
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, { fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
count: 1, count: 1,
results: [ results: [
{ {
id: '1', id: '1',
name: 'Pad 1', name: 'Doc 1',
accesses: [ accesses: [
{ {
id: '1', id: '1',
@@ -103,11 +103,11 @@ describe('PanelPads', () => {
], ],
}); });
render(<PadList />, { wrapper: AppWrapper }); render(<DocList />, { wrapper: AppWrapper });
expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByRole('status')).toBeInTheDocument();
expect(await screen.findByLabelText('Pads icon')).toBeInTheDocument(); expect(await screen.findByLabelText('Docs icon')).toBeInTheDocument();
}); });
it('renders the error', async () => { it('renders the error', async () => {
@@ -115,7 +115,7 @@ describe('PanelPads', () => {
status: 500, status: 500,
}); });
render(<PadList />, { wrapper: AppWrapper }); render(<DocList />, { wrapper: AppWrapper });
expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByRole('status')).toBeInTheDocument();

View File

@@ -5,15 +5,15 @@ import { useTranslation } from 'react-i18next';
import IconGroup from '@/assets/icons/icon-group.svg'; import IconGroup from '@/assets/icons/icon-group.svg';
import { Box, StyledLink, Text } from '@/components'; import { Box, StyledLink, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { Pad } from '@/features/pads/pad-management'; import { Doc } from '@/features/docs/doc-management';
import IconNone from '../assets/icon-none.svg'; import IconNone from '../assets/icon-none.svg';
interface PadItemProps { interface DocItemProps {
pad: Pad; doc: Doc;
} }
export const PadItem = ({ pad }: PadItemProps) => { export const DocItem = ({ doc }: DocItemProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme(); const { colorsTokens } = useCunninghamTheme();
const { const {
@@ -21,8 +21,8 @@ export const PadItem = ({ pad }: PadItemProps) => {
} = useRouter(); } = useRouter();
// There is at least 1 owner in the team // There is at least 1 owner in the team
const hasMembers = pad.accesses.length > 1; const hasMembers = doc.accesses.length > 1;
const isActive = pad.id === id; const isActive = doc.id === id;
const commonProps = { const commonProps = {
className: 'p-t', className: 'p-t',
@@ -63,11 +63,11 @@ export const PadItem = ({ pad }: PadItemProps) => {
${isActive ? activeStyle : hoverStyle} ${isActive ? activeStyle : hoverStyle}
`} `}
> >
<StyledLink className="p-s pt-t pb-t" href={`/docs/${pad.id}`}> <StyledLink className="p-s pt-t pb-t" href={`/docs/${doc.id}`}>
<Box $align="center" $direction="row" $gap="0.5rem"> <Box $align="center" $direction="row" $gap="0.5rem">
{hasMembers ? ( {hasMembers ? (
<IconGroup <IconGroup
aria-label={t(`Pads icon`)} aria-label={t(`Docs icon`)}
color={colorsTokens()['primary-500']} color={colorsTokens()['primary-500']}
{...commonProps} {...commonProps}
style={{ style={{
@@ -77,7 +77,7 @@ export const PadItem = ({ pad }: PadItemProps) => {
/> />
) : ( ) : (
<IconNone <IconNone
aria-label={t(`Empty pads icon`)} aria-label={t(`Empty docs icon`)}
color={colorsTokens()['greyscale-500']} color={colorsTokens()['greyscale-500']}
{...commonProps} {...commonProps}
style={{ style={{
@@ -93,7 +93,7 @@ export const PadItem = ({ pad }: PadItemProps) => {
min-width: 14rem; min-width: 14rem;
`} `}
> >
{pad.title} {doc.title}
</Text> </Text>
</Box> </Box>
</StyledLink> </StyledLink>

View File

@@ -5,19 +5,19 @@ import { useTranslation } from 'react-i18next';
import { APIError } from '@/api'; import { APIError } from '@/api';
import { Box, Text, TextErrors } from '@/components'; import { Box, Text, TextErrors } from '@/components';
import { InfiniteScroll } from '@/components/InfiniteScroll'; import { InfiniteScroll } from '@/components/InfiniteScroll';
import { Pad, usePads } from '@/features/pads/pad-management'; import { Doc, useDocs } from '@/features/docs/doc-management';
import { usePadPanelStore } from '../store'; import { useDocPanelStore } from '../store';
import { PadItem } from './PadItem'; import { DocItem } from './DocItem';
interface PanelTeamsStateProps { interface PanelTeamsStateProps {
isLoading: boolean; isLoading: boolean;
error: APIError<unknown> | null; error: APIError<unknown> | null;
pads?: Pad[]; docs?: Doc[];
} }
const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => { const DocListState = ({ isLoading, error, docs }: PanelTeamsStateProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
if (isLoading) { if (isLoading) {
@@ -28,7 +28,7 @@ const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => {
); );
} }
if (!pads?.length && !error) { if (!docs?.length && !error) {
return ( return (
<Box $justify="center" $margin="small"> <Box $justify="center" $margin="small">
<Text <Text
@@ -50,7 +50,7 @@ const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => {
return ( return (
<> <>
{pads?.map((pad) => <PadItem pad={pad} key={pad.id} />)} {docs?.map((doc) => <DocItem doc={doc} key={doc.id} />)}
{error && ( {error && (
<Box <Box
$justify="center" $justify="center"
@@ -72,8 +72,8 @@ const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => {
); );
}; };
export const PadList = () => { export const DocList = () => {
const ordering = usePadPanelStore((state) => state.ordering); const ordering = useDocPanelStore((state) => state.ordering);
const { const {
data, data,
error, error,
@@ -81,14 +81,14 @@ export const PadList = () => {
fetchNextPage, fetchNextPage,
hasNextPage, hasNextPage,
isFetchingNextPage, isFetchingNextPage,
} = usePads({ } = useDocs({
ordering, ordering,
}); });
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const pads = useMemo(() => { const docs = useMemo(() => {
return data?.pages.reduce((acc, page) => { return data?.pages.reduce((acc, page) => {
return acc.concat(page.results); return acc.concat(page.results);
}, [] as Pad[]); }, [] as Doc[]);
}, [data?.pages]); }, [data?.pages]);
return ( return (
@@ -105,7 +105,7 @@ export const PadList = () => {
$margin={{ top: 'none' }} $margin={{ top: 'none' }}
role="listbox" role="listbox"
> >
<PadListState isLoading={isLoading} error={error} pads={pads} /> <DocListState isLoading={isLoading} error={error} docs={docs} />
</InfiniteScroll> </InfiniteScroll>
</Box> </Box>
); );

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Box, BoxButton, Text } from '@/components'; import { Box, BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { PadList } from './PadList'; import { DocList } from './DocList';
import { PanelActions } from './PanelActions'; import { PanelActions } from './PanelActions';
export const Panel = () => { export const Panel = () => {
@@ -80,7 +80,7 @@ export const Panel = () => {
</Text> </Text>
<PanelActions /> <PanelActions />
</Box> </Box>
<PadList /> <DocList />
</Box> </Box>
</Box> </Box>
); );

View File

@@ -3,18 +3,18 @@ import { useTranslation } from 'react-i18next';
import { Box, BoxButton, StyledLink } from '@/components'; import { Box, BoxButton, StyledLink } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { PadsOrdering } from '@/features/pads/pad-management'; import { DocsOrdering } from '@/features/docs/doc-management';
import IconAdd from '../assets/icon-add.svg'; import IconAdd from '../assets/icon-add.svg';
import IconSort from '../assets/icon-sort.svg'; import IconSort from '../assets/icon-sort.svg';
import { usePadPanelStore } from '../store'; import { useDocPanelStore } from '../store';
export const PanelActions = () => { export const PanelActions = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { changeOrdering, ordering } = usePadPanelStore(); const { changeOrdering, ordering } = useDocPanelStore();
const { colorsTokens } = useCunninghamTheme(); const { colorsTokens } = useCunninghamTheme();
const isSortAsc = ordering === PadsOrdering.BY_CREATED_ON; const isSortAsc = ordering === DocsOrdering.BY_CREATED_ON;
return ( return (
<Box <Box

View File

@@ -0,0 +1 @@
export * from './useDocPanelStore';

View File

@@ -0,0 +1,19 @@
import { create } from 'zustand';
import { DocsOrdering } from '@/features/docs/doc-management/api';
interface DocPanelStore {
ordering: DocsOrdering;
changeOrdering: () => void;
}
export const useDocPanelStore = create<DocPanelStore>((set) => ({
ordering: DocsOrdering.BY_CREATED_ON_DESC,
changeOrdering: () =>
set(({ ordering }) => ({
ordering:
ordering === DocsOrdering.BY_CREATED_ON
? DocsOrdering.BY_CREATED_ON_DESC
: DocsOrdering.BY_CREATED_ON,
})),
}));

View File

@@ -0,0 +1,3 @@
export * from './doc-editor';
export * from './doc-management';
export * from './docs-panel';

View File

@@ -2,13 +2,13 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api'; import { APIError, errorCauses, fetchAPI } from '@/api';
import { User } from '@/core/auth'; import { User } from '@/core/auth';
import { KEY_LIST_DOC_ACCESSES } from '@/features/pads/members/members-grid/';
import { import {
Access, Access,
KEY_LIST_PAD, Doc,
Pad, KEY_LIST_DOC,
Role, Role,
} from '@/features/pads/pad-management'; } from '@/features/docs/doc-management';
import { KEY_LIST_DOC_ACCESSES } from '@/features/docs/members/members-grid/';
import { OptionType } from '../types'; import { OptionType } from '../types';
@@ -16,7 +16,7 @@ import { KEY_LIST_USER } from './useUsers';
interface CreateDocAccessParams { interface CreateDocAccessParams {
role: Role; role: Role;
docId: Pad['id']; docId: Doc['id'];
memberId: User['id']; memberId: User['id'];
} }
@@ -51,7 +51,7 @@ export function useCreateDocAccess() {
mutationFn: createDocAccess, mutationFn: createDocAccess,
onSuccess: () => { onSuccess: () => {
void queryClient.invalidateQueries({ void queryClient.invalidateQueries({
queryKey: [KEY_LIST_PAD], queryKey: [KEY_LIST_DOC],
}); });
void queryClient.resetQueries({ void queryClient.resetQueries({
queryKey: [KEY_LIST_USER], queryKey: [KEY_LIST_USER],

View File

@@ -2,14 +2,14 @@ import { useMutation } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api'; import { APIError, errorCauses, fetchAPI } from '@/api';
import { User } from '@/core/auth'; import { User } from '@/core/auth';
import { Pad, Role } from '@/features/pads/pad-management'; import { Doc, Role } from '@/features/docs/doc-management';
import { DocInvitation, OptionType } from '../types'; import { DocInvitation, OptionType } from '../types';
interface CreateDocInvitationParams { interface CreateDocInvitationParams {
email: User['email']; email: User['email'];
role: Role; role: Role;
docId: Pad['id']; docId: Doc['id'];
} }
export const createDocInvitation = async ({ export const createDocInvitation = async ({

View File

@@ -2,11 +2,11 @@ import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { APIError, APIList, errorCauses, fetchAPI } from '@/api'; import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
import { User } from '@/core/auth'; import { User } from '@/core/auth';
import { Pad } from '@/features/pads/pad-management'; import { Doc } from '@/features/docs/doc-management';
export type UsersParams = { export type UsersParams = {
query: string; query: string;
docId: Pad['id']; docId: Doc['id'];
}; };
type UsersResponse = APIList<User>; type UsersResponse = APIList<User>;

View File

@@ -1,7 +1,7 @@
import { Radio, RadioGroup } from '@openfun/cunningham-react'; import { Radio, RadioGroup } from '@openfun/cunningham-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Role } from '@/features/pads/pad-management'; import { Role } from '@/features/docs/doc-management';
interface ChooseRoleProps { interface ChooseRoleProps {
currentRole: Role; currentRole: Role;

View File

@@ -12,7 +12,7 @@ import { createGlobalStyle } from 'styled-components';
import { APIError } from '@/api'; import { APIError } from '@/api';
import { Box, Text } from '@/components'; import { Box, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { Pad, Role } from '@/features/pads/pad-management'; import { Doc, Role } from '@/features/docs/doc-management';
import { useCreateDocAccess, useCreateInvitation } from '../api'; import { useCreateDocAccess, useCreateInvitation } from '../api';
import IconAddUser from '../assets/add-user.svg'; import IconAddUser from '../assets/add-user.svg';
@@ -41,7 +41,7 @@ type APIErrorUser = APIError<{
interface ModalAddMembersProps { interface ModalAddMembersProps {
currentRole: Role; currentRole: Role;
onClose: () => void; onClose: () => void;
doc: Pad; doc: Doc;
} }
export const ModalAddMembers = ({ export const ModalAddMembers = ({

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Options } from 'react-select'; import { Options } from 'react-select';
import AsyncSelect from 'react-select/async'; import AsyncSelect from 'react-select/async';
import { Pad } from '@/features/pads/pad-management'; import { Doc } from '@/features/docs/doc-management';
import { isValidEmail } from '@/utils'; import { isValidEmail } from '@/utils';
import { KEY_LIST_USER, useUsers } from '../api/useUsers'; import { KEY_LIST_USER, useUsers } from '../api/useUsers';
@@ -12,7 +12,7 @@ import { OptionSelect, OptionType } from '../types';
export type OptionsSelect = Options<OptionSelect>; export type OptionsSelect = Options<OptionSelect>;
interface SearchUsersProps { interface SearchUsersProps {
doc: Pad; doc: Doc;
selectedUsers: OptionsSelect; selectedUsers: OptionsSelect;
setSelectedUsers: (value: OptionsSelect) => void; setSelectedUsers: (value: OptionsSelect) => void;
disabled?: boolean; disabled?: boolean;

View File

@@ -1,5 +1,5 @@
import { User } from '@/core/auth'; import { User } from '@/core/auth';
import { Pad, Role } from '@/features/pads/pad-management'; import { Doc, Role } from '@/features/docs/doc-management';
export enum OptionType { export enum OptionType {
INVITATION = 'invitation', INVITATION = 'invitation',
@@ -30,7 +30,7 @@ export interface DocInvitation {
id: string; id: string;
created_at: string; created_at: string;
email: string; email: string;
team: Pad['id']; team: Doc['id'];
role: Role; role: Role;
issuer: User['id']; issuer: User['id'];
} }

View File

@@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { Access, Pad, Role } from '@/features/pads/pad-management'; import { Access, Doc, Role } from '@/features/docs/doc-management';
import { AppWrapper } from '@/tests/utils'; import { AppWrapper } from '@/tests/utils';
import { MemberAction } from '../components/MemberAction'; import { MemberAction } from '../components/MemberAction';
@@ -23,7 +23,7 @@ const access: Access = {
const doc = { const doc = {
id: '123456', id: '123456',
title: 'teamName', title: 'teamName',
} as Pad; } as Doc;
describe('MemberAction', () => { describe('MemberAction', () => {
afterEach(() => { afterEach(() => {

View File

@@ -3,12 +3,12 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { Access, Pad, Role } from '@/features/pads/pad-management'; import { Access, Doc, Role } from '@/features/docs/doc-management';
import { AppWrapper } from '@/tests/utils'; import { AppWrapper } from '@/tests/utils';
import { MemberGrid } from '../components/MemberGrid'; import { MemberGrid } from '../components/MemberGrid';
const doc: Pad = { const doc: Doc = {
id: '123456', id: '123456',
title: 'teamName', title: 'teamName',
abilities: { abilities: {

View File

@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { useAuthStore } from '@/core/auth'; import { useAuthStore } from '@/core/auth';
import { Access, Role } from '@/features/pads/pad-management'; import { Access, Role } from '@/features/docs/doc-management';
import { AppWrapper } from '@/tests/utils'; import { AppWrapper } from '@/tests/utils';
import { ModalRole } from '../components/ModalRole'; import { ModalRole } from '../components/ModalRole';

View File

@@ -5,7 +5,7 @@ import {
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api'; import { APIError, errorCauses, fetchAPI } from '@/api';
import { KEY_LIST_PAD, KEY_PAD } from '@/features/pads/pad-management'; import { KEY_DOC, KEY_LIST_DOC } from '@/features/docs/doc-management';
import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses'; import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses';
@@ -46,10 +46,10 @@ export const useDeleteDocAccess = (options?: UseDeleteDocAccessOptions) => {
queryKey: [KEY_LIST_DOC_ACCESSES], queryKey: [KEY_LIST_DOC_ACCESSES],
}); });
void queryClient.invalidateQueries({ void queryClient.invalidateQueries({
queryKey: [KEY_PAD], queryKey: [KEY_DOC],
}); });
void queryClient.invalidateQueries({ void queryClient.invalidateQueries({
queryKey: [KEY_LIST_PAD], queryKey: [KEY_LIST_DOC],
}); });
if (options?.onSuccess) { if (options?.onSuccess) {
options.onSuccess(data, variables, context); options.onSuccess(data, variables, context);

View File

@@ -1,7 +1,7 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query'; import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { APIError, APIList, errorCauses, fetchAPI } from '@/api'; import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
import { Access } from '@/features/pads/pad-management'; import { Access } from '@/features/docs/doc-management';
export type DocAccessesAPIParams = { export type DocAccessesAPIParams = {
page: number; page: number;

View File

@@ -5,7 +5,7 @@ import {
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api'; import { APIError, errorCauses, fetchAPI } from '@/api';
import { Access, KEY_PAD, Role } from '@/features/pads/pad-management'; import { Access, KEY_DOC, Role } from '@/features/docs/doc-management';
import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses'; import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses';
@@ -52,7 +52,7 @@ export const useUpdateDocAccess = (options?: UseUpdateDocAccessOptions) => {
queryKey: [KEY_LIST_DOC_ACCESSES], queryKey: [KEY_LIST_DOC_ACCESSES],
}); });
void queryClient.invalidateQueries({ void queryClient.invalidateQueries({
queryKey: [KEY_PAD], queryKey: [KEY_DOC],
}); });
if (options?.onSuccess) { if (options?.onSuccess) {
options.onSuccess(data, variables, context); options.onSuccess(data, variables, context);

View File

@@ -3,7 +3,7 @@ import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Box, DropButton, IconOptions, Text } from '@/components'; import { Box, DropButton, IconOptions, Text } from '@/components';
import { Access, Pad, Role } from '@/features/pads/pad-management'; import { Access, Doc, Role } from '@/features/docs/doc-management';
import { ModalDelete } from './ModalDelete'; import { ModalDelete } from './ModalDelete';
import { ModalRole } from './ModalRole'; import { ModalRole } from './ModalRole';
@@ -11,7 +11,7 @@ import { ModalRole } from './ModalRole';
interface MemberActionProps { interface MemberActionProps {
access: Access; access: Access;
currentRole: Role; currentRole: Role;
doc: Pad; doc: Doc;
} }
export const MemberAction = ({ export const MemberAction = ({

View File

@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Card, TextErrors } from '@/components'; import { Card, TextErrors } from '@/components';
import { Pad, Role, currentDocRole } from '@/features/pads/pad-management'; import { Doc, Role, currentDocRole } from '@/features/docs/doc-management';
import { useDocAccesses } from '../api'; import { useDocAccesses } from '../api';
import { PAGE_SIZE } from '../conf'; import { PAGE_SIZE } from '../conf';
@@ -11,7 +11,7 @@ import { PAGE_SIZE } from '../conf';
import { MemberAction } from './MemberAction'; import { MemberAction } from './MemberAction';
interface MemberGridProps { interface MemberGridProps {
doc: Pad; doc: Doc;
} }
// FIXME : ask Cunningham to export this type // FIXME : ask Cunningham to export this type

View File

@@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next';
import IconUser from '@/assets/icons/icon-user.svg'; import IconUser from '@/assets/icons/icon-user.svg';
import { Box, Text, TextErrors } from '@/components'; import { Box, Text, TextErrors } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { Access, Pad, Role } from '@/features/pads/pad-management'; import { Access, Doc, Role } from '@/features/docs/doc-management';
import { useDeleteDocAccess } from '../api'; import { useDeleteDocAccess } from '../api';
import IconRemoveMember from '../assets/icon-remove-member.svg'; import IconRemoveMember from '../assets/icon-remove-member.svg';
@@ -22,7 +22,7 @@ interface ModalDeleteProps {
access: Access; access: Access;
currentRole: Role; currentRole: Role;
onClose: () => void; onClose: () => void;
doc: Pad; doc: Doc;
} }
export const ModalDelete = ({ access, onClose, doc }: ModalDeleteProps) => { export const ModalDelete = ({ access, onClose, doc }: ModalDeleteProps) => {

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components'; import { createGlobalStyle } from 'styled-components';
import { Box, Text } from '@/components'; import { Box, Text } from '@/components';
import { Pad } from '@/features/pads/pad-management'; import { Doc } from '@/features/docs/doc-management';
import { MemberGrid } from './MemberGrid'; import { MemberGrid } from './MemberGrid';
@@ -15,7 +15,7 @@ const GlobalStyle = createGlobalStyle`
interface ModalGridMembersProps { interface ModalGridMembersProps {
onClose: () => void; onClose: () => void;
doc: Pad; doc: Doc;
} }
export const ModalGridMembers = ({ doc, onClose }: ModalGridMembersProps) => { export const ModalGridMembers = ({ doc, onClose }: ModalGridMembersProps) => {

View File

@@ -10,7 +10,7 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Box, Text, TextErrors } from '@/components'; import { Box, Text, TextErrors } from '@/components';
import { Access, Role } from '@/features/pads/pad-management'; import { Access, Role } from '@/features/docs/doc-management';
import { ChooseRole } from '../../members-add/components/ChooseRole'; import { ChooseRole } from '../../members-add/components/ChooseRole';
import { useUpdateDocAccess } from '../api'; import { useUpdateDocAccess } from '../api';

View File

@@ -1,5 +1,5 @@
import { useAuthStore } from '@/core/auth'; import { useAuthStore } from '@/core/auth';
import { Access, Role } from '@/features/pads/pad-management'; import { Access, Role } from '@/features/docs/doc-management';
export const useWhoAmI = (access: Access) => { export const useWhoAmI = (access: Access) => {
const { userData } = useAuthStore(); const { userData } = useAuthStore();

View File

@@ -1,3 +0,0 @@
export * from './pad-editor';
export * from './pad-management';
export * from './pads-panel';

View File

@@ -1 +0,0 @@
export * from './PadEditor';

View File

@@ -1 +0,0 @@
export * from './usePadStore';

View File

@@ -1,66 +0,0 @@
import { BlockNoteEditor } from '@blocknote/core';
import { WebrtcProvider } from 'y-webrtc';
import * as Y from 'yjs';
import { create } from 'zustand';
import { signalingUrl } from '@/core';
import { Base64, Pad } from '@/features/pads/pad-management';
export interface PadStore {
padsStore: {
[padId: Pad['id']]: {
provider: WebrtcProvider;
editor?: BlockNoteEditor;
};
};
createProvider: (padId: Pad['id'], initialDoc: Base64) => WebrtcProvider;
setEditor: (padId: Pad['id'], editor: BlockNoteEditor) => void;
}
const initialState = {
padsStore: {},
};
export const usePadStore = create<PadStore>((set) => ({
padsStore: initialState.padsStore,
createProvider: (padId: string, initialDoc: Base64) => {
const doc = new Y.Doc({
guid: padId,
});
if (initialDoc) {
Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64'));
}
const provider = new WebrtcProvider(padId, doc, {
signaling: [signalingUrl(padId)],
maxConns: 5,
});
set(({ padsStore }) => {
return {
padsStore: {
...padsStore,
[padId]: {
provider,
},
},
};
});
return provider;
},
setEditor: (padId, editor) => {
set(({ padsStore }) => {
return {
padsStore: {
...padsStore,
[padId]: {
...padsStore[padId],
editor,
},
},
};
});
},
}));

View File

@@ -1,3 +0,0 @@
export * from './usePad';
export * from './usePads';
export * from './useUpdatePad';

View File

@@ -1,42 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { KEY_LIST_PAD, Pad } from '@/features/pads';
type CreatePadParam = Pick<Pad, 'title' | 'is_public'>;
export const createPad = async ({
title,
is_public,
}: CreatePadParam): Promise<Pad> => {
const response = await fetchAPI(`documents/`, {
method: 'POST',
body: JSON.stringify({
title,
is_public,
}),
});
if (!response.ok) {
throw new APIError('Failed to create the pad', await errorCauses(response));
}
return response.json() as Promise<Pad>;
};
interface CreatePadProps {
onSuccess: (data: Pad) => void;
}
export function useCreatePad({ onSuccess }: CreatePadProps) {
const queryClient = useQueryClient();
return useMutation<Pad, APIError, CreatePadParam>({
mutationFn: createPad,
onSuccess: (data) => {
void queryClient.invalidateQueries({
queryKey: [KEY_LIST_PAD],
});
onSuccess(data);
},
});
}

View File

@@ -1,32 +0,0 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Pad } from '../types';
export type PadParams = {
id: string;
};
export const getPad = async ({ id }: PadParams): Promise<Pad> => {
const response = await fetchAPI(`documents/${id}/`);
if (!response.ok) {
throw new APIError('Failed to get the pad', await errorCauses(response));
}
return response.json() as Promise<Pad>;
};
export const KEY_PAD = 'pad';
export function usePad(
param: PadParams,
queryConfig?: UseQueryOptions<Pad, APIError, Pad>,
) {
return useQuery<Pad, APIError, Pad>({
queryKey: [KEY_PAD, param],
queryFn: () => getPad(param),
...queryConfig,
});
}

View File

@@ -1,3 +0,0 @@
export * from './CardCreatePad';
export * from './ModalRemovePad';
export * from './ModalUpdatePad';

View File

@@ -1 +0,0 @@
export * from './PadToolBox';

View File

@@ -1 +0,0 @@
export * from './usePadPanelStore';

View File

@@ -1,19 +0,0 @@
import { create } from 'zustand';
import { PadsOrdering } from '@/features/pads/pad-management/api';
interface PadPanelStore {
ordering: PadsOrdering;
changeOrdering: () => void;
}
export const usePadPanelStore = create<PadPanelStore>((set) => ({
ordering: PadsOrdering.BY_CREATED_ON_DESC,
changeOrdering: () =>
set(({ ordering }) => ({
ordering:
ordering === PadsOrdering.BY_CREATED_ON
? PadsOrdering.BY_CREATED_ON_DESC
: PadsOrdering.BY_CREATED_ON,
})),
}));

View File

@@ -37,6 +37,7 @@
"Docs": "Docs", "Docs": "Docs",
"Docs Description": "Description de Docs", "Docs Description": "Description de Docs",
"Docs Logo": "Logo Docs", "Docs Logo": "Logo Docs",
"Docs icon": "Icône Docs",
"Document icon": "Icône du document", "Document icon": "Icône du document",
"Document name": "Nom du document", "Document name": "Nom du document",
"Documents": "Documents", "Documents": "Documents",
@@ -44,7 +45,7 @@
"E-mail:": "E-mail:", "E-mail:": "E-mail:",
"Editor": "Éditeur", "Editor": "Éditeur",
"Emails": "Emails", "Emails": "Emails",
"Empty pads icon": "Icône des pads vides", "Empty docs icon": "Icône de docs vide",
"Enter the new name of the selected document.": "Entrez le nouveau nom du document sélectionné.", "Enter the new name of the selected document.": "Entrez le nouveau nom du document sélectionné.",
"Established on December 20, 2023.": "Établi le 20 décembre 2023.", "Established on December 20, 2023.": "Établi le 20 décembre 2023.",
"Failed to add the member in the document.": "Impossible d'ajouter le membre dans le document.", "Failed to add the member in the document.": "Impossible d'ajouter le membre dans le document.",
@@ -84,7 +85,6 @@
"Open the modal to update the role of this member": "Ouvrir la fenêtre modale pour mettre à jour le rôle de ce membre", "Open the modal to update the role of this member": "Ouvrir la fenêtre modale pour mettre à jour le rôle de ce membre",
"Ouch !": "Aïe !", "Ouch !": "Aïe !",
"Owner": "Propriétaire", "Owner": "Propriétaire",
"Pads icon": "Icône de pads",
"Personal data and cookies": "Données personnelles et cookies", "Personal data and cookies": "Données personnelles et cookies",
"Publication Director": "Directeur de la publication", "Publication Director": "Directeur de la publication",
"Publisher": "Éditeur", "Publisher": "Éditeur",

View File

@@ -2,11 +2,11 @@ import { PropsWithChildren } from 'react';
import { Box } from '@/components'; import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { Panel } from '@/features/pads/pads-panel'; import { Panel } from '@/features/docs/docs-panel';
import { MainLayout } from './MainLayout'; import { MainLayout } from './MainLayout';
export function PadLayout({ children }: PropsWithChildren) { export function DocLayout({ children }: PropsWithChildren) {
const { colorsTokens } = useCunninghamTheme(); const { colorsTokens } = useCunninghamTheme();
return ( return (

View File

@@ -1,3 +1,3 @@
export * from './MainLayout'; export * from './MainLayout';
export * from './PadLayout'; export * from './DocLayout';
export * from './PageLayout'; export * from './PageLayout';

View File

@@ -4,9 +4,9 @@ import { useRouter } from 'next/router';
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { Box, Text, TextErrors } from '@/components/'; import { Box, Text, TextErrors } from '@/components/';
import { PadEditor } from '@/features/pads/pad-editor'; import { DocEditor } from '@/features/docs/doc-editor';
import { usePad } from '@/features/pads/pad-management'; import { useDoc } from '@/features/docs/doc-management';
import { PadLayout } from '@/layouts'; import { DocLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next'; import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => { const Page: NextPageWithLayout = () => {
@@ -18,15 +18,15 @@ const Page: NextPageWithLayout = () => {
return null; return null;
} }
return <Pad id={id} />; return <Doc id={id} />;
}; };
interface PadProps { interface DocProps {
id: string; id: string;
} }
const Pad = ({ id }: PadProps) => { const Doc = ({ id }: DocProps) => {
const { data: pad, isLoading, isError, error } = usePad({ id }); const { data: doc, isLoading, isError, error } = useDoc({ id });
const navigate = useNavigate(); const navigate = useNavigate();
if (isError && error) { if (isError && error) {
@@ -51,7 +51,7 @@ const Pad = ({ id }: PadProps) => {
); );
} }
if (isLoading || !pad) { if (isLoading || !doc) {
return ( return (
<Box $align="center" $justify="center" $height="100%"> <Box $align="center" $justify="center" $height="100%">
<Loader /> <Loader />
@@ -59,11 +59,11 @@ const Pad = ({ id }: PadProps) => {
); );
} }
return <PadEditor pad={pad} />; return <DocEditor doc={doc} />;
}; };
Page.getLayout = function getLayout(page: ReactElement) { Page.getLayout = function getLayout(page: ReactElement) {
return <PadLayout>{page}</PadLayout>; return <DocLayout>{page}</DocLayout>;
}; };
export default Page; export default Page;

View File

@@ -1,20 +1,20 @@
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { Box } from '@/components'; import { Box } from '@/components';
import { CardCreatePad } from '@/features/pads/pad-management'; import { CardCreateDoc } from '@/features/docs/doc-management';
import { PadLayout } from '@/layouts'; import { DocLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next'; import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => { const Page: NextPageWithLayout = () => {
return ( return (
<Box $padding="large" $justify="center" $align="start" $height="inherit"> <Box $padding="large" $justify="center" $align="start" $height="inherit">
<CardCreatePad /> <CardCreateDoc />
</Box> </Box>
); );
}; };
Page.getLayout = function getLayout(page: ReactElement) { Page.getLayout = function getLayout(page: ReactElement) {
return <PadLayout>{page}</PadLayout>; return <DocLayout>{page}</DocLayout>;
}; };
export default Page; export default Page;

Some files were not shown because too many files have changed in this diff Show More