Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c89909bd1a | |||
| 9991820cb1 | |||
| 2801ece358 | |||
| 0b37996899 |
+17
-5
@@ -6,12 +6,24 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- 💫(frontend) fix the help button to the bottom in tree #2073
|
||||
|
||||
## [v4.8.2] - 2026-03-19
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(backend) add resource server api #1923
|
||||
- ✨(frontend) activate Find search #1834
|
||||
- ✨ handle searching on subdocuments #1834
|
||||
- ✨(backend) add search feature flags #1897
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿️(frontend) ensure doc title is h1 for accessibility #2006
|
||||
- ♿️(frontend) add nb accesses in share button aria-label #2017
|
||||
- ✨(backend) improve fallback logic on search endpoint #1834
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -25,6 +37,11 @@ and this project adheres to
|
||||
- 🐛(backend) stop using add_sibling method to create sandbox document #2084
|
||||
- 🐛(backend) duplicate a document as last-sibling #2084
|
||||
|
||||
### Removed
|
||||
|
||||
- 🔥(api) remove `documents/<document_id>/descendants/` endpoint #1834
|
||||
- 🔥(api) remove pagination on `documents/search/` endpoint #1834
|
||||
|
||||
## [v4.8.1] - 2026-03-17
|
||||
|
||||
### Added
|
||||
@@ -41,7 +58,6 @@ and this project adheres to
|
||||
|
||||
- ✨(backend) add a is_first_connection flag to the User model #1938
|
||||
- ✨(frontend) add onboarding modal with help menu button #1868
|
||||
- ✨(backend) add resource server api #1923
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -127,16 +143,12 @@ and this project adheres to
|
||||
- ✨(frontend) Add stat for Crisp #1824
|
||||
- ✨(auth) add silent login #1690
|
||||
- 🔧(project) add DJANGO_EMAIL_URL_APP environment variable #1825
|
||||
- ✨(frontend) activate Find search #1834
|
||||
- ✨ handle searching on subdocuments #1834
|
||||
- ✨(backend) add search feature flags #1897
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿(frontend) improve accessibility:
|
||||
- ♿️(frontend) fix subdoc opening and emoji pick focus #1745
|
||||
- ✨(backend) add field for button label in email template #1817
|
||||
- ✨(backend) improve fallback logic on search endpoint #1834
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ test.describe('Doc Tree', () => {
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
await expect(page.getByLabel('Open onboarding menu')).toBeFocused();
|
||||
await expect(page.getByLabel('Open help menu')).toBeFocused();
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
@@ -309,7 +309,7 @@ test.describe('Doc Tree', () => {
|
||||
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
|
||||
await expect(page.getByLabel('Open onboarding menu')).toBeFocused();
|
||||
await expect(page.getByLabel('Open help menu')).toBeFocused();
|
||||
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ test.describe('Help feature', () => {
|
||||
await expect(page.getByRole('button', { name: 'New doc' })).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Open onboarding menu' }),
|
||||
page.getByRole('button', { name: 'Open help menu' }),
|
||||
).toBeHidden();
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ test.describe('Help feature', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: 'Open onboarding menu' }).click();
|
||||
await page.getByRole('button', { name: 'Open help menu' }).click();
|
||||
|
||||
await getMenuItem(page, 'Onboarding').click();
|
||||
|
||||
@@ -87,7 +87,7 @@ test.describe('Help feature', () => {
|
||||
});
|
||||
|
||||
test('closes modal with Skip button', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Open onboarding menu' }).click();
|
||||
await page.getByRole('button', { name: 'Open help menu' }).click();
|
||||
await getMenuItem(page, 'Onboarding').click();
|
||||
|
||||
const modal = page.getByTestId('onboarding-modal');
|
||||
@@ -107,9 +107,7 @@ test.describe('Help feature', () => {
|
||||
// switch to french
|
||||
await waitForLanguageSwitch(page, TestLanguage.French);
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: "Ouvrir le menu d'embarquement" })
|
||||
.click();
|
||||
await page.getByRole('button', { name: "Ouvrir le menu d'aide" }).click();
|
||||
|
||||
await getMenuItem(page, 'Premiers pas').click();
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export const FilterDropdown = ({
|
||||
$direction="row"
|
||||
$align="center"
|
||||
>
|
||||
<Text $weight={400} $variation="tertiary" $theme="neutral">
|
||||
<Text $weight={400} $variation="tertiary" $theme="neutral" $size="sm">
|
||||
{selectedOption?.label ?? options[0].label}
|
||||
</Text>
|
||||
<Icon
|
||||
|
||||
@@ -43,7 +43,7 @@ export const QuickSearchInput = ({
|
||||
$align="center"
|
||||
className="quick-search-input"
|
||||
$gap={spacingsTokens['2xs']}
|
||||
$padding={{ horizontal: 'base', vertical: 'xxs' }}
|
||||
$padding={{ horizontal: 'base', vertical: 'xs' }}
|
||||
>
|
||||
<Icon iconName="search" $variation="secondary" aria-hidden="true" />
|
||||
<Command.Input
|
||||
@@ -58,7 +58,7 @@ export const QuickSearchInput = ({
|
||||
data-testid="quick-search-input"
|
||||
/>
|
||||
</Box>
|
||||
{separator && <HorizontalSeparator $margin={{ top: 'base' }} />}
|
||||
{separator && <HorizontalSeparator $margin={{ top: '2xs' }} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,9 +16,10 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
}
|
||||
|
||||
[cmdk-input] {
|
||||
font-family: var(--c--globals--font--families--base);
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-size: 17px;
|
||||
font-size: 16px;
|
||||
background: white;
|
||||
outline: none;
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
|
||||
+3
-3
@@ -38,19 +38,19 @@ export const DocSearchFilters = ({
|
||||
$justify="space-between"
|
||||
$gap="10px"
|
||||
data-testid="doc-search-filters"
|
||||
$margin={{ vertical: 'base' }}
|
||||
$margin={{ vertical: 'sm' }}
|
||||
>
|
||||
<Box $direction="row" $align="center" $gap="10px">
|
||||
<FilterDropdown
|
||||
selectedValue={values?.target}
|
||||
options={[
|
||||
{
|
||||
label: t('All docs'),
|
||||
label: t('All documents'),
|
||||
value: DocSearchTarget.ALL,
|
||||
callback: () => handleTargetChange(DocSearchTarget.ALL),
|
||||
},
|
||||
{
|
||||
label: t('Current doc'),
|
||||
label: t('Current document only'),
|
||||
value: DocSearchTarget.CURRENT,
|
||||
callback: () => handleTargetChange(DocSearchTarget.CURRENT),
|
||||
},
|
||||
|
||||
+18
-19
@@ -60,37 +60,36 @@ const DocSearchModalGlobal = ({
|
||||
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
|
||||
hideCloseButton
|
||||
aria-describedby="doc-search-modal-title"
|
||||
title={
|
||||
<>
|
||||
<Text as="h2" $margin="0" $size="s" $align="flex-start">
|
||||
{t('Search for a document')}
|
||||
</Text>
|
||||
<Box $position="absolute" $css="top: 4px; right: 4px;">
|
||||
<ButtonCloseModal
|
||||
aria-label={t('Close the search modal')}
|
||||
onClick={modalProps.onClose}
|
||||
size="small"
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
$direction="column"
|
||||
$justify="space-between"
|
||||
className="--docs--doc-search-modal"
|
||||
$padding={{ vertical: 'base' }}
|
||||
$padding={{ bottom: 'base' }}
|
||||
>
|
||||
<Text
|
||||
as="h1"
|
||||
$margin="0"
|
||||
id="doc-search-modal-title"
|
||||
className="sr-only"
|
||||
>
|
||||
{t('Search docs')}
|
||||
</Text>
|
||||
<Box $position="absolute" $css="top: 4px; right: 4px;">
|
||||
<ButtonCloseModal
|
||||
aria-label={t('Close the search modal')}
|
||||
onClick={modalProps.onClose}
|
||||
size="small"
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
/>
|
||||
</Box>
|
||||
<QuickSearch
|
||||
placeholder={t('Type the name of a document')}
|
||||
loading={loading}
|
||||
onFilter={handleInputSearch}
|
||||
>
|
||||
<Box
|
||||
$padding={{ horizontal: '10px', vertical: 'base' }}
|
||||
$padding={{ horizontal: 'sm', vertical: 'base' }}
|
||||
$height={isDesktop ? '500px' : 'calc(100vh - 68px - 1rem)'}
|
||||
>
|
||||
{showFilters && (
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
useMoveDoc,
|
||||
useTrans,
|
||||
} from '@/docs/doc-management';
|
||||
import { TreeSkeleton } from '@/features/skeletons/components/TreeSkeleton';
|
||||
|
||||
import { CLASS_DOC_TITLE } from '../../doc-header';
|
||||
import { KEY_DOC_TREE, useDocTree } from '../api/useDocTree';
|
||||
@@ -257,7 +258,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
}, [currentDoc, treeContext]);
|
||||
|
||||
if (!treeContext || !treeContext.root) {
|
||||
return null;
|
||||
return <TreeSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -60,7 +60,7 @@ export const HelpMenu = ({
|
||||
>
|
||||
<Box $direction="row" $align="center">
|
||||
<Button
|
||||
aria-label={t('Open onboarding menu')}
|
||||
aria-label={t('Open help menu')}
|
||||
color={colorButton || 'neutral'}
|
||||
variant="tertiary"
|
||||
iconPosition="left"
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useRouter } from 'next/router';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, SeparatedSection } from '@/components';
|
||||
import { useDocStore } from '@/docs/doc-management';
|
||||
|
||||
import { LeftPanelTargetFilters } from './LefPanelTargetFilters';
|
||||
import { LeftPanelDocContent } from './LeftPanelDocContent';
|
||||
@@ -12,33 +11,35 @@ export const LeftPanelContent = () => {
|
||||
const router = useRouter();
|
||||
const isHome = router.pathname === '/';
|
||||
const isDoc = router.pathname === '/docs/[id]';
|
||||
const { currentDoc } = useDocStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isHome && (
|
||||
<>
|
||||
<Box
|
||||
$width="100%"
|
||||
$css={css`
|
||||
flex: 0 0 auto;
|
||||
`}
|
||||
className="--docs--home-left-panel-content"
|
||||
>
|
||||
<SeparatedSection showSeparator={false}>
|
||||
<LeftPanelTargetFilters />
|
||||
</SeparatedSection>
|
||||
</Box>
|
||||
<Box
|
||||
$flex={1}
|
||||
$width="100%"
|
||||
$css="overflow-y: auto; overflow-x: hidden;"
|
||||
>
|
||||
<LeftPanelFavorites />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{isDoc && currentDoc && <LeftPanelDocContent doc={currentDoc} />}
|
||||
</>
|
||||
);
|
||||
if (isHome) {
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
$width="100%"
|
||||
$css={css`
|
||||
flex: 0 0 auto;
|
||||
`}
|
||||
className="--docs--home-left-panel-content"
|
||||
>
|
||||
<SeparatedSection showSeparator={false}>
|
||||
<LeftPanelTargetFilters />
|
||||
</SeparatedSection>
|
||||
</Box>
|
||||
<Box
|
||||
$flex={1}
|
||||
$width="100%"
|
||||
$css="overflow-y: auto; overflow-x: hidden;"
|
||||
>
|
||||
<LeftPanelFavorites />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isDoc) {
|
||||
return <LeftPanelDocContent />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
+9
-7
@@ -1,15 +1,13 @@
|
||||
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
import { Doc, useDocStore } from '@/docs/doc-management';
|
||||
import { DocTree } from '@/docs/doc-tree/';
|
||||
import { TreeSkeleton } from '@/features/skeletons/components/TreeSkeleton';
|
||||
|
||||
export const LeftPanelDocContent = ({ doc }: { doc: Doc }) => {
|
||||
export const LeftPanelDocContent = () => {
|
||||
const tree = useTreeContext<Doc>();
|
||||
|
||||
if (!tree) {
|
||||
return null;
|
||||
}
|
||||
const { currentDoc } = useDocStore();
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -18,7 +16,11 @@ export const LeftPanelDocContent = ({ doc }: { doc: Doc }) => {
|
||||
$css="width: 100%; overflow-y: auto; overflow-x: hidden;"
|
||||
className="--docs--left-panel-doc-content"
|
||||
>
|
||||
<DocTree currentDoc={doc} />
|
||||
{tree && currentDoc ? (
|
||||
<DocTree currentDoc={currentDoc} />
|
||||
) : (
|
||||
<TreeSkeleton />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,69 +1,12 @@
|
||||
import { css, keyframes } from 'styled-components';
|
||||
|
||||
import { Box, BoxType } from '@/components';
|
||||
import { Box } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
const shimmer = keyframes`
|
||||
0% {
|
||||
background-position: -1000px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 1000px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
type SkeletonLineProps = Partial<BoxType>;
|
||||
|
||||
type SkeletonCircleProps = Partial<BoxType>;
|
||||
import { SkeletonCircle, SkeletonLine } from './SkeletionUI';
|
||||
|
||||
export const DocEditorSkeleton = () => {
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
|
||||
const SkeletonLine = ({ $css, ...props }: SkeletonLineProps) => {
|
||||
return (
|
||||
<Box
|
||||
$width="100%"
|
||||
$height="16px"
|
||||
$css={css`
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${colorsTokens['black-050']} 0%,
|
||||
${colorsTokens['black-100']} 50%,
|
||||
${colorsTokens['black-050']} 100%
|
||||
);
|
||||
background-size: 1000px 100%;
|
||||
animation: ${shimmer} 2s infinite linear;
|
||||
border-radius: 4px;
|
||||
${$css}
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SkeletonCircle = ({ $css, ...props }: SkeletonCircleProps) => {
|
||||
return (
|
||||
<Box
|
||||
$width="32px"
|
||||
$height="32px"
|
||||
$css={css`
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${colorsTokens['black-050']} 0%,
|
||||
${colorsTokens['black-100']} 50%,
|
||||
${colorsTokens['black-050']} 100%
|
||||
);
|
||||
background-size: 1000px 100%;
|
||||
animation: ${shimmer} 2s infinite linear;
|
||||
border-radius: 50%;
|
||||
${$css}
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { css, keyframes } from 'styled-components';
|
||||
|
||||
import { Box, BoxType } from '@/components/Box';
|
||||
import { useCunninghamTheme } from '@/cunningham/useCunninghamTheme';
|
||||
|
||||
const shimmer = keyframes`
|
||||
0% {
|
||||
background-position: -1000px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 1000px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
type SkeletonLineProps = Partial<BoxType>;
|
||||
|
||||
type SkeletonCircleProps = Partial<BoxType>;
|
||||
|
||||
export const SkeletonLine = ({ $css, ...props }: SkeletonLineProps) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
$width="100%"
|
||||
$height="16px"
|
||||
$css={css`
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${colorsTokens['black-050']} 0%,
|
||||
${colorsTokens['black-100']} 50%,
|
||||
${colorsTokens['black-050']} 100%
|
||||
);
|
||||
background-size: 1000px 100%;
|
||||
animation: ${shimmer} 2s infinite linear;
|
||||
border-radius: 4px;
|
||||
${$css}
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonCircle = ({ $css, ...props }: SkeletonCircleProps) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
$width="32px"
|
||||
$height="32px"
|
||||
$css={css`
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${colorsTokens['black-050']} 0%,
|
||||
${colorsTokens['black-100']} 50%,
|
||||
${colorsTokens['black-050']} 100%
|
||||
);
|
||||
background-size: 1000px 100%;
|
||||
animation: ${shimmer} 2s infinite linear;
|
||||
border-radius: 50%;
|
||||
${$css}
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Box } from '@/components';
|
||||
|
||||
import { SkeletonLine } from './SkeletionUI';
|
||||
|
||||
export const TreeSkeleton = () => {
|
||||
return (
|
||||
<Box className="--docs--tree-skeleton">
|
||||
<SkeletonLine
|
||||
$width="92%"
|
||||
$height="40px"
|
||||
$margin={{ left: 'sm', top: 'sm' }}
|
||||
/>
|
||||
<SkeletonLine
|
||||
$width="92%"
|
||||
$height="30px"
|
||||
$margin={{ left: 'sm', top: 'sm' }}
|
||||
/>
|
||||
<SkeletonLine
|
||||
$width="92%"
|
||||
$height="30px"
|
||||
$margin={{ left: 'sm', top: 'sm' }}
|
||||
/>
|
||||
<SkeletonLine
|
||||
$width="92%"
|
||||
$height="30px"
|
||||
$margin={{ left: 'sm', top: 'sm' }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -905,7 +905,7 @@
|
||||
"Open Source": "Open Source",
|
||||
"Open document {{title}}": "Ouvrir le document {{title}}",
|
||||
"Open document: {{title}}": "Ouvrir le document : {{title}}",
|
||||
"Open onboarding menu": "Ouvrir le menu d'embarquement",
|
||||
"Open help menu": "Ouvrir le menu d'aide",
|
||||
"Open root document": "Ouvrir le document racine",
|
||||
"Open the document options": "Ouvrir les options du document",
|
||||
"Open the menu of actions for the document: {{title}}": "Ouvrir le menu des actions du document : {{title}}",
|
||||
|
||||
Reference in New Issue
Block a user