✏️(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

@@ -26,9 +26,9 @@ export const randomName = (name: string, browserName: string, length: number) =>
return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`;
});
export const createPad = async (
export const createDoc = async (
page: Page,
padName: string,
docName: string,
browserName: string,
length: number,
isPublic: boolean = false,
@@ -38,11 +38,11 @@ export const createPad = async (
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 page.getByText('Document name').fill(randomPads[i]);
await page.getByText('Document name').fill(randomDocs[i]);
if (isPublic) {
await page.getByText('Is it public ?').click();
@@ -50,10 +50,10 @@ export const createPad = async (
await expect(buttonCreate).toBeEnabled();
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 (

View File

@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
import { createPad, randomName } from './common';
import { createDoc, randomName } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
@@ -12,7 +12,7 @@ test.describe('Document add users', () => {
(response) =>
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.getByRole('button', { name: 'Add members' }).click();
@@ -73,7 +73,7 @@ test.describe('Document add users', () => {
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.getByRole('button', { name: 'Add members' }).click();
@@ -125,7 +125,7 @@ test.describe('Document add users', () => {
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.getByRole('button', { name: 'Add members' }).click();
@@ -165,7 +165,7 @@ test.describe('Document add users', () => {
page,
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.getByRole('button', { name: 'Add members' }).click();

View File

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

View File

@@ -1,26 +1,26 @@
import { expect, test } from '@playwright/test';
import { createPad } from './common';
import { createDoc } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test.describe('Pad Editor', () => {
test('checks the Pad Editor interact correctly', async ({
test.describe('Doc Editor', () => {
test('checks the Doc Editor interact correctly', async ({
page,
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').fill('Hello World');
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,
browserName,
}) => {
@@ -28,8 +28,8 @@ test.describe('Pad Editor', () => {
return webSocket.url().includes('ws://localhost:4444/');
});
const randomPad = await createPad(page, 'pad-editor', browserName, 1);
await expect(page.locator('h2').getByText(randomPad[0])).toBeVisible();
const randomDoc = await createDoc(page, 'doc-editor', browserName, 1);
await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible();
const webSocket = await webSocketPromise;
expect(webSocket.url()).toContain('ws://localhost:4444/');
@@ -52,9 +52,9 @@ test.describe('Pad Editor', () => {
page,
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
@@ -74,57 +74,57 @@ test.describe('Pad Editor', () => {
).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,
browserName,
}) => {
const [firstPad, secondPad] = await createPad(
const [firstDoc, secondDoc] = await createDoc(
page,
'pad-multiple',
'doc-multiple',
browserName,
2,
);
const panel = page.getByLabel('Documents panel').first();
// Check the first pad
await panel.getByText(firstPad).click();
await expect(page.locator('h2').getByText(firstPad)).toBeVisible();
// Check the first doc
await panel.getByText(firstDoc).click();
await expect(page.locator('h2').getByText(firstDoc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World Pad 1');
await expect(page.getByText('Hello World Pad 1')).toBeVisible();
await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 1');
await expect(page.getByText('Hello World Doc 1')).toBeVisible();
// Check the second pad
await panel.getByText(secondPad).click();
await expect(page.locator('h2').getByText(secondPad)).toBeVisible();
await expect(page.getByText('Hello World Pad 1')).toBeHidden();
// Check the second doc
await panel.getByText(secondDoc).click();
await expect(page.locator('h2').getByText(secondDoc)).toBeVisible();
await expect(page.getByText('Hello World Doc 1')).toBeHidden();
await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World Pad 2');
await expect(page.getByText('Hello World Pad 2')).toBeVisible();
await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 2');
await expect(page.getByText('Hello World Doc 2')).toBeVisible();
// Check the first pad again
await panel.getByText(firstPad).click();
await expect(page.locator('h2').getByText(firstPad)).toBeVisible();
await expect(page.getByText('Hello World Pad 2')).toBeHidden();
await expect(page.getByText('Hello World Pad 1')).toBeVisible();
// Check the first doc again
await panel.getByText(firstDoc).click();
await expect(page.locator('h2').getByText(firstDoc)).toBeVisible();
await expect(page.getByText('Hello World Doc 2')).toBeHidden();
await expect(page.getByText('Hello World Doc 1')).toBeVisible();
});
test('it saves the doc when we change pages', async ({
page,
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();
// Check the first pad
await panel.getByText(pad).click();
await expect(page.locator('h2').getByText(pad)).toBeVisible();
// Check the first doc
await panel.getByText(doc).click();
await expect(page.locator('h2').getByText(doc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click();
await page
.locator('.ProseMirror.bn-editor')
.fill('Hello World Pad persisted 1');
await expect(page.getByText('Hello World Pad persisted 1')).toBeVisible();
.fill('Hello World Doc persisted 1');
await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible();
await panel
.getByRole('button', {
@@ -142,33 +142,33 @@ test.describe('Pad Editor', () => {
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 }) => {
// eslint-disable-next-line playwright/no-skipped-test
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();
// Check the first pad
await panel.getByText(pad).click();
await expect(page.locator('h2').getByText(pad)).toBeVisible();
// Check the first doc
await panel.getByText(doc).click();
await expect(page.locator('h2').getByText(doc)).toBeVisible();
await page.locator('.ProseMirror.bn-editor').click();
await page
.locator('.ProseMirror.bn-editor')
.fill('Hello World Pad persisted 2');
await expect(page.getByText('Hello World Pad persisted 2')).toBeVisible();
.fill('Hello World Doc persisted 2');
await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible();
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 }) => {
@@ -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(
page.getByText(

View File

@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
import { addNewMember, createPad } from './common';
import { addNewMember, createDoc } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
@@ -11,7 +11,7 @@ test.describe('Members Delete', () => {
page,
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.getByRole('button', { name: 'Manage members' }).click();
@@ -37,7 +37,7 @@ test.describe('Members Delete', () => {
page,
browserName,
}) => {
await createPad(page, 'member-delete-2', browserName, 1);
await createDoc(page, 'member-delete-2', browserName, 1);
await addNewMember(page, 0, 'Owner');
@@ -64,7 +64,7 @@ test.describe('Members Delete', () => {
});
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');
@@ -88,7 +88,7 @@ test.describe('Members Delete', () => {
});
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');
@@ -116,7 +116,7 @@ test.describe('Members Delete', () => {
page,
browserName,
}) => {
await createPad(page, 'member-delete-5', browserName, 1);
await createDoc(page, 'member-delete-5', browserName, 1);
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 }) => {
await createPad(page, 'member-delete-6', browserName, 1);
await createDoc(page, 'member-delete-6', browserName, 1);
// To not be the only owner
await addNewMember(page, 0, 'Owner');

View File

@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
import { createPad } from './common';
import { createDoc } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
@@ -8,7 +8,7 @@ test.beforeEach(async ({ page }) => {
test.describe('Document grid members', () => {
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.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.getByRole('button', { name: 'Manage members' }).click();

View File

@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
import { waitForElementCount } from '../helpers';
import { createPad } from './common';
import { createDoc } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
@@ -146,16 +146,16 @@ test.describe('Documents Panel', () => {
test('checks the hover and selected state', async ({ page, browserName }) => {
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);
await expect(selectedPad).toHaveCSS(
const selectedDoc = panel.locator('li').nth(0);
await expect(selectedDoc).toHaveCSS(
'background-color',
'rgb(202, 202, 251)',
);
const hoverPad = panel.locator('li').nth(1);
await hoverPad.hover();
await expect(hoverPad).toHaveCSS('background-color', 'rgb(227, 227, 253)');
const hoverDoc = panel.locator('li').nth(1);
await hoverDoc.hover();
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 pdf from 'pdf-parse';
import { createPad } from './common';
import { createDoc } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test.describe('Pad Tools', () => {
test('it converts the pad to pdf with a template integrated', async ({
test.describe('Doc Tools', () => {
test('it converts the doc to pdf with a template integrated', async ({
page,
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) => {
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').fill('Hello World');
@@ -38,19 +38,19 @@ test.describe('Pad Tools', () => {
.click();
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 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 ({
page,
browserName,
}) => {
const [randomPad] = await createPad(page, 'pad-editor', browserName, 1);
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
let body = '';
await page.route('**/templates/*/generate-document/', async (route) => {
@@ -60,7 +60,7 @@ test.describe('Pad Tools', () => {
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().click();
@@ -85,15 +85,15 @@ test.describe('Pad Tools', () => {
expect(body).toContain('<br/><br/>');
});
test('it updates the pad', async ({ page, browserName }) => {
const [randomPad] = await createPad(
test('it updates the doc', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(
page,
'pad-update',
'doc-update',
browserName,
1,
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
@@ -103,14 +103,14 @@ test.describe('Pad Tools', () => {
.click();
await expect(
page.locator('h2').getByText(`Update document "${randomPad}"`),
page.locator('h2').getByText(`Update document "${randomDoc}"`),
).toBeVisible();
await expect(
page.getByRole('checkbox', { name: 'Is it public ?' }),
).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
@@ -125,7 +125,7 @@ test.describe('Pad Tools', () => {
const panel = page.getByLabel('Documents panel').first();
await expect(
panel.locator('li').getByText(`${randomPad}-updated`),
panel.locator('li').getByText(`${randomDoc}-updated`),
).toBeVisible();
await page.getByLabel('Open the document options').click();
@@ -140,9 +140,9 @@ test.describe('Pad Tools', () => {
).not.toBeChecked();
});
test('it deletes the pad', async ({ page, browserName }) => {
const [randomPad] = await createPad(page, 'pad-delete', browserName, 1);
await expect(page.locator('h2').getByText(randomPad)).toBeVisible();
test('it deletes the doc', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-delete', browserName, 1);
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
await page.getByLabel('Open the document options').click();
await page
@@ -152,7 +152,7 @@ test.describe('Pad Tools', () => {
.click();
await expect(
page.locator('h2').getByText(`Deleting the document "${randomPad}"`),
page.locator('h2').getByText(`Deleting the document "${randomDoc}"`),
).toBeVisible();
await page
@@ -170,7 +170,7 @@ test.describe('Pad Tools', () => {
).toBeVisible();
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 ({
@@ -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();
@@ -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();
@@ -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();

View File

@@ -6,10 +6,10 @@ export const baseApiUrl = (apiVersion: string = '1.0') => {
return `${origin}/api/v${apiVersion}/`;
};
export const signalingUrl = (padId: string) => {
export const signalingUrl = (docId: string) => {
const base =
process.env.NEXT_PUBLIC_SIGNALING_URL ||
(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 { useAuthStore } from '@/core/auth';
import { Pad } from '@/features/pads/pad-management';
import { Doc } from '@/features/docs/doc-management';
import useSavePad from '../hook/useSavePad';
import { usePadStore } from '../stores';
import useSaveDoc from '../hook/useSaveDoc';
import { useDocStore } from '../stores';
import { randomColor } from '../utils';
import { BlockNoteToolbar } from './BlockNoteToolbar';
interface BlockNoteEditorProps {
pad: Pad;
doc: Doc;
}
export const BlockNoteEditor = ({ pad }: BlockNoteEditorProps) => {
const { createProvider, padsStore } = usePadStore();
const provider = padsStore?.[pad.id]?.provider;
export const BlockNoteEditor = ({ doc }: BlockNoteEditorProps) => {
const { createProvider, docsStore } = useDocStore();
const provider = docsStore?.[doc.id]?.provider;
if (!provider) {
createProvider(pad.id, pad.content);
createProvider(doc.id, doc.content);
return null;
}
return <BlockNoteContent pad={pad} provider={provider} />;
return <BlockNoteContent doc={doc} provider={provider} />;
};
interface BlockNoteContentProps {
pad: Pad;
doc: Doc;
provider: WebrtcProvider;
}
export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => {
export const BlockNoteContent = ({ doc, provider }: BlockNoteContentProps) => {
const { userData } = useAuthStore();
const { setEditor, padsStore } = usePadStore();
useSavePad(pad.id, provider.doc, pad.abilities.partial_update);
const { setEditor, docsStore } = useDocStore();
useSaveDoc(doc.id, provider.doc, doc.abilities.partial_update);
const storedEditor = padsStore?.[pad.id]?.editor;
const storedEditor = docsStore?.[doc.id]?.editor;
const editor = useMemo(() => {
if (storedEditor) {
return storedEditor;
@@ -60,8 +60,8 @@ export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => {
}, [provider, storedEditor, userData?.email]);
useEffect(() => {
setEditor(pad.id, editor);
}, [setEditor, pad.id, editor]);
setEditor(doc.id, editor);
}, [setEditor, doc.id, editor]);
return (
<Box
@@ -77,7 +77,7 @@ export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => {
<BlockNoteView
editor={editor}
formattingToolbar={false}
editable={pad.abilities.partial_update}
editable={doc.abilities.partial_update}
>
<BlockNoteToolbar />
</BlockNoteView>

View File

@@ -2,16 +2,16 @@ import { Alert, VariantType } from '@openfun/cunningham-react';
import React from 'react';
import { Box, Card, Text } from '@/components';
import { Pad } from '@/features/pads/pad-management';
import { PadToolBox } from '@/features/pads/pad-tools';
import { Doc } from '@/features/docs/doc-management';
import { DocToolBox } from '@/features/docs/doc-tools';
import { BlockNoteEditor } from './BlockNoteEditor';
interface PadEditorProps {
pad: Pad;
interface DocEditorProps {
doc: Doc;
}
export const PadEditor = ({ pad }: PadEditorProps) => {
export const DocEditor = ({ doc }: DocEditorProps) => {
return (
<>
<Box
@@ -21,11 +21,11 @@ export const PadEditor = ({ pad }: PadEditorProps) => {
$position="relative"
>
<Text as="h2" $align="center" $margin="auto">
{pad.title}
{doc.title}
</Text>
<PadToolBox pad={pad} />
<DocToolBox doc={doc} />
</Box>
{!pad.abilities.partial_update && (
{!doc.abilities.partial_update && (
<Box className="m-b" $css="margin-top:0;">
<Alert
type={VariantType.WARNING}
@@ -38,7 +38,7 @@ export const PadEditor = ({ pad }: PadEditorProps) => {
$css="flex:1;"
$overflow="auto"
>
<BlockNoteEditor pad={pad} />
<BlockNoteEditor doc={doc} />
</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 * as Y from 'yjs';
import { useUpdatePad } from '@/features/pads/pad-management/';
import { useUpdateDoc } from '@/features/docs/doc-management/';
import { toBase64 } from '../utils';
const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
const { mutate: updatePad } = useUpdatePad();
const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => {
const { mutate: updateDoc } = useUpdateDoc();
const [initialDoc, setInitialDoc] = useState<string>(
toBase64(Y.encodeStateAsUpdate(doc)),
);
@@ -40,7 +40,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
};
}, [doc]);
const savePad = useCallback(() => {
const saveDoc = useCallback(() => {
const newDoc = toBase64(Y.encodeStateAsUpdate(doc));
/**
@@ -52,11 +52,11 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
setInitialDoc(newDoc);
updatePad({
id: padId,
updateDoc({
id: docId,
content: newDoc,
});
}, [initialDoc, padId, doc, updatePad, canSave]);
}, [initialDoc, docId, doc, updateDoc, canSave]);
const timeout = useRef<NodeJS.Timeout>();
const router = useRouter();
@@ -67,7 +67,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
}
const onSave = () => {
savePad();
saveDoc();
};
// Save every minute
@@ -82,7 +82,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
removeEventListener('beforeunload', 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';
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_DESC = '-created_at',
}
export type PadsParams = {
ordering: PadsOrdering;
export type DocsParams = {
ordering: DocsOrdering;
};
type PadsAPIParams = PadsParams & {
type DocsAPIParams = DocsParams & {
page: number;
};
type PadsResponse = APIList<Pad>;
type DocsResponse = APIList<Doc>;
export const getPads = async ({
export const getDocs = async ({
ordering,
page,
}: PadsAPIParams): Promise<PadsResponse> => {
}: DocsAPIParams): Promise<DocsResponse> => {
const orderingQuery = ordering ? `&ordering=${ordering}` : '';
const response = await fetchAPI(`documents/?page=${page}${orderingQuery}`);
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(
param: PadsParams,
export function useDocs(
param: DocsParams,
queryConfig?: DefinedInitialDataInfiniteOptions<
PadsResponse,
DocsResponse,
APIError,
InfiniteData<PadsResponse>,
InfiniteData<DocsResponse>,
QueryKey,
number
>,
) {
return useInfiniteQuery<
PadsResponse,
DocsResponse,
APIError,
InfiniteData<PadsResponse>,
InfiniteData<DocsResponse>,
QueryKey,
number
>({
initialPageParam: 1,
queryKey: [KEY_LIST_PAD, param],
queryKey: [KEY_LIST_DOC, param],
queryFn: ({ pageParam }) =>
getPads({
getDocs({
...param,
page: pageParam,
}),

View File

@@ -6,32 +6,32 @@ import {
import { APIError, errorCauses, fetchAPI } from '@/api';
import { KEY_LIST_PAD } from './usePads';
import { KEY_LIST_DOC } from './useDocs';
interface RemovePadProps {
padId: string;
interface RemoveDocProps {
docId: string;
}
export const removePad = async ({ padId }: RemovePadProps): Promise<void> => {
const response = await fetchAPI(`documents/${padId}/`, {
export const removeDoc = async ({ docId }: RemoveDocProps): Promise<void> => {
const response = await fetchAPI(`documents/${docId}/`, {
method: 'DELETE',
});
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();
return useMutation<void, APIError, RemovePadProps>({
mutationFn: removePad,
return useMutation<void, APIError, RemoveDocProps>({
mutationFn: removeDoc,
...options,
onSuccess: (data, variables, context) => {
void queryClient.invalidateQueries({
queryKey: [KEY_LIST_PAD],
queryKey: [KEY_LIST_DOC],
});
if (options?.onSuccess) {
options.onSuccess(data, variables, context);

View File

@@ -1,15 +1,15 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Pad } from '@/features/pads';
import { Doc } from '@/features/docs';
export type UpdatePadParams = Pick<Pad, 'id'> &
Partial<Pick<Pad, 'content' | 'title' | 'is_public'>>;
export type UpdateDocParams = Pick<Doc, 'id'> &
Partial<Pick<Doc, 'content' | 'title' | 'is_public'>>;
export const updatePad = async ({
export const updateDoc = async ({
id,
...params
}: UpdatePadParams): Promise<Pad> => {
}: UpdateDocParams): Promise<Doc> => {
const response = await fetchAPI(`documents/${id}/`, {
method: 'PATCH',
body: JSON.stringify({
@@ -18,24 +18,24 @@ export const updatePad = async ({
});
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 {
onSuccess?: (data: Pad) => void;
interface UpdateDocProps {
onSuccess?: (data: Doc) => void;
listInvalideQueries?: string[];
}
export function useUpdatePad({
export function useUpdateDoc({
onSuccess,
listInvalideQueries,
}: UpdatePadProps = {}) {
}: UpdateDocProps = {}) {
const queryClient = useQueryClient();
return useMutation<Pad, APIError, UpdatePadParams>({
mutationFn: updatePad,
return useMutation<Doc, APIError, UpdateDocParams>({
mutationFn: updateDoc,
onSuccess: (data) => {
listInvalideQueries?.forEach((queryKey) => {
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 { 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 router = useRouter();
const {
mutate: createPad,
mutate: createDoc,
isError,
isPending,
error,
} = useCreatePad({
onSuccess: (pad) => {
router.push(`/docs/${pad.id}`);
} = useCreateDoc({
onSuccess: (doc) => {
router.push(`/docs/${doc.id}`);
},
});
const [padName, setPadName] = useState('');
const [padPublic, setPadPublic] = useState(false);
const [docName, setDocName] = useState('');
const [docPublic, setDocPublic] = useState(false);
const { colorsTokens } = useCunninghamTheme();
return (
@@ -49,14 +49,14 @@ export const CardCreatePad = () => {
{t('Name the document')}
</Text>
</Box>
<InputPadName
<InputDocName
label={t('Document name')}
{...{ error, isError, isPending, setPadName }}
{...{ error, isError, isPending, setDocName }}
/>
<Switch
label={t('Is it public ?')}
labelSide="right"
onChange={() => setPadPublic(!padPublic)}
onChange={() => setDocPublic(!docPublic)}
/>
</Box>
<Box $justify="space-between" $direction="row" $align="center">
@@ -64,8 +64,8 @@ export const CardCreatePad = () => {
<Button color="secondary">{t('Cancel')}</Button>
</StyledLink>
<Button
onClick={() => createPad({ title: padName, is_public: padPublic })}
disabled={!padName}
onClick={() => createDoc({ title: docName, is_public: docPublic })}
disabled={!docName}
>
{t('Create the document')}
</Button>

View File

@@ -4,23 +4,23 @@ import { useEffect, useState } from 'react';
import { APIError } from '@/api';
import { Box, TextErrors } from '@/components';
interface InputPadNameProps {
interface InputDocNameProps {
error: APIError | null;
isError: boolean;
isPending: boolean;
label: string;
setPadName: (newPadName: string) => void;
setDocName: (newDocName: string) => void;
defaultValue?: string;
}
export const InputPadName = ({
export const InputDocName = ({
defaultValue,
error,
isError,
isPending,
label,
setPadName,
}: InputPadNameProps) => {
setDocName,
}: InputDocNameProps) => {
const [isInputError, setIsInputError] = useState(isError);
useEffect(() => {
@@ -37,7 +37,7 @@ export const InputPadName = ({
label={label}
defaultValue={defaultValue}
onChange={(e) => {
setPadName(e.target.value);
setDocName(e.target.value);
setIsInputError(false);
}}
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 useCunninghamTheme from '@/cunningham/useCunninghamTheme';
import { useRemovePad } from '../api/useRemovePad';
import IconPad from '../assets/icon-pad.svg';
import { useRemoveDoc } from '../api/useRemoveDoc';
import IconDoc from '../assets/icon-doc.svg';
import IconRemove from '../assets/icon-trash.svg';
import { Pad } from '../types';
import { Doc } from '../types';
interface ModalRemovePadProps {
interface ModalRemoveDocProps {
onClose: () => void;
pad: Pad;
doc: Doc;
}
export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
export const ModalRemoveDoc = ({ onClose, doc }: ModalRemoveDocProps) => {
const { colorsTokens } = useCunninghamTheme();
const { toast } = useToastProvider();
const router = useRouter();
const {
mutate: removePad,
mutate: removeDoc,
isError,
error,
} = useRemovePad({
} = useRemoveDoc({
onSuccess: () => {
toast(t('The document has been deleted.'), VariantType.SUCCESS, {
duration: 4000,
@@ -62,8 +62,8 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
color="primary"
fullWidth
onClick={() =>
removePad({
padId: pad.id,
removeDoc({
docId: doc.id,
})
}
>
@@ -75,7 +75,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
<Box $align="center" $gap="1rem">
<IconRemove width={48} color={colorsTokens()['primary-text']} />
<Text as="h2" $size="h3" $margin="none">
{t('Deleting the document "{{title}}"', { title: pad.title })}
{t('Deleting the document "{{title}}"', { title: doc.title })}
</Text>
</Box>
}
@@ -88,7 +88,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
<Alert canClose={false} type={VariantType.WARNING}>
<Text>
{t('Are you sure you want to delete the document "{{title}}"?', {
title: pad.title,
title: doc.title,
})}
</Text>
</Alert>
@@ -106,7 +106,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
$align="center"
$radius="2px"
>
<IconPad
<IconDoc
className="p-t"
aria-label={t(`Document icon`)}
color={colorsTokens()['primary-500']}
@@ -118,7 +118,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
}}
/>
<Text $theme="primary" $weight="bold" $size="l">
{pad.title}
{doc.title}
</Text>
</Text>
</Box>

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import fetchMock from 'fetch-mock';
import { AppWrapper } from '@/tests/utils';
import { PadList } from '../components/PadList';
import { DocList } from '../components/DocList';
import { Panel } from '../components/Panel';
window.HTMLElement.prototype.scroll = function () {};
@@ -17,18 +17,18 @@ jest.mock('next/router', () => ({
}),
}));
describe('PanelPads', () => {
describe('PanelDocs', () => {
afterEach(() => {
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`, {
count: 0,
results: [],
});
render(<PadList />, { wrapper: AppWrapper });
render(<DocList />, { wrapper: AppWrapper });
expect(screen.getByRole('status')).toBeInTheDocument();
@@ -39,7 +39,7 @@ describe('PanelPads', () => {
).toBeInTheDocument();
});
it('renders an empty pad', async () => {
it('renders an empty doc', async () => {
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
count: 1,
results: [
@@ -51,14 +51,14 @@ describe('PanelPads', () => {
],
});
render(<PadList />, { wrapper: AppWrapper });
render(<DocList />, { wrapper: AppWrapper });
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`, {
count: 1,
results: [
@@ -75,20 +75,20 @@ describe('PanelPads', () => {
],
});
render(<PadList />, { wrapper: AppWrapper });
render(<DocList />, { wrapper: AppWrapper });
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`, {
count: 1,
results: [
{
id: '1',
name: 'Pad 1',
name: 'Doc 1',
accesses: [
{
id: '1',
@@ -103,11 +103,11 @@ describe('PanelPads', () => {
],
});
render(<PadList />, { wrapper: AppWrapper });
render(<DocList />, { wrapper: AppWrapper });
expect(screen.getByRole('status')).toBeInTheDocument();
expect(await screen.findByLabelText('Pads icon')).toBeInTheDocument();
expect(await screen.findByLabelText('Docs icon')).toBeInTheDocument();
});
it('renders the error', async () => {
@@ -115,7 +115,7 @@ describe('PanelPads', () => {
status: 500,
});
render(<PadList />, { wrapper: AppWrapper });
render(<DocList />, { wrapper: AppWrapper });
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 { Box, StyledLink, Text } from '@/components';
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';
interface PadItemProps {
pad: Pad;
interface DocItemProps {
doc: Doc;
}
export const PadItem = ({ pad }: PadItemProps) => {
export const DocItem = ({ doc }: DocItemProps) => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
const {
@@ -21,8 +21,8 @@ export const PadItem = ({ pad }: PadItemProps) => {
} = useRouter();
// There is at least 1 owner in the team
const hasMembers = pad.accesses.length > 1;
const isActive = pad.id === id;
const hasMembers = doc.accesses.length > 1;
const isActive = doc.id === id;
const commonProps = {
className: 'p-t',
@@ -63,11 +63,11 @@ export const PadItem = ({ pad }: PadItemProps) => {
${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">
{hasMembers ? (
<IconGroup
aria-label={t(`Pads icon`)}
aria-label={t(`Docs icon`)}
color={colorsTokens()['primary-500']}
{...commonProps}
style={{
@@ -77,7 +77,7 @@ export const PadItem = ({ pad }: PadItemProps) => {
/>
) : (
<IconNone
aria-label={t(`Empty pads icon`)}
aria-label={t(`Empty docs icon`)}
color={colorsTokens()['greyscale-500']}
{...commonProps}
style={{
@@ -93,7 +93,7 @@ export const PadItem = ({ pad }: PadItemProps) => {
min-width: 14rem;
`}
>
{pad.title}
{doc.title}
</Text>
</Box>
</StyledLink>

View File

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

View File

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

View File

@@ -3,18 +3,18 @@ import { useTranslation } from 'react-i18next';
import { Box, BoxButton, StyledLink } from '@/components';
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 IconSort from '../assets/icon-sort.svg';
import { usePadPanelStore } from '../store';
import { useDocPanelStore } from '../store';
export const PanelActions = () => {
const { t } = useTranslation();
const { changeOrdering, ordering } = usePadPanelStore();
const { changeOrdering, ordering } = useDocPanelStore();
const { colorsTokens } = useCunninghamTheme();
const isSortAsc = ordering === PadsOrdering.BY_CREATED_ON;
const isSortAsc = ordering === DocsOrdering.BY_CREATED_ON;
return (
<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 { User } from '@/core/auth';
import { KEY_LIST_DOC_ACCESSES } from '@/features/pads/members/members-grid/';
import {
Access,
KEY_LIST_PAD,
Pad,
Doc,
KEY_LIST_DOC,
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';
@@ -16,7 +16,7 @@ import { KEY_LIST_USER } from './useUsers';
interface CreateDocAccessParams {
role: Role;
docId: Pad['id'];
docId: Doc['id'];
memberId: User['id'];
}
@@ -51,7 +51,7 @@ export function useCreateDocAccess() {
mutationFn: createDocAccess,
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_LIST_PAD],
queryKey: [KEY_LIST_DOC],
});
void queryClient.resetQueries({
queryKey: [KEY_LIST_USER],

View File

@@ -2,14 +2,14 @@ import { useMutation } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
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';
interface CreateDocInvitationParams {
email: User['email'];
role: Role;
docId: Pad['id'];
docId: Doc['id'];
}
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 { User } from '@/core/auth';
import { Pad } from '@/features/pads/pad-management';
import { Doc } from '@/features/docs/doc-management';
export type UsersParams = {
query: string;
docId: Pad['id'];
docId: Doc['id'];
};
type UsersResponse = APIList<User>;

View File

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

View File

@@ -12,7 +12,7 @@ import { createGlobalStyle } from 'styled-components';
import { APIError } from '@/api';
import { Box, Text } from '@/components';
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 IconAddUser from '../assets/add-user.svg';
@@ -41,7 +41,7 @@ type APIErrorUser = APIError<{
interface ModalAddMembersProps {
currentRole: Role;
onClose: () => void;
doc: Pad;
doc: Doc;
}
export const ModalAddMembers = ({

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Options } from 'react-select';
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 { KEY_LIST_USER, useUsers } from '../api/useUsers';
@@ -12,7 +12,7 @@ import { OptionSelect, OptionType } from '../types';
export type OptionsSelect = Options<OptionSelect>;
interface SearchUsersProps {
doc: Pad;
doc: Doc;
selectedUsers: OptionsSelect;
setSelectedUsers: (value: OptionsSelect) => void;
disabled?: boolean;

View File

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

View File

@@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
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 { MemberAction } from '../components/MemberAction';
@@ -23,7 +23,7 @@ const access: Access = {
const doc = {
id: '123456',
title: 'teamName',
} as Pad;
} as Doc;
describe('MemberAction', () => {
afterEach(() => {

View File

@@ -3,12 +3,12 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
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 { MemberGrid } from '../components/MemberGrid';
const doc: Pad = {
const doc: Doc = {
id: '123456',
title: 'teamName',
abilities: {

View File

@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
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 { ModalRole } from '../components/ModalRole';

View File

@@ -5,7 +5,7 @@ import {
} from '@tanstack/react-query';
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';
@@ -46,10 +46,10 @@ export const useDeleteDocAccess = (options?: UseDeleteDocAccessOptions) => {
queryKey: [KEY_LIST_DOC_ACCESSES],
});
void queryClient.invalidateQueries({
queryKey: [KEY_PAD],
queryKey: [KEY_DOC],
});
void queryClient.invalidateQueries({
queryKey: [KEY_LIST_PAD],
queryKey: [KEY_LIST_DOC],
});
if (options?.onSuccess) {
options.onSuccess(data, variables, context);

View File

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

View File

@@ -5,7 +5,7 @@ import {
} from '@tanstack/react-query';
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';
@@ -52,7 +52,7 @@ export const useUpdateDocAccess = (options?: UseUpdateDocAccessOptions) => {
queryKey: [KEY_LIST_DOC_ACCESSES],
});
void queryClient.invalidateQueries({
queryKey: [KEY_PAD],
queryKey: [KEY_DOC],
});
if (options?.onSuccess) {
options.onSuccess(data, variables, context);

View File

@@ -3,7 +3,7 @@ import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
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 { ModalRole } from './ModalRole';
@@ -11,7 +11,7 @@ import { ModalRole } from './ModalRole';
interface MemberActionProps {
access: Access;
currentRole: Role;
doc: Pad;
doc: Doc;
}
export const MemberAction = ({

View File

@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
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 { PAGE_SIZE } from '../conf';
@@ -11,7 +11,7 @@ import { PAGE_SIZE } from '../conf';
import { MemberAction } from './MemberAction';
interface MemberGridProps {
doc: Pad;
doc: Doc;
}
// 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 { Box, Text, TextErrors } from '@/components';
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 IconRemoveMember from '../assets/icon-remove-member.svg';
@@ -22,7 +22,7 @@ interface ModalDeleteProps {
access: Access;
currentRole: Role;
onClose: () => void;
doc: Pad;
doc: Doc;
}
export const ModalDelete = ({ access, onClose, doc }: ModalDeleteProps) => {

View File

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

View File

@@ -10,7 +10,7 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next';
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 { useUpdateDocAccess } from '../api';

View File

@@ -1,5 +1,5 @@
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) => {
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 Description": "Description de Docs",
"Docs Logo": "Logo Docs",
"Docs icon": "Icône Docs",
"Document icon": "Icône du document",
"Document name": "Nom du document",
"Documents": "Documents",
@@ -44,7 +45,7 @@
"E-mail:": "E-mail:",
"Editor": "Éditeur",
"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é.",
"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.",
@@ -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",
"Ouch !": "Aïe !",
"Owner": "Propriétaire",
"Pads icon": "Icône de pads",
"Personal data and cookies": "Données personnelles et cookies",
"Publication Director": "Directeur de la publication",
"Publisher": "Éditeur",

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { Box, StyledLink } from '@/components';
import { PadLayout } from '@/layouts';
import { DocLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next';
const StyledButton = styled(Button)`
@@ -24,7 +24,7 @@ const Page: NextPageWithLayout = () => {
};
Page.getLayout = function getLayout(page: ReactElement) {
return <PadLayout>{page}</PadLayout>;
return <DocLayout>{page}</DocLayout>;
};
export default Page;

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