✏️(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.
@@ -32,6 +32,8 @@ and this project adheres to
|
||||
- ⚡️(e2e) unique login between tests (#80)
|
||||
- ⚡️(CI) improve e2e job (#86)
|
||||
- ♻️(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
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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(
|
||||
@@ -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');
|
||||
@@ -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();
|
||||
@@ -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)');
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -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}`;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './DocEditor';
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './useDocStore';
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './useDoc';
|
||||
export * from './useDocs';
|
||||
export * from './useUpdateDoc';
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
}),
|
||||
@@ -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);
|
||||
@@ -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({
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
|
Before Width: | Height: | Size: 742 B After Width: | Height: | Size: 742 B |
@@ -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>
|
||||
@@ -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>}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './CardCreateDoc';
|
||||
export * from './ModalRemoveDoc';
|
||||
export * from './ModalUpdateDoc';
|
||||
@@ -22,7 +22,7 @@ export enum Role {
|
||||
|
||||
export type Base64 = string;
|
||||
|
||||
export interface Pad {
|
||||
export interface Doc {
|
||||
id: string;
|
||||
title: string;
|
||||
content: Base64;
|
||||
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './DocToolBox';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Access } from '../pad-management';
|
||||
import { Access } from '../doc-management';
|
||||
|
||||
export interface Template {
|
||||
id: string;
|
||||
@@ -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();
|
||||
|
||||
|
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 630 B |
|
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 578 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
@@ -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>
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
export * from './useDocPanelStore';
|
||||
@@ -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,
|
||||
})),
|
||||
}));
|
||||
3
src/frontend/apps/impress/src/features/docs/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './doc-editor';
|
||||
export * from './doc-management';
|
||||
export * from './docs-panel';
|
||||
@@ -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],
|
||||
@@ -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 ({
|
||||
@@ -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>;
|
||||
|
Before Width: | Height: | Size: 925 B After Width: | Height: | Size: 925 B |
@@ -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;
|
||||
@@ -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 = ({
|
||||
@@ -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;
|
||||
@@ -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'];
|
||||
}
|
||||
@@ -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(() => {
|
||||
@@ -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: {
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -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 = ({
|
||||
@@ -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
|
||||
@@ -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) => {
|
||||
@@ -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) => {
|
||||
@@ -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';
|
||||
@@ -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();
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './pad-editor';
|
||||
export * from './pad-management';
|
||||
export * from './pads-panel';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './PadEditor';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './usePadStore';
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
}));
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './usePad';
|
||||
export * from './usePads';
|
||||
export * from './useUpdatePad';
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './CardCreatePad';
|
||||
export * from './ModalRemovePad';
|
||||
export * from './ModalUpdatePad';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './PadToolBox';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './usePadPanelStore';
|
||||
@@ -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,
|
||||
})),
|
||||
}));
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './MainLayout';
|
||||
export * from './PadLayout';
|
||||
export * from './DocLayout';
|
||||
export * from './PageLayout';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||