Compare commits

..

5 Commits

Author SHA1 Message Date
Arnaud Robin d3868235f1 🌐(frontend) add localization to editor 2024-10-21 10:48:18 +02:00
Anthony LC 5bd78b8068 🚚(frontend) rename feature summary to table of content
We rename the feature summary to table of content
to better reflect the feature purpose.
2024-09-17 15:06:37 +02:00
Anthony LC ed39c01608 ♻️(frontent) improve summary feature
- Change Summary to Table of content
- No dash before the title
- Change font-size depend the type of heading
- If more than 2 headings the panel is open
by default
- improve sticky
- highligth the title where you are in the page
2024-09-17 15:06:37 +02:00
Anthony LC 748ebc8f26 🔧(helm) change conf helm dev
Some frontend env vars were added on the frontend
side, we need to add them to the dev helm chart.
2024-09-17 15:06:37 +02:00
renovate[bot] 03262878c4 ⬆️(dependencies) update js dependencies 2024-09-16 14:30:29 +02:00
32 changed files with 587 additions and 709 deletions
+2
View File
@@ -14,12 +14,14 @@ and this project adheres to
- ✨Add link public/authenticated/restricted access with read/editor roles #234
- ✨(frontend) add copy link button #235
- 🛂(frontend) access public docs without being logged #235
- 🌐(frontend) add localization to editor #268
## Changed
- ♻️ Allow null titles on documents for easier creation #234
- 🛂(backend) stop to list public doc to everyone #234
- 🚚(frontend) change visibility in share modal #235
- ⚡️(frontend) Improve summary #244
## Fixed
-3
View File
@@ -39,6 +39,3 @@ LOGOUT_REDIRECT_URL=http://localhost:3000
OIDC_REDIRECT_ALLOWED_HOSTS=["http://localhost:8083", "http://localhost:3000"]
OIDC_AUTH_REQUEST_EXTRA_PARAMS={"acr_values": "eidas1"}
AI_BASE_URL=https://openaiendpoint.com
AI_API_KEY=password
-69
View File
@@ -1,6 +1,5 @@
"""API endpoints"""
import json
import os
import re
import uuid
@@ -18,9 +17,6 @@ from django.db.models import (
from django.http import Http404
from botocore.exceptions import ClientError
from openai import OpenAI
from rest_framework import (
decorators,
exceptions,
@@ -371,71 +367,6 @@ class DocumentViewSet(
pass
return drf_response.Response(serializer.data)
@decorators.action(detail=True, methods=["post"], url_path="ai")
def ai(self, request, *args, **kwargs):
"""
Process text using AI based on the specified action.
"""
if not request.user.is_authenticated:
raise exceptions.PermissionDenied("Authentication required.")
action = request.data.get("action")
text = request.data.get("text")
client = OpenAI(
base_url=settings.AI_BASE_URL,
api_key=settings.AI_API_KEY
)
action_configs = {
"prompt": {
"system_content": 'Answer to the prompt. The output should be in markdown format. Send the data back in this json format: {"answer_prompt": "Your markdown answer"}. Do not give any other information.',
"response_key": 'answer_prompt'
},
"correct": {
"system_content": 'You are a text corrector. Only correct the grammar and spelling of the given markdown text. Keep the language and markdown formatting in which the text was originally written. Return only a JSON in the following format: {"corrected_text": "your corrected markdown text"}. Do not provide any other information.',
"response_key": 'corrected_text'
},
"rephrase": {
"system_content": 'You are a writer. Rephrase the given markdown text. Keep the language and markdown formatting in which the text was originally written. Return only a JSON in the following format: {"rephrased_text": "your rephrased markdown text"}. Do not provide any other information.',
"response_key": 'rephrased_text'
},
"summarize": {
"system_content": 'You are a writer. Summarize the given markdown text. Keep the language in which the text was originally written and use appropriate markdown formatting. Return only a JSON in the following format: {"summary": "your markdown summary"}. Do not provide any other information.',
"response_key": 'summary'
},
"translate_en": {
"system_content": 'You are an English translator. Translate the given markdown text to English, preserving the markdown formatting. Return only a JSON in the following format: {"text": "Your translated markdown text in English"}. Do not provide any other information.',
"response_key": 'text'
},
"translate_de": {
"system_content": 'You are a German translator. Translate the given markdown text to German, preserving the markdown formatting. Return only a JSON in the following format: {"text": "Your translated markdown text in German"}. Do not provide any other information.',
"response_key": 'text'
},
"translate_fr": {
"system_content": 'You are a French translator. Translate the given markdown text to French, preserving the markdown formatting. Return only a JSON in the following format: {"text": "Your translated markdown text in French"}. Do not provide any other information.',
"response_key": 'text'
}
}
if action not in action_configs:
return drf_response.Response({"error": "Invalid action"}, status=400)
config = action_configs[action]
try:
response = client.chat.completions.create(
model="meta-llama/Meta-Llama-3.1-70B-Instruct",
response_format={ "type": "json_object"},
messages=[
{"role": "system", "content": config["system_content"]},
{"role": "user", "content": json.dumps({"mardown_input": text})},
]
)
corrected_response = json.loads(response.choices[0].message.content)
return drf_response.Response(corrected_response[config["response_key"]])
except (json.JSONDecodeError, KeyError) as e:
return drf_response.Response({"error": f"Error processing AI response: {str(e)}"}, status=500)
@decorators.action(detail=True, methods=["get"], url_path="versions")
def versions_list(self, request, *args, **kwargs):
-6
View File
@@ -387,12 +387,6 @@ class Base(Configuration):
ALLOW_LOGOUT_GET_METHOD = values.BooleanValue(
default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None
)
AI_BASE_URL = values.Value(
None, environ_name="AI_BASE_URL", environ_prefix=None
)
AI_API_KEY = values.Value(
None, environ_name="AI_API_KEY", environ_prefix=None
)
# pylint: disable=invalid-name
@property
-1
View File
@@ -47,7 +47,6 @@ dependencies = [
"jsonschema==4.23.0",
"markdown==3.7",
"nested-multipart-parser==1.5.0",
"openai==1.44.1",
"psycopg[binary]==3.2.1",
"PyJWT==2.9.0",
"pypandoc==1.13",
@@ -1,64 +0,0 @@
import { expect, test } from '@playwright/test';
import { createDoc } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test.describe('Doc Summary', () => {
test('it checks the doc summary', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-summary', browserName, 1);
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
await page.getByLabel('Open the document options').click();
await page
.getByRole('button', {
name: 'Summary',
})
.click();
const panel = page.getByLabel('Document panel');
const editor = page.locator('.ProseMirror');
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Heading 1').click();
await page.keyboard.type('Hello World');
await page.locator('.bn-block-outer').last().click();
// Create space to fill the viewport
for (let i = 0; i < 6; i++) {
await page.keyboard.press('Enter');
}
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Heading 2').click();
await page.keyboard.type('Super World');
await page.locator('.bn-block-outer').last().click();
// Create space to fill the viewport
for (let i = 0; i < 4; i++) {
await page.keyboard.press('Enter');
}
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Heading 3').click();
await page.keyboard.type('Another World');
await expect(panel.getByText('Hello World')).toBeVisible();
await expect(panel.getByText('Super World')).toBeVisible();
await panel.getByText('Another World').click();
await expect(editor.getByText('Hello World')).not.toBeInViewport();
await panel.getByText('Back to top').click();
await expect(editor.getByText('Hello World')).toBeInViewport();
await panel.getByText('Go to bottom').click();
await expect(editor.getByText('Hello World')).not.toBeInViewport();
});
});
@@ -0,0 +1,93 @@
import { expect, test } from '@playwright/test';
import { createDoc } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test.describe('Doc Table Content', () => {
test('it checks the doc table content', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(
page,
'doc-table-content',
browserName,
1,
);
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
await page.getByLabel('Open the document options').click();
await page
.getByRole('button', {
name: 'Table of content',
})
.click();
const panel = page.getByLabel('Document panel');
const editor = page.locator('.ProseMirror');
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Heading 1').click();
await page.keyboard.type('Hello World');
await page.locator('.bn-block-outer').last().click();
// Create space to fill the viewport
for (let i = 0; i < 5; i++) {
await page.keyboard.press('Enter');
}
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Heading 2').click();
await page.keyboard.type('Super World');
await page.locator('.bn-block-outer').last().click();
// Create space to fill the viewport
for (let i = 0; i < 5; i++) {
await page.keyboard.press('Enter');
}
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Heading 3').click();
await page.keyboard.type('Another World');
const hello = panel.getByText('Hello World');
const superW = panel.getByText('Super World');
const another = panel.getByText('Another World');
await expect(hello).toBeVisible();
await expect(hello).toHaveCSS('font-size', '19.2px');
await expect(hello).toHaveAttribute('aria-selected', 'true');
await expect(superW).toBeVisible();
await expect(superW).toHaveCSS('font-size', '16px');
await expect(superW).toHaveAttribute('aria-selected', 'false');
await expect(another).toBeVisible();
await expect(another).toHaveCSS('font-size', '12.8px');
await expect(another).toHaveAttribute('aria-selected', 'false');
await hello.click();
await expect(editor.getByText('Hello World')).toBeInViewport();
await expect(hello).toHaveAttribute('aria-selected', 'true');
await expect(superW).toHaveAttribute('aria-selected', 'false');
await another.click();
await expect(editor.getByText('Hello World')).not.toBeInViewport();
await expect(hello).toHaveAttribute('aria-selected', 'false');
await expect(superW).toHaveAttribute('aria-selected', 'true');
await panel.getByText('Back to top').click();
await expect(editor.getByText('Hello World')).toBeInViewport();
await expect(hello).toHaveAttribute('aria-selected', 'true');
await expect(superW).toHaveAttribute('aria-selected', 'false');
await panel.getByText('Go to bottom').click();
await expect(editor.getByText('Hello World')).not.toBeInViewport();
await expect(superW).toHaveAttribute('aria-selected', 'true');
});
});
+1 -1
View File
@@ -12,7 +12,7 @@
"test:ui::chromium": "yarn test:ui --project=chromium"
},
"devDependencies": {
"@playwright/test": "1.47.0",
"@playwright/test": "1.47.1",
"@types/node": "*",
"@types/pdf-parse": "1.1.4",
"eslint-config-impress": "*",
+6 -6
View File
@@ -21,16 +21,16 @@
"@gouvfr-lasuite/integration": "1.0.2",
"@hocuspocus/provider": "2.13.5",
"@openfun/cunningham-react": "2.9.4",
"@tanstack/react-query": "5.55.4",
"@tanstack/react-query": "5.56.2",
"i18next": "23.15.1",
"idb": "8.0.0",
"lodash": "4.17.21",
"luxon": "3.5.0",
"next": "14.2.9",
"next": "14.2.11",
"react": "*",
"react-aria-components": "1.3.3",
"react-dom": "*",
"react-i18next": "15.0.1",
"react-i18next": "15.0.2",
"react-select": "5.8.0",
"styled-components": "6.1.13",
"yjs": "*",
@@ -39,16 +39,16 @@
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
"@tanstack/react-query-devtools": "5.55.4",
"@tanstack/react-query-devtools": "5.56.2",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.5.0",
"@testing-library/react": "16.0.1",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.12",
"@types/jest": "29.5.13",
"@types/lodash": "4.17.7",
"@types/luxon": "3.4.2",
"@types/node": "*",
"@types/react": "18.3.5",
"@types/react": "18.3.6",
"@types/react-dom": "*",
"cross-env": "*",
"dotenv": "16.4.5",
@@ -5,7 +5,7 @@ import { Box, Card, IconBG, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
interface PanelProps {
title: string;
title?: string;
setIsPanelOpen: (isOpen: boolean) => void;
}
@@ -53,11 +53,14 @@ export const Panel = ({
{...closedOverridingStyles}
>
<Box
$overflow="hidden"
$overflow="inherit"
$position="sticky"
$css={`
top: 0;
opacity: ${isOpen ? '1' : '0'};
transition: ${transition};
`}
$maxHeight="100%"
>
<Box
$padding={{ all: 'small' }}
@@ -90,9 +93,11 @@ export const Panel = ({
}}
$radius="2px"
/>
<Text $weight="bold" $size="l" $theme="primary">
{title}
</Text>
{title && (
<Text $weight="bold" $size="l" $theme="primary">
{title}
</Text>
)}
</Box>
{children}
</Box>
@@ -1,45 +0,0 @@
import { useMutation } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
export type AIActions =
| 'prompt'
| 'rephrase'
| 'summarize'
| 'translate'
| 'correct'
| 'translate_fr'
| 'translate_en'
| 'translate_de';
export type DocAIParams = {
docId: string;
text: string;
action: AIActions;
};
export type DocAIResponse = string;
export const DocAI = async ({
docId,
...params
}: DocAIParams): Promise<DocAIResponse> => {
const response = await fetchAPI(`documents/${docId}/ai/`, {
method: 'POST',
body: JSON.stringify({
...params,
}),
});
if (!response.ok) {
throw new APIError('Failed to get AI', await errorCauses(response));
}
return response.json() as Promise<DocAIResponse>;
};
export function useAIRewrite() {
return useMutation<DocAIResponse, APIError, DocAIParams>({
mutationFn: DocAI,
});
}
@@ -1,217 +0,0 @@
import {
useBlockNoteEditor,
useComponentsContext,
useSelectedBlocks,
ComponentProps,
} from '@blocknote/react';
import { mergeRefs } from "@mantine/hooks";
import { ReactNode, useMemo, forwardRef, useRef, useCallback, useState, createContext } from 'react';
import {
Menu as MantineMenu,
} from "@mantine/core";
import {Box, Text } from '@/components';
import { Doc } from '../../doc-management';
import { AIActions, useAIRewrite } from '../api/useAIRewrite';
import { useTranslation } from 'react-i18next';
interface AIGroupButtonProps {
doc: Doc;
}
export function AIGroupButton({ doc }: AIGroupButtonProps) {
const editor = useBlockNoteEditor();
const Components = useComponentsContext();
const selectedBlocks = useSelectedBlocks(editor);
const { t } = useTranslation();
const show = useMemo(() => {
return !!selectedBlocks.find((block) => block.content !== undefined);
}, [selectedBlocks]);
if (!show || !editor.isEditable || !Components) {
return null;
}
return (
<Components.Generic.Menu.Root>
<Components.Generic.Menu.Trigger>
<Components.FormattingToolbar.Button
className="bn-button"
data-test="ai-actions"
label="AI"
mainTooltip={t('AI Actions')}
>
AI
</Components.FormattingToolbar.Button>
</Components.Generic.Menu.Trigger>
<Components.Generic.Menu.Dropdown className="bn-menu-dropdown bn-drag-handle-menu">
<AIMenuItem action="prompt" docId={doc.id} icon={<Text $isMaterialIcon $size="s">text_fields</Text>}>
{t('Use as prompt')}
</AIMenuItem>
<AIMenuItem action="rephrase" docId={doc.id} icon={<Text $isMaterialIcon $size="s">refresh</Text>}>
{t('Rephrase')}
</AIMenuItem>
<AIMenuItem action="summarize" docId={doc.id} icon={<Text $isMaterialIcon $size="s">summarize</Text>}>
{t('Summarize')}
</AIMenuItem>
<AIMenuItem action="correct" docId={doc.id} icon={<Text $isMaterialIcon $size="s">check</Text>}>
{t('Correct')}
</AIMenuItem>
<TranslateMenu docId={doc.id} />
</Components.Generic.Menu.Dropdown>
</Components.Generic.Menu.Root>
);
}
interface AIMenuItemProps {
action: AIActions;
docId: Doc['id'];
children: ReactNode;
icon: ReactNode;
}
const AIMenuItem = ({ action, docId, children, icon }: AIMenuItemProps) => {
const editor = useBlockNoteEditor();
const Components = useComponentsContext()!;
const { mutateAsync: requestAI, isPending } = useAIRewrite();
const handleAIAction = useCallback(async () => {
const selectedBlocks = editor.getSelection()?.blocks;
if (!selectedBlocks || selectedBlocks.length === 0) {
return;
}
const markdown = await editor.blocksToMarkdownLossy(selectedBlocks);
const newText = await requestAI({
docId,
text: markdown,
action,
});
const blockMarkdown = await editor.tryParseMarkdownToBlocks(newText);
editor.replaceBlocks(selectedBlocks, blockMarkdown);
}, [editor, requestAI, docId, action]);
return (
<Components.Generic.Menu.Item
closeMenuOnClick={false}
icon={icon}
onClick={handleAIAction}
rightSection={isPending && <Box className="loader" />}
>
{children}
</Components.Generic.Menu.Item>
);
};
interface TranslateMenuProps {
docId: Doc['id'];
}
const TranslateMenu = ({ docId }: TranslateMenuProps) => {
const Components = useComponentsContext()!;
const { t } = useTranslation();
return (
<SubMenu position="right" sub={true} icon={<Text $isMaterialIcon $size="s">translate</Text>} close>
<Components.Generic.Menu.Trigger sub={true}>
<Components.Generic.Menu.Item subTrigger={true}>
{t('Translate')}
</Components.Generic.Menu.Item>
</Components.Generic.Menu.Trigger>
<Components.Generic.Menu.Dropdown className="bn-menu-dropdown bn-color-picker-dropdown">
<AIMenuItem action="translate_en" docId={docId}>
{t('English')}
</AIMenuItem>
<AIMenuItem action="translate_fr" docId={docId}>
{t('French')}
</AIMenuItem>
<AIMenuItem action="translate_de" docId={docId}>
{t('German')}
</AIMenuItem>
</Components.Generic.Menu.Dropdown>
</SubMenu>
);
};
const SubMenuContext = createContext<
| {
onMenuMouseOver: () => void;
onMenuMouseLeave: () => void;
}
| undefined
>(undefined);
const SubMenu = forwardRef<
HTMLButtonElement,
ComponentProps["Generic"]["Menu"]["Root"]
>((props, ref) => {
const {
children,
onOpenChange,
position,
icon,
sub, // not used
...rest
} = props;
const [opened, setOpened] = useState(false);
const itemRef = useRef<HTMLButtonElement | null>(null);
const menuCloseTimer = useRef<ReturnType<typeof setTimeout> | undefined>();
const mouseLeave = useCallback(() => {
if (menuCloseTimer.current) {
clearTimeout(menuCloseTimer.current);
}
menuCloseTimer.current = setTimeout(() => {
setOpened(false);
}, 250);
}, []);
const mouseOver = useCallback(() => {
if (menuCloseTimer.current) {
clearTimeout(menuCloseTimer.current);
}
setOpened(true);
}, []);
return (
<SubMenuContext.Provider
value={{
onMenuMouseOver: mouseOver,
onMenuMouseLeave: mouseLeave,
}}>
<MantineMenu.Item
className="bn-menu-item bn-mt-sub-menu-item"
closeMenuOnClick={false}
ref={mergeRefs(ref, itemRef)}
onMouseOver={mouseOver}
onMouseLeave={mouseLeave}
leftSection={icon}>
<MantineMenu
portalProps={{
target: itemRef.current
? itemRef.current.parentElement!
: undefined,
}}
middlewares={{ flip: true, shift: true, inline: false, size: true }}
trigger={"hover"}
opened={opened}
onClose={() => onOpenChange?.(false)}
onOpen={() => onOpenChange?.(true)}
position={position}>
{children}
</MantineMenu>
</MantineMenu.Item>
</SubMenuContext.Provider>
);
});
@@ -1,4 +1,7 @@
import { BlockNoteEditor as BlockNoteEditorCore } from '@blocknote/core';
import {
BlockNoteEditor as BlockNoteEditorCore,
locales,
} from '@blocknote/core';
import '@blocknote/core/fonts/inter.css';
import { BlockNoteView } from '@blocknote/mantine';
import '@blocknote/mantine/style.css';
@@ -18,6 +21,8 @@ import { randomColor } from '../utils';
import { BlockNoteToolbar } from './BlockNoteToolbar';
import { useTranslation } from 'react-i18next';
const cssEditor = `
&, & > .bn-container, & .ProseMirror {
height:100%
@@ -94,6 +99,19 @@ export const BlockNoteContent = ({
[createDocAttachment, doc.id],
);
const { t, i18n } = useTranslation();
const lang = i18n.language;
const resetStore = () => {
setStore(storeId, { editor: undefined });
};
// Invalidate the stored editor when the language changes
useEffect(() => {
resetStore();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lang]);
const editor = useMemo(() => {
if (storedEditor) {
return storedEditor;
@@ -108,9 +126,10 @@ export const BlockNoteContent = ({
color: randomColor(),
},
},
dictionary: locales[lang as keyof typeof locales],
uploadFile,
});
}, [provider, storedEditor, uploadFile, userData?.email]);
}, [provider, storedEditor, uploadFile, userData?.email, lang]);
useEffect(() => {
setStore(storeId, { editor });
@@ -130,7 +149,7 @@ export const BlockNoteContent = ({
editable={doc.abilities.partial_update && !isVersion}
theme="light"
>
<BlockNoteToolbar doc={doc} />
<BlockNoteToolbar />
</BlockNoteView>
</Box>
);
@@ -15,26 +15,15 @@ import {
} from '@blocknote/react';
import { forEach, isArray } from 'lodash';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Doc } from '../../doc-management';
// import { AIButton } from './AIButton';
import { AIGroupButton } from './AIButton';
interface BlockNoteToolbarProps {
doc: Doc;
}
export const BlockNoteToolbar = ({ doc }: BlockNoteToolbarProps) => {
export const BlockNoteToolbar = () => {
return (
<FormattingToolbarController
formattingToolbar={() => (
<FormattingToolbar>
<BlockTypeSelect key="blockTypeSelect" />
{/* Extra button to convert from markdown to json */}
<AIGroupButton key="AIButton" doc={doc} />
{/* Extra button to convert from markdown to json */}
<MarkdownButton key="customButton" />
@@ -105,6 +94,7 @@ export function MarkdownButton() {
const editor = useBlockNoteEditor();
const Components = useComponentsContext();
const selectedBlocks = useSelectedBlocks(editor);
const { t } = useTranslation();
const handleConvertMarkdown = () => {
const blocks = editor.getSelection()?.blocks;
@@ -138,7 +128,7 @@ export function MarkdownButton() {
return (
<Components.FormattingToolbar.Button
mainTooltip="Convert Markdown"
mainTooltip={t('Convert Markdown')}
onClick={handleConvertMarkdown}
>
M
@@ -9,7 +9,7 @@ import { Panel } from '@/components/Panel';
import { useCunninghamTheme } from '@/cunningham';
import { DocHeader } from '@/features/docs/doc-header';
import { Doc } from '@/features/docs/doc-management';
import { Summary, useDocSummaryStore } from '@/features/docs/doc-summary';
import { TableContent } from '@/features/docs/doc-table-content';
import {
VersionList,
Versions,
@@ -28,8 +28,6 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
query: { versionId },
} = useRouter();
const { isPanelVersionOpen, setIsPanelVersionOpen } = useDocVersionStore();
const { isPanelSummaryOpen, setIsPanelSummaryOpen } = useDocSummaryStore();
const { t } = useTranslation();
const isVersion = versionId && typeof versionId === 'string';
@@ -72,11 +70,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
<VersionList doc={doc} />
</Panel>
)}
{isPanelSummaryOpen && (
<Panel title={t('SUMMARY')} setIsPanelOpen={setIsPanelSummaryOpen}>
<Summary doc={doc} />
</Panel>
)}
<TableContent doc={doc} />
</Box>
</>
);
@@ -9,7 +9,7 @@ import {
ModalShare,
ModalUpdateDoc,
} from '@/features/docs/doc-management';
import { useDocSummaryStore } from '@/features/docs/doc-summary';
import { useDocTableContentStore } from '@/features/docs/doc-table-content';
import { useDocVersionStore } from '@/features/docs/doc-versioning';
import { ModalPDF } from './ModalExport';
@@ -26,7 +26,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
const [isDropOpen, setIsDropOpen] = useState(false);
const { setIsPanelVersionOpen } = useDocVersionStore();
const { setIsPanelSummaryOpen } = useDocSummaryStore();
const { setIsPanelTableContentOpen } = useDocTableContentStore();
return (
<Box
@@ -83,7 +83,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
)}
<Button
onClick={() => {
setIsPanelSummaryOpen(true);
setIsPanelTableContentOpen(true);
setIsPanelVersionOpen(false);
setIsDropOpen(false);
}}
@@ -91,7 +91,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
icon={<span className="material-icons">summarize</span>}
size="small"
>
<Text $theme="primary">{t('Summary')}</Text>
<Text $theme="primary">{t('Table of content')}</Text>
</Button>
<Button
onClick={() => {
@@ -1,104 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, BoxButton, Text } from '@/components';
import { useDocStore } from '../../doc-editor';
import { Doc } from '../../doc-management';
import { useDocSummaryStore } from '../stores';
interface SummaryProps {
doc: Doc;
}
export const Summary = ({ doc }: SummaryProps) => {
const { docsStore } = useDocStore();
const { t } = useTranslation();
const editor = docsStore?.[doc.id]?.editor;
const headingFiltering = useCallback(
() => editor?.document.filter((block) => block.type === 'heading'),
[editor?.document],
);
const [headings, setHeadings] = useState(headingFiltering());
const { setIsPanelSummaryOpen } = useDocSummaryStore();
useEffect(() => {
return () => {
setIsPanelSummaryOpen(false);
};
}, [setIsPanelSummaryOpen]);
if (!editor) {
return null;
}
editor.onEditorContentChange(() => {
setHeadings(headingFiltering());
});
return (
<Box $overflow="auto" $padding="small">
{headings?.map((heading) => (
<BoxButton
key={heading.id}
onClick={() => {
editor.focus();
editor?.setTextCursorPosition(heading.id, 'end');
document
.querySelector(`[data-id="${heading.id}"]`)
?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}}
style={{ textAlign: 'left' }}
>
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
{heading.content?.[0]?.type === 'text' && heading.content?.[0]?.text
? `- ${heading.content[0].text}`
: ''}
</Text>
</BoxButton>
))}
<Box
$height="1px"
$width="auto"
$background="#e5e5e5"
$margin={{ vertical: 'small' }}
$css="flex: none;"
/>
<BoxButton
onClick={() => {
editor.focus();
document.querySelector(`.bn-editor`)?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}}
>
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
{t('Back to top')}
</Text>
</BoxButton>
<BoxButton
onClick={() => {
editor.focus();
document
.querySelector(
`.bn-editor > .bn-block-group > .bn-block-outer:last-child`,
)
?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}}
>
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
{t('Go to bottom')}
</Text>
</BoxButton>
</Box>
);
};
@@ -1 +0,0 @@
export * from './Summary';
@@ -1 +0,0 @@
export * from './useDocSummaryStore';
@@ -1,13 +0,0 @@
import { create } from 'zustand';
export interface UseDocSummaryStore {
isPanelSummaryOpen: boolean;
setIsPanelSummaryOpen: (isOpen: boolean) => void;
}
export const useDocSummaryStore = create<UseDocSummaryStore>((set) => ({
isPanelSummaryOpen: false,
setIsPanelSummaryOpen: (isPanelSummaryOpen) => {
set(() => ({ isPanelSummaryOpen }));
},
}));
@@ -0,0 +1,66 @@
import { BlockNoteEditor } from '@blocknote/core';
import { useState } from 'react';
import { BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
const sizeMap: { [key: number]: string } = {
1: '1.2rem',
2: '1rem',
3: '0.8rem',
};
export type HeadingsHighlight = {
headingId: string;
isVisible: boolean;
}[];
interface HeadingProps {
editor: BlockNoteEditor;
level: number;
text: string;
headingId: string;
isHighlight: boolean;
}
export const Heading = ({
headingId,
editor,
isHighlight,
level,
text,
}: HeadingProps) => {
const [isHover, setIsHover] = useState(isHighlight);
const { colorsTokens } = useCunninghamTheme();
return (
<BoxButton
key={headingId}
onMouseOver={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
onClick={() => {
editor.focus();
editor.setTextCursorPosition(headingId, 'end');
document.querySelector(`[data-id="${headingId}"]`)?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}}
>
<Text
$theme="primary"
$padding={{ vertical: 'xtiny', left: 'tiny' }}
$size={sizeMap[level]}
$hasTransition
$css={
isHover || isHighlight
? `box-shadow: -2px 0px 0px ${colorsTokens()[isHighlight ? 'primary-500' : 'primary-400']};`
: ''
}
aria-selected={isHighlight}
>
{text}
</Text>
</BoxButton>
);
};
@@ -0,0 +1,180 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, BoxButton, Text } from '@/components';
import { Panel } from '@/components/Panel';
import { useDocStore } from '@/features/docs/doc-editor';
import { Doc } from '@/features/docs/doc-management';
import { useDocTableContentStore } from '../stores';
import { Heading } from './Heading';
type HeadingBlock = {
id: string;
type: string;
text: string;
content: HeadingBlock[];
props: {
level: number;
};
};
interface TableContentProps {
doc: Doc;
}
export const TableContent = ({ doc }: TableContentProps) => {
const { docsStore } = useDocStore();
const { t } = useTranslation();
const editor = docsStore?.[doc.id]?.editor;
const headingFiltering = useCallback(
() =>
editor?.document.filter(
(block) => block.type === 'heading',
) as unknown as HeadingBlock[],
[editor?.document],
);
const [headings, setHeadings] = useState<HeadingBlock[]>();
const { setIsPanelTableContentOpen, isPanelTableContentOpen } =
useDocTableContentStore();
const [hasBeenClose, setHasBeenClose] = useState(false);
const setClosePanel = () => {
setHasBeenClose(true);
setIsPanelTableContentOpen(false);
};
const [headingIdHighlight, setHeadingIdHighlight] = useState<string>();
// Open the panel if there are more than 1 heading
useEffect(() => {
if (headings?.length && headings.length > 1 && !hasBeenClose) {
setIsPanelTableContentOpen(true);
}
}, [setIsPanelTableContentOpen, headings, hasBeenClose]);
// Close the panel unmount
useEffect(() => {
return () => {
setIsPanelTableContentOpen(false);
};
}, [setIsPanelTableContentOpen]);
// To highlight the first heading in the viewport
useEffect(() => {
const handleScroll = () => {
if (!headings) {
return;
}
for (const heading of headings) {
const elHeading = document.body.querySelector(
`.bn-block-outer[data-id="${heading.id}"]`,
);
if (!elHeading) {
return;
}
const rect = elHeading.getBoundingClientRect();
const isVisible =
rect.top + rect.height >= 1 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight);
if (isVisible) {
setHeadingIdHighlight(heading.id);
break;
}
}
};
window.addEventListener('scroll', () => {
setTimeout(() => {
handleScroll();
}, 300);
});
handleScroll();
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [headings, setHeadingIdHighlight]);
if (!editor) {
return null;
}
// Update the headings when the editor content changes
editor?.onEditorContentChange(() => {
setHeadings(headingFiltering());
});
if (!isPanelTableContentOpen) {
return null;
}
return (
<Panel setIsPanelOpen={setClosePanel}>
<Box $padding="small" $maxHeight="95%">
<Box $overflow="auto">
{headings?.map((heading) => {
const content = heading.content?.[0];
const text = content?.type === 'text' ? content.text : '';
return (
<Heading
editor={editor}
headingId={heading.id}
level={heading.props.level}
text={text}
key={heading.id}
isHighlight={headingIdHighlight === heading.id}
/>
);
})}
</Box>
<Box
$height="1px"
$width="auto"
$background="#e5e5e5"
$margin={{ vertical: 'small' }}
$css="flex: none;"
/>
<BoxButton
onClick={() => {
editor.focus();
document.querySelector(`.bn-editor`)?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}}
>
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
{t('Back to top')}
</Text>
</BoxButton>
<BoxButton
onClick={() => {
editor.focus();
document
.querySelector(
`.bn-editor > .bn-block-group > .bn-block-outer:last-child`,
)
?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}}
>
<Text $theme="primary" $padding={{ vertical: 'xtiny' }}>
{t('Go to bottom')}
</Text>
</BoxButton>
</Box>
</Panel>
);
};
@@ -0,0 +1 @@
export * from './TableContent';
@@ -0,0 +1 @@
export * from './useDocTableContentStore';
@@ -0,0 +1,15 @@
import { create } from 'zustand';
export interface UseDocTableContentStore {
isPanelTableContentOpen: boolean;
setIsPanelTableContentOpen: (isOpen: boolean) => void;
}
export const useDocTableContentStore = create<UseDocTableContentStore>(
(set) => ({
isPanelTableContentOpen: false,
setIsPanelTableContentOpen: (isPanelTableContentOpen) => {
set(() => ({ isPanelTableContentOpen }));
},
}),
);
@@ -125,7 +125,7 @@
"Validate the modification": "Valider les modifications",
"Version restored successfully": "Version restaurée avec succès",
"We didn't find a mail matching, try to be more accurate": "Nous n'avons pas trouvé de correspondance par mail, essayez d'être plus précis",
"We simply comply with the law, which states that certain audience measurement tools, properly configured to respect privacy, are exempt from prior authorization.": "Nous nous conformons simplement à la loi, qui stipule que certains outils de mesure daudience, correctement configurés pour respecter la vie privée, sont exemptés de toute autorisation préalable.",
"We simply comply with the law, which states that certain audience measurement tools, properly configured to respect privacy, are exempt from prior authorization.": "Nous nous conformons simplement à la loi, qui stipule que certains outils de mesure d'audience, correctement configurés pour respecter la vie privée, sont exemptés de toute autorisation préalable.",
"We try to respond within 2 working days.": "Nous essayons de répondre dans les 2 jours ouvrables.",
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Vous êtes le seul propriétaire de ce groupe, faites d'un autre membre le propriétaire du groupe, avant de pouvoir modifier votre propre rôle ou vous supprimer du document.",
"You can oppose the tracking of your browsing on this website.": "Vous pouvez vous opposer au suivi de votre navigation sur ce site.",
@@ -141,15 +141,7 @@
"accessibility-form-defenseurdesdroits": "Écrire un message au<1>Défenseur des droits</1>",
"accessibility-not-audit": "<strong>docs.numerique.gouv.fr</strong> n'est pas en conformité avec le RGAA 4.1. Le site n'a <strong>pas encore été audité.</strong>",
"you have reported to the website manager a lack of accessibility that prevents you from accessing content or one of the services of the portal and you have not received a satisfactory response.": "vous avez signalé au responsable du site internet un défaut d'accessibilité qui vous empêche d'accéder à un contenu ou à un des services du portail et vous n'avez pas obtenu de réponse satisfaisante.",
"AI Actions": "Actions IA",
"Use as prompt": "Utiliser comme prompt",
"Rephrase": "Reformuler",
"Summarize": "Résumer",
"Correct": "Corriger",
"Translate": "Traduire",
"English": "Anglais",
"French": "Français",
"German": "Allemand"
"Convert Markdown": "Convertir Markdown"
}
}
}
@@ -41,17 +41,3 @@ main ::-webkit-scrollbar-thumb:hover,
cursor: pointer;
outline: inherit;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 71%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@@ -6,19 +6,19 @@
"lint": "eslint --ext .js ."
},
"dependencies": {
"@next/eslint-plugin-next": "14.2.9",
"@tanstack/eslint-plugin-query": "5.53.0",
"@next/eslint-plugin-next": "14.2.11",
"@tanstack/eslint-plugin-query": "5.56.1",
"@typescript-eslint/eslint-plugin": "*",
"@typescript-eslint/parser": "*",
"eslint": "*",
"eslint-config-next": "14.2.9",
"eslint-config-next": "14.2.11",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.30.0",
"eslint-plugin-jest": "28.8.3",
"eslint-plugin-jsx-a11y": "6.10.0",
"eslint-plugin-playwright": "1.6.2",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-react": "7.35.2",
"eslint-plugin-react": "7.36.1",
"eslint-plugin-testing-library": "6.3.0",
"prettier": "3.3.3"
}
+1 -1
View File
@@ -11,7 +11,7 @@
"test": "jest"
},
"dependencies": {
"@types/jest": "29.5.12",
"@types/jest": "29.5.13",
"@types/node": "*",
"eslint-config-impress": "*",
"eslint-plugin-import": "2.30.0",
+171 -114
View File
@@ -1010,7 +1010,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.0.0", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.5", "@babel/runtime@^7.24.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.5", "@babel/runtime@^7.25.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2"
integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==
@@ -1891,62 +1891,62 @@
resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-6.0.22.tgz#7eace697084e2bc5a831eb0fd7cbbc04cc1b0354"
integrity sha512-RSKlNZvxhMCkOFZ6slbYvZYbWjHUM+PxDQnupIOxIdsTZQQjx/BFfrfJ7kQFOP+g7MtpOds8weAetEs5obwMOQ==
"@next/env@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.9.tgz#f7fed48efa51b069cfc611082ad0101756df4c6a"
integrity sha512-hnDAoDPMii31V0ivibI8p6b023jOF1XblWTVjsDUoZKwnZlaBtJFZKDwFqi22R8r9i6W08dThUWU7Bsh2Rg8Ww==
"@next/env@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.11.tgz#91fa6865140e7c89c555651cbe28b57180b26b9e"
integrity sha512-HYsQRSIXwiNqvzzYThrBwq6RhXo3E0n8j8nQnAs8i4fCEo2Zf/3eS0IiRA8XnRg9Ha0YnpkyJZIZg1qEwemrHw==
"@next/eslint-plugin-next@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.9.tgz#13a3c48758e86fe2af7d05d08d557533368689fa"
integrity sha512-tmLXuDNfPTqoFuSfsd9Q4R96SS/UCKTPtBnnR+cKDcbh8xZU+126vZnRWH1WEpOmS4Vl2Hy/X6SPmgOGZzn+hA==
"@next/eslint-plugin-next@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.11.tgz#a3f8d23e90d8a2938540d3fe111b2a975398613f"
integrity sha512-7mw+xW7Y03Ph4NTCcAzYe+vu4BNjEHZUfZayyF3Y1D9RX6c5NIe25m1grHEAkyUuaqjRxOYhnCNeglOkIqLkBA==
dependencies:
glob "10.3.10"
"@next/swc-darwin-arm64@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.9.tgz#6d6880b580a0cb8d71be929d5399f0904d867e0a"
integrity sha512-/kfQifl3uLYi3DlwFlzCkgxe6fprJNLzzTUFknq3M5wGYicDIbdGlxUl6oHpVLJpBB/CBY3Y//gO6alz/K4NWA==
"@next/swc-darwin-arm64@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.11.tgz#0022b52ccc62e21c34a34311ee0251e31cd5ff49"
integrity sha512-eiY9u7wEJZWp/Pga07Qy3ZmNEfALmmSS1HtsJF3y1QEyaExu7boENz11fWqDmZ3uvcyAxCMhTrA1jfVxITQW8g==
"@next/swc-darwin-x64@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.9.tgz#56af7531ed75638923cd8cba9a43b724bcfd7fea"
integrity sha512-tK/RyhCmOCiXQ9IVdFrBbZOf4/1+0RSuJkebXU2uMEsusS51TjIJO4l8ZmEijH9gZa0pJClvmApRHi7JuBqsRw==
"@next/swc-darwin-x64@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.11.tgz#41151d22b5009a2a83bb57e6d98efb8a8995a3cd"
integrity sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==
"@next/swc-linux-arm64-gnu@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.9.tgz#d3e9a1fdf8eabd510c1139446178bfea2737c1e5"
integrity sha512-tS5eqwsp2nO7mzywRUuFYmefNZsUKM/mTG3exK2jIHv9TEVklE1SByB1KMhFkqlit1PxS9YK1tV8BOV90Wpbrw==
"@next/swc-linux-arm64-gnu@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.11.tgz#8fdc732c39b79dc7519bb561c48a2417e0eb6b24"
integrity sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==
"@next/swc-linux-arm64-musl@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.9.tgz#82e570b25b471e9aabba70c8f62ccb7dd33e45fa"
integrity sha512-8svpeTFNAMTUMKQbEzE8qRAwl9o7mNBv7LR1bmSkQvo1oy4WrNyZbhWsldOiKrc4mZ5dfQkGYsI9T75mIFMfeA==
"@next/swc-linux-arm64-musl@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.11.tgz#04c3730ca4bc2f0c56c73db53dd03cf00126a243"
integrity sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==
"@next/swc-linux-x64-gnu@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.9.tgz#277884aecc9ef7cddc9079d2dc137ecaa537ce0c"
integrity sha512-0HNulLWpKTB7H5BhHCkEhcRAnWUHeAYCftrrGw3QC18+ZywTdAoPv/zEqKy/0adqt+ks4JDdlgSQ1lNKOKjo0A==
"@next/swc-linux-x64-gnu@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.11.tgz#533823c0a96d31122671f670b58da65bdb71f330"
integrity sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==
"@next/swc-linux-x64-musl@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.9.tgz#6fd60f804c95b9cd8abf178424b76b63806c9b53"
integrity sha512-hhVFViPHLAVUJRNtwwm609p9ozWajOmRvzOZzzKXgiVGwx/CALxlMUeh+M+e0Zj6orENhWLZeilOPHpptuENsA==
"@next/swc-linux-x64-musl@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.11.tgz#fa81b56095ef4a64bae7f5798e6cdeac9de2906f"
integrity sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==
"@next/swc-win32-arm64-msvc@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.9.tgz#f7d99c80d118e29a5910f8925ff11eb29fd775b3"
integrity sha512-p/v6XlOdrk06xfN9z4evLNBqftVQUWiyduQczCwSj7hNh8fWTbzdVxsEiNOcajMXJbQiaX/ZzZdFgKVmmJnnGQ==
"@next/swc-win32-arm64-msvc@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.11.tgz#3ad4a72282f9b5e72f600522156e5bab6efb63a8"
integrity sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==
"@next/swc-win32-ia32-msvc@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.9.tgz#e233fd707d827786a676df4b858345e8812cbae3"
integrity sha512-IcW9dynWDjMK/0M05E3zopbRen7v0/yEaMZbHFOSS1J/w+8YG3jKywOGZWNp/eCUVtUUXs0PW+7Lpz8uLu+KQA==
"@next/swc-win32-ia32-msvc@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.11.tgz#80a6dd48e2f8035c518c2162bf6058bd61921a1b"
integrity sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==
"@next/swc-win32-x64-msvc@14.2.9":
version "14.2.9"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.9.tgz#c71cc74a6247ddcb4ec1b0e73f52e084c71efe9b"
integrity sha512-gcbpoXyWZdVOBgNa5BRzynrL5UR1nb2ZT38yKgnphYU9UHjeecnylMHntrQiMg/QtONDcJPFC/PmsS47xIRYoA==
"@next/swc-win32-x64-msvc@14.2.11":
version "14.2.11"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.11.tgz#2150783dfec3b3e0e7c3ecfda2d4f30fbf913d44"
integrity sha512-gQpS7mcgovWoaTG1FbS5/ojF7CGfql1Q0ZLsMrhcsi2Sr9HEqsUZ70MPJyaYBXbk6iEAP7UXMD9HC8KY1qNwvA==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@@ -2021,12 +2021,12 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@playwright/test@1.47.0":
version "1.47.0"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.47.0.tgz#69fc55b10754147cc20021afbfa05747d4961bf0"
integrity sha512-SgAdlSwYVpToI4e/IH19IHHWvoijAYH5hu2MWSXptRypLSnzj51PcGD+rsOXFayde4P9ZLi+loXVwArg6IUkCA==
"@playwright/test@1.47.1":
version "1.47.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.47.1.tgz#568a46229a5aef54b74977297a7946bb5ac4b67b"
integrity sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==
dependencies:
playwright "1.47.0"
playwright "1.47.1"
"@popperjs/core@^2.9.0":
version "2.11.8"
@@ -3392,36 +3392,36 @@
dependencies:
tslib "^2.4.0"
"@tanstack/eslint-plugin-query@5.53.0":
version "5.53.0"
resolved "https://registry.yarnpkg.com/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.53.0.tgz#b346659f97b03c3cb87f63ed7fc468bbd045d5bb"
integrity sha512-Q3WgvK2YTGc3h5EaktDouRkKBPGl3QQFLPZBagpBa6zD70PiNoDY72wWrX9T4yKClMmSulAa0wg5Nj3LVXGkEw==
"@tanstack/eslint-plugin-query@5.56.1":
version "5.56.1"
resolved "https://registry.yarnpkg.com/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.56.1.tgz#b1b9707f18ce9ed50044e356e81cbf523061bd83"
integrity sha512-IUm2Zy5BXOqMbaa7QwNg3cPa5NP5Rm3pIFCFpe7Y3pLC7Ftp8Q0Y8GU2uNpCbMFW79jHJXdQ4Oxnu1eTQr8GXQ==
dependencies:
"@typescript-eslint/utils" "^8.3.0"
"@tanstack/query-core@5.55.4":
version "5.55.4"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.55.4.tgz#21ef6c6505bf108570f1c18f5f3b90efc8b1c2d6"
integrity sha512-uoRqNnRfzOH4OMIoxj8E2+Us89UIGXfau981qYJWsNMkFS1GXR4UIyzUTVGq4N7SDLHgFPpo6IOazqUV5gkMZA==
"@tanstack/query-core@5.56.2":
version "5.56.2"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.56.2.tgz#2def2fb0290cd2836bbb08afb0c175595bb8109b"
integrity sha512-gor0RI3/R5rVV3gXfddh1MM+hgl0Z4G7tj6Xxpq6p2I03NGPaJ8dITY9Gz05zYYb/EJq9vPas/T4wn9EaDPd4Q==
"@tanstack/query-devtools@5.55.1":
version "5.55.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.55.1.tgz#fa83c40e77d21f894ca447a62424cf62a6d75706"
integrity sha512-2g0TWQGlkyHs9maHIU5A7lRunG4Rj3Y5lOEenE+fydE4zk7GqRs7rKJBp7F74iqRo/cA9V6t1YYQWqd6YRBmcQ==
"@tanstack/query-devtools@5.56.1":
version "5.56.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.56.1.tgz#319c362dd19c6cfe005e74a8777baefa4a4f72de"
integrity sha512-xnp9jq/9dHfSCDmmf+A5DjbIjYqbnnUL2ToqlaaviUQGRTapXQ8J+GxusYUu1IG0vZMaWdiVUA4HRGGZYAUU+A==
"@tanstack/react-query-devtools@5.55.4":
version "5.55.4"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.55.4.tgz#4db46ac41e095180d75e33332ca34517f29e373c"
integrity sha512-4uXcG95JV6TrlefF6n0NDckgY9QVwNPKVcn81a+MmJyKkZ704otBjtuSgXNznx5zNFnGrWlgH1luoD+bZPASuQ==
"@tanstack/react-query-devtools@5.56.2":
version "5.56.2"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.56.2.tgz#c129cdb811927085434ea27691e4b7f605eb4128"
integrity sha512-7nINJtRZZVwhTTyDdMIcSaXo+EHMLYJu1S2e6FskvvD5prx87LlAXXWZDfU24Qm4HjshEtM5lS3HIOszNGblcw==
dependencies:
"@tanstack/query-devtools" "5.55.1"
"@tanstack/query-devtools" "5.56.1"
"@tanstack/react-query@5.55.4":
version "5.55.4"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.55.4.tgz#4c8f54a05704e9f9a9ffc77a6dfa79b5a33f9905"
integrity sha512-e3uX5XkLD9oTV66/VsVpkYz3Ds/ps/Yk+V5d89xthAbtNIKKBEm4FdNb9yISFzGEGezUzVO68qmfmiSrtScvsg==
"@tanstack/react-query@5.56.2":
version "5.56.2"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.56.2.tgz#3a0241b9d010910905382f5e99160997b8795f91"
integrity sha512-SR0GzHVo6yzhN72pnRhkEFRAHMsUo5ZPzAxfTMvUxFIDVS6W9LYUp6nXW3fcHVdg0ZJl8opSH85jqahvm6DSVg==
dependencies:
"@tanstack/query-core" "5.55.4"
"@tanstack/query-core" "5.56.2"
"@tanstack/react-table@8.20.1":
version "8.20.1"
@@ -3749,10 +3749,10 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jest@29.5.12":
version "29.5.12"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544"
integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==
"@types/jest@29.5.13":
version "29.5.13"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc"
integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==
dependencies:
expect "^29.0.0"
pretty-format "^29.0.0"
@@ -3851,7 +3851,7 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@18.3.5":
"@types/react@*":
version "18.3.5"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.5.tgz#5f524c2ad2089c0ff372bbdabc77ca2c4dbadf8f"
integrity sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==
@@ -3859,6 +3859,14 @@
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/react@18.3.6":
version "18.3.6"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.6.tgz#1cb5895c5ea0d99d8bc7d659e42f72713cbd3942"
integrity sha512-CnGaRYNu2iZlkGXGrOYtdg5mLK8neySj0woZ4e2wF/eli2E6Sazmq5X+Nrj6OBrrFVQfJWTUFeqAzoRhWQXYvg==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/resolve@1.20.2":
version "1.20.2"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
@@ -5771,12 +5779,12 @@ escodegen@^2.0.0:
optionalDependencies:
source-map "~0.6.1"
eslint-config-next@14.2.9:
version "14.2.9"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-14.2.9.tgz#7230dbf285ff7c377be13e8f05febcdbfc6276fc"
integrity sha512-aNgGqWBp2KFcuEf9zNqmv+8dBkOrdyOlCIbdtyw7fiCQioLqXNcXmalAyeNtVyE95Kwgg11bgXvuVqdxpbR79g==
eslint-config-next@14.2.11:
version "14.2.11"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-14.2.11.tgz#759158f35d4a98081868b4bbcd9eaa675d147a65"
integrity sha512-gGIoBoHCJuLn6vaV1Ke8UurVvgb7JjQv6oRlWmI6RAAxz7KwJOYxxm2blctavA0a3eofbE9TdgKvvTb2G55OHQ==
dependencies:
"@next/eslint-plugin-next" "14.2.9"
"@next/eslint-plugin-next" "14.2.11"
"@rushstack/eslint-patch" "^1.3.3"
"@typescript-eslint/eslint-plugin" "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0"
"@typescript-eslint/parser" "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0"
@@ -5895,7 +5903,31 @@ eslint-plugin-prettier@5.2.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596"
integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==
eslint-plugin-react@7.35.2, eslint-plugin-react@^7.33.2:
eslint-plugin-react@7.36.1:
version "7.36.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz#f1dabbb11f3d4ebe8b0cf4e54aff4aee81144ee5"
integrity sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==
dependencies:
array-includes "^3.1.8"
array.prototype.findlast "^1.2.5"
array.prototype.flatmap "^1.3.2"
array.prototype.tosorted "^1.1.4"
doctrine "^2.1.0"
es-iterator-helpers "^1.0.19"
estraverse "^5.3.0"
hasown "^2.0.2"
jsx-ast-utils "^2.4.1 || ^3.0.0"
minimatch "^3.1.2"
object.entries "^1.1.8"
object.fromentries "^2.0.8"
object.values "^1.2.0"
prop-types "^15.8.1"
resolve "^2.0.0-next.5"
semver "^6.3.1"
string.prototype.matchall "^4.0.11"
string.prototype.repeat "^1.0.0"
eslint-plugin-react@^7.33.2:
version "7.35.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz#d32500d3ec268656d5071918bfec78cfd8b070ed"
integrity sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ==
@@ -8740,12 +8772,12 @@ neo-async@^2.6.2:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
next@14.2.9:
version "14.2.9"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.9.tgz#51d0e067cd9eb8a51fd5c0efd5d8f28181849729"
integrity sha512-3CzBNo6BuJnRjcQvRw+irnU1WiuJNZEp+dkzkt91y4jeIDN/Emg95F+takSYiLpJ/HkxClVQRyqiTwYce5IVqw==
next@14.2.11:
version "14.2.11"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.11.tgz#86572882d340c5c1ee7e46d00790fa7ba664d6a9"
integrity sha512-8MDFqHBhdmR2wdfaWc8+lW3A/hppFe1ggQ9vgIu/g2/2QEMYJrPoQP6b+VNk56gIug/bStysAmrpUKtj3XN8Bw==
dependencies:
"@next/env" "14.2.9"
"@next/env" "14.2.11"
"@swc/helpers" "0.5.5"
busboy "1.6.0"
caniuse-lite "^1.0.30001579"
@@ -8753,15 +8785,15 @@ next@14.2.9:
postcss "8.4.31"
styled-jsx "5.1.1"
optionalDependencies:
"@next/swc-darwin-arm64" "14.2.9"
"@next/swc-darwin-x64" "14.2.9"
"@next/swc-linux-arm64-gnu" "14.2.9"
"@next/swc-linux-arm64-musl" "14.2.9"
"@next/swc-linux-x64-gnu" "14.2.9"
"@next/swc-linux-x64-musl" "14.2.9"
"@next/swc-win32-arm64-msvc" "14.2.9"
"@next/swc-win32-ia32-msvc" "14.2.9"
"@next/swc-win32-x64-msvc" "14.2.9"
"@next/swc-darwin-arm64" "14.2.11"
"@next/swc-darwin-x64" "14.2.11"
"@next/swc-linux-arm64-gnu" "14.2.11"
"@next/swc-linux-arm64-musl" "14.2.11"
"@next/swc-linux-x64-gnu" "14.2.11"
"@next/swc-linux-x64-musl" "14.2.11"
"@next/swc-win32-arm64-msvc" "14.2.11"
"@next/swc-win32-ia32-msvc" "14.2.11"
"@next/swc-win32-x64-msvc" "14.2.11"
no-case@^3.0.4:
version "3.0.4"
@@ -9091,17 +9123,17 @@ pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
playwright-core@1.47.0:
version "1.47.0"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.47.0.tgz#b54ec060fd83e5c2e46b63986b5ebb5e96ace427"
integrity sha512-1DyHT8OqkcfCkYUD9zzUTfg7EfTd+6a8MkD/NWOvjo0u/SCNd5YmY/lJwFvUZOxJbWNds+ei7ic2+R/cRz/PDg==
playwright-core@1.47.1:
version "1.47.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.47.1.tgz#bb45bdfb0d48412c535501aa3805867282857df8"
integrity sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==
playwright@1.47.0:
version "1.47.0"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.47.0.tgz#fb9b028883fad11362f9ff63ce7ba44bda0bf626"
integrity sha512-jOWiRq2pdNAX/mwLiwFYnPHpEZ4rM+fRSQpRHwEwZlP2PUANvL3+aJOF/bvISMhFD30rqMxUB4RJx9aQbfh4Ww==
playwright@1.47.1:
version "1.47.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.47.1.tgz#cdc1116f5265b8d2ff7be0d8942d49900634dc6c"
integrity sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==
dependencies:
playwright-core "1.47.0"
playwright-core "1.47.1"
optionalDependencies:
fsevents "2.3.2"
@@ -9608,12 +9640,12 @@ react-dom@*, react-dom@18.3.1, react-dom@^18:
loose-envify "^1.1.0"
scheduler "^0.23.2"
react-i18next@15.0.1:
version "15.0.1"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.0.1.tgz#fc662d93829ecb39683fe2757a47ebfbc5c912a0"
integrity sha512-NwxLqNM6CLbeGA9xPsjits0EnXdKgCRSS6cgkgOdNcPXqL+1fYNl8fBg1wmnnHvFy812Bt4IWTPE9zjoPmFj3w==
react-i18next@15.0.2:
version "15.0.2"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.0.2.tgz#8b7f3c0e66cb4f99f95e2c507353398c41a68cc2"
integrity sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==
dependencies:
"@babel/runtime" "^7.24.8"
"@babel/runtime" "^7.25.0"
html-parse-stringify "^3.0.1"
react-icons@^5.2.1:
@@ -10390,7 +10422,16 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -10501,7 +10542,14 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -11820,7 +11868,16 @@ workbox-window@7.1.0:
"@types/trusted-types" "^2.0.2"
workbox-core "7.1.0"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -72,7 +72,8 @@ frontend:
envVars:
PORT: 8080
NEXT_PUBLIC_API_ORIGIN: https://impress.127.0.0.1.nip.io
NEXT_PUBLIC_SIGNALING_URL: wss://impress.127.0.0.1.nip.io/ws
NEXT_PUBLIC_Y_PROVIDER_URL: wss://impress.127.0.0.1.nip.io/ws
NEXT_PUBLIC_MEDIA_URL: https://impress.127.0.0.1.nip.io
replicas: 1
command: