Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 270b374a17 |
+1
-2
@@ -17,13 +17,12 @@ and this project adheres to
|
||||
- 🥅(frontend) intercept 401 error on GET threads #1754
|
||||
- 🦺(frontend) check content type pdf on PdfBlock #1756
|
||||
- ✈️(frontend) pause Posthog when offline #1755
|
||||
- 📱(frontend) toolbar to the bottom when mobile #1774
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(frontend) fix tables deletion #1752
|
||||
- 🐛(frontend) fix children not display when first resize #1753
|
||||
|
||||
- 📝(doc) fix publiccode.yml syntax #1770
|
||||
|
||||
## [4.2.0] - 2025-12-17
|
||||
|
||||
|
||||
+16
-6
@@ -1,16 +1,18 @@
|
||||
publiccodeYmlVersion: "2.4.0"
|
||||
publiccodeYmlVersion: "0.5.0"
|
||||
name: Docs
|
||||
url: https://github.com/suitenumerique/docs
|
||||
landingURL: https://github.com/suitenumerique/docs
|
||||
creationDate: 2023-12-10
|
||||
logo: https://raw.githubusercontent.com/suitenumerique/docs/main/docs/assets/docs-logo.png
|
||||
usedBy:
|
||||
- Direction interministériel du numérique (DINUM)
|
||||
- Direction interministérielle du numérique (DINUM)
|
||||
fundedBy:
|
||||
- name: Direction interministériel du numérique (DINUM)
|
||||
url: https://www.numerique.gouv.fr
|
||||
- name: Direction interministérielle du numérique (DINUM)
|
||||
uri: https://www.numerique.gouv.fr
|
||||
roadmap: "https://github.com/orgs/suitenumerique/projects/2/views/1"
|
||||
softwareType: "standalone/other"
|
||||
platforms:
|
||||
- "web"
|
||||
developmentStatus: "stable"
|
||||
description:
|
||||
en:
|
||||
shortDescription: "The open source document editor where your notes can become knowledge through live collaboration"
|
||||
@@ -18,10 +20,18 @@ description:
|
||||
shortDescription: "L'éditeur de documents open source où vos notes peuvent devenir des connaissances grâce à la collaboration en direct."
|
||||
legal:
|
||||
license: MIT
|
||||
localisation:
|
||||
localisationReady: true
|
||||
availableLanguages:
|
||||
- de
|
||||
- en
|
||||
- es
|
||||
- fr
|
||||
- nl
|
||||
maintenance:
|
||||
type: internal
|
||||
contacts:
|
||||
- name: "Virgile Deville"
|
||||
email: "virgile.deville@numerique.gouv.fr"
|
||||
- name: "samuel.paccoud"
|
||||
- name: "Samuel Paccoud"
|
||||
email: "samuel.paccoud@numerique.gouv.fr"
|
||||
|
||||
@@ -52,17 +52,14 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
const { theme } = useCunninghamTheme();
|
||||
const { replace } = useRouter();
|
||||
|
||||
const { initializeResizeListener, initializeInputDetection } =
|
||||
useResponsiveStore();
|
||||
const initializeResizeListener = useResponsiveStore(
|
||||
(state) => state.initializeResizeListener,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return initializeResizeListener();
|
||||
}, [initializeResizeListener]);
|
||||
|
||||
useEffect(() => {
|
||||
return initializeInputDetection();
|
||||
}, [initializeInputDetection]);
|
||||
|
||||
/**
|
||||
* Update the global router replace function
|
||||
* This allows us to use the router replace function globally
|
||||
|
||||
+1
-54
@@ -1,26 +1,15 @@
|
||||
import { FormattingToolbarExtension } from '@blocknote/core/extensions';
|
||||
import {
|
||||
ExperimentalMobileFormattingToolbarController,
|
||||
FormattingToolbar,
|
||||
FormattingToolbarController,
|
||||
blockTypeSelectItems,
|
||||
getFormattingToolbarItems,
|
||||
useBlockNoteEditor,
|
||||
useDictionary,
|
||||
useExtensionState,
|
||||
} from '@blocknote/react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { useConfig } from '@/core/config/api';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import {
|
||||
DocsBlockSchema,
|
||||
DocsInlineContentSchema,
|
||||
DocsStyleSchema,
|
||||
} from '../../types';
|
||||
import { CommentToolbarButton } from '../comments/CommentToolbarButton';
|
||||
import { getCalloutFormattingToolbarItems } from '../custom-blocks';
|
||||
|
||||
@@ -35,7 +24,6 @@ export const BlockNoteToolbar = () => {
|
||||
const [onConfirm, setOnConfirm] = useState<() => void | Promise<void>>();
|
||||
const { t } = useTranslation();
|
||||
const { data: conf } = useConfig();
|
||||
const { isTablet, isInputTouch } = useResponsiveStore();
|
||||
|
||||
const toolbarItems = useMemo(() => {
|
||||
let toolbarItems = getFormattingToolbarItems([
|
||||
@@ -96,13 +84,7 @@ export const BlockNoteToolbar = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{isInputTouch && isTablet ? (
|
||||
<MobileFormattingToolbarController
|
||||
formattingToolbar={formattingToolbar}
|
||||
/>
|
||||
) : (
|
||||
<FormattingToolbarController formattingToolbar={formattingToolbar} />
|
||||
)}
|
||||
<FormattingToolbarController formattingToolbar={formattingToolbar} />
|
||||
{confirmOpen && (
|
||||
<ModalConfirmDownloadUnsafe
|
||||
onClose={() => setIsConfirmOpen(false)}
|
||||
@@ -112,38 +94,3 @@ export const BlockNoteToolbar = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MobileFormattingToolbarController = ({
|
||||
formattingToolbar,
|
||||
}: {
|
||||
formattingToolbar: () => React.ReactNode;
|
||||
}) => {
|
||||
const editor = useBlockNoteEditor<
|
||||
DocsBlockSchema,
|
||||
DocsInlineContentSchema,
|
||||
DocsStyleSchema
|
||||
>();
|
||||
const show = useExtensionState(FormattingToolbarExtension, {
|
||||
editor,
|
||||
});
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
$position="absolute"
|
||||
$css={`
|
||||
& > div {
|
||||
left: 50%;
|
||||
transform: translate(0px, 0px) scale(1) translateX(-50%)!important;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ExperimentalMobileFormattingToolbarController
|
||||
formattingToolbar={formattingToolbar}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-3
@@ -74,9 +74,7 @@ export function MarkdownButton() {
|
||||
};
|
||||
|
||||
const show = useMemo(() => {
|
||||
return (
|
||||
selectedBlocks.filter((block) => block.content !== undefined).length !== 0
|
||||
);
|
||||
return !!selectedBlocks.find((block) => block.content !== undefined);
|
||||
}, [selectedBlocks]);
|
||||
|
||||
if (!show || !editor.isEditable || !Components) {
|
||||
|
||||
+25
-28
@@ -59,7 +59,11 @@ interface PdfBlockComponentProps {
|
||||
>;
|
||||
}
|
||||
|
||||
const PdfBlockComponent = ({ editor, block }: PdfBlockComponentProps) => {
|
||||
const PdfBlockComponent = ({
|
||||
editor,
|
||||
block,
|
||||
contentRef,
|
||||
}: PdfBlockComponentProps) => {
|
||||
const pdfUrl = block.props.url;
|
||||
const { i18n, t } = useTranslation();
|
||||
const lang = i18n.resolvedLanguage;
|
||||
@@ -110,34 +114,27 @@ const PdfBlockComponent = ({ editor, block }: PdfBlockComponentProps) => {
|
||||
void validatePDFContent();
|
||||
}, [pdfUrl]);
|
||||
|
||||
if (isPDFContentLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!isPDFContentLoading && isPDFContent !== null && !isPDFContent) {
|
||||
return (
|
||||
<Box
|
||||
$align="center"
|
||||
$justify="center"
|
||||
$color="#666"
|
||||
$background="#f5f5f5"
|
||||
$border="1px solid #ddd"
|
||||
$height="300px"
|
||||
$css={css`
|
||||
text-align: center;
|
||||
`}
|
||||
$width="100%"
|
||||
contentEditable={false}
|
||||
onClick={() => editor.setTextCursorPosition(block)}
|
||||
>
|
||||
{t('Invalid or missing PDF file.')}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box ref={contentRef} className="bn-file-block-content-wrapper">
|
||||
<PDFBlockStyle />
|
||||
{isPDFContentLoading && <Loading />}
|
||||
{!isPDFContentLoading && isPDFContent !== null && !isPDFContent && (
|
||||
<Box
|
||||
$align="center"
|
||||
$justify="center"
|
||||
$color="#666"
|
||||
$background="#f5f5f5"
|
||||
$border="1px solid #ddd"
|
||||
$height="300px"
|
||||
$css={css`
|
||||
text-align: center;
|
||||
`}
|
||||
contentEditable={false}
|
||||
onClick={() => editor.setTextCursorPosition(block)}
|
||||
>
|
||||
{t('Invalid or missing PDF file.')}
|
||||
</Box>
|
||||
)}
|
||||
<ResizableFileBlockWrapper
|
||||
buttonIcon={
|
||||
<Icon iconName="upload" $size="24px" $css="line-height: normal;" />
|
||||
@@ -161,7 +158,7 @@ const PdfBlockComponent = ({ editor, block }: PdfBlockComponentProps) => {
|
||||
/>
|
||||
)}
|
||||
</ResizableFileBlockWrapper>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -180,9 +180,6 @@ export const cssEditor = css`
|
||||
& .bn-editor {
|
||||
padding-right: 36px;
|
||||
}
|
||||
& .bn-toolbar {
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (width <= 560px) {
|
||||
|
||||
@@ -120,7 +120,7 @@ const MainContent = ({
|
||||
$css={css`
|
||||
overflow-y: auto;
|
||||
overflow-x: clip;
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
outline: 3px solid ${colorsTokens['brand-400']};
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export type ScreenSize = 'small-mobile' | 'mobile' | 'tablet' | 'desktop';
|
||||
export type InputMethod = 'touch' | 'mouse' | 'unknown';
|
||||
|
||||
export interface UseResponsiveStore {
|
||||
isMobile: boolean;
|
||||
@@ -11,11 +10,7 @@ export interface UseResponsiveStore {
|
||||
screenWidth: number;
|
||||
setScreenSize: (size: ScreenSize) => void;
|
||||
isDesktop: boolean;
|
||||
isTouchCapable: boolean;
|
||||
isInputTouch: boolean;
|
||||
inputMethod: InputMethod;
|
||||
initializeResizeListener: () => () => void;
|
||||
initializeInputDetection: () => () => void;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
@@ -25,9 +20,6 @@ const initialState = {
|
||||
isDesktop: false,
|
||||
screenSize: 'desktop' as ScreenSize,
|
||||
screenWidth: 0,
|
||||
isTouchCapable: false,
|
||||
isInputTouch: false,
|
||||
inputMethod: 'unknown' as InputMethod,
|
||||
};
|
||||
|
||||
export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
|
||||
@@ -37,9 +29,6 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
|
||||
isTablet: initialState.isTablet,
|
||||
screenSize: initialState.screenSize,
|
||||
screenWidth: initialState.screenWidth,
|
||||
isTouchCapable: initialState.isTouchCapable,
|
||||
isInputTouch: initialState.isInputTouch,
|
||||
inputMethod: initialState.inputMethod,
|
||||
setScreenSize: (size: ScreenSize) => set(() => ({ screenSize: size })),
|
||||
initializeResizeListener: () => {
|
||||
const resizeHandler = () => {
|
||||
@@ -95,32 +84,4 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
|
||||
window.removeEventListener('resize', debouncedResizeHandler);
|
||||
};
|
||||
},
|
||||
initializeInputDetection: () => {
|
||||
// Detect if device has touch capability
|
||||
const isTouchCapable =
|
||||
'ontouchstart' in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
// @ts-ignore - for older browsers
|
||||
navigator.msMaxTouchPoints > 0;
|
||||
|
||||
set({ isTouchCapable });
|
||||
|
||||
// Track actual input method being used
|
||||
const handleTouchStart = () => {
|
||||
set({ inputMethod: 'touch', isInputTouch: true });
|
||||
};
|
||||
|
||||
const handleMouseMove = () => {
|
||||
set({ inputMethod: 'mouse', isInputTouch: false });
|
||||
};
|
||||
|
||||
// Listen for first interaction to determine input method
|
||||
window.addEventListener('touchstart', handleTouchStart, { once: false });
|
||||
window.addEventListener('mousemove', handleMouseMove, { once: false });
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('touchstart', handleTouchStart);
|
||||
window.removeEventListener('mousemove', handleMouseMove);
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user