(frontend) add find and replace feature to the editor

We have added a new feature to the editor that allows
users to find and replace text within their documents.
This feature enhances the editing experience by
providing a convenient way to search for specific
words or phrases and replace them with new content.
This commit is contained in:
Anthony LC
2026-08-11 15:45:03 +02:00
parent 28438fdd92
commit f42f8b5fd8
18 changed files with 657 additions and 205 deletions
+7 -6
View File
@@ -11,17 +11,18 @@ and this project adheres to
- ♿️(frontend) restore skip to content link after header redesign #2510
- 🌐(i18n) rename cn_CN to zh_CN, add eo_PL and zh_TW locales #2486
- ✨(backend) conditional email notification in server to server api #2554
### Fixed
- 🐛(frontend) redirect homepage to login when homepage feat is disabled #2521
- 🐛(backend) ignore CSPs for API docs in development
- 🐛(frontend) export images embedded with a relative url #2573
- ✨(frontend) add find and replace feature to the editor #2570
### Changed
- ♿️(frontend) use semantic `<dl>` structure in document info card #2379
### Fixed
- 🐛(frontend) redirect homepage to login when homepage feat is disabled #2521
- 🐛(backend) ignore CSPs for API docs in development #2538
- 🐛(frontend) export images embedded with a relative url #2573
## [v5.4.1] - 2026-07-09
### Changed
@@ -602,13 +602,7 @@ test.describe('Doc Editor', () => {
page,
browserName,
}) => {
const [docTitle] = await createDoc(
page,
'doc-viewport-test',
browserName,
1,
);
await verifyDocName(page, docTitle);
await createDoc(page, 'doc-viewport-test', browserName, 1);
const editor = await writeInEditor({
page,
@@ -637,4 +631,57 @@ test.describe('Doc Editor', () => {
await expect(editor.getByText('Mobile Text')).toBeVisible();
});
test('it searches and replaces occurrences', async ({
page,
browserName,
}) => {
await createDoc(page, 'doc-search-replace', browserName);
const editor = await writeInEditor({
page,
text: 'World',
});
await writeInEditor({
page,
text: 'Hello World - Hello World',
});
// Open the find and replace panel
await page.keyboard.press('Control+f');
// Search for "Hello" and check that the occurrences are highlighted
await page.getByRole('textbox', { name: 'Find in document' }).fill('Hello');
await expect(page.getByText('1 / 2')).toBeVisible();
await expect(
editor
.locator('.find-and-replace-result-current')
.first()
.getByText('Hello'),
).toBeVisible();
await expect(editor.locator('.find-and-replace-result')).toHaveCount(2);
await page.getByRole('button', { name: 'Next match' }).click();
await expect(page.getByText('2 / 2')).toBeVisible();
await page.keyboard.press('Escape');
// Select World then press Ctrl+f to check if the selected text is prefilled in the find input
await page.getByText('World').first().selectText();
await page.keyboard.press('Control+f');
await expect(
page.getByRole('textbox', { name: 'Find in document' }),
).toHaveValue('World');
// Replace occurrences
await page.getByRole('button', { name: 'Next match' }).click();
await page.getByRole('button', { name: 'Toggle replace' }).click();
await page.getByRole('textbox', { name: 'Replace with' }).fill('Docs');
await page.getByRole('button', { name: 'Replace', exact: true }).click();
await expect(editor.getByText('Hello Docs - Hello World')).toBeVisible();
await page.getByRole('button', { name: 'Replace all' }).click();
await expect(editor.getByText('Docs', { exact: true })).toBeVisible();
await expect(editor.getByText('Hello Docs - Hello Docs')).toBeVisible();
});
});
+1
View File
@@ -51,6 +51,7 @@
"@react-pdf/renderer": "4.3.1",
"@sentry/nextjs": "10.69.0",
"@tanstack/react-query": "5.101.4",
"@tiptap/extension-find-and-replace": "3.29.2",
"@tiptap/extensions": "*",
"ai": "6.0.205",
"canvg": "4.0.3",
@@ -1,3 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.29302 12.707C3.90249 12.3165 3.90249 11.6835 4.29302 11.293C4.68354 10.9024 5.31655 10.9024 5.70708 11.293L11 16.5859V5C11 4.44771 11.4478 4 12 4C12.5523 4 13 4.44771 13 5V16.5859L18.293 11.293C18.6835 10.9024 19.3166 10.9024 19.7071 11.293C20.0976 11.6835 20.0976 12.3165 19.7071 12.707L12.7071 19.707C12.3166 20.0976 11.6835 20.0976 11.293 19.707L4.29302 12.707Z" fill="#222631"/>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.29302 12.707C3.90249 12.3165 3.90249 11.6835 4.29302 11.293C4.68354 10.9024 5.31655 10.9024 5.70708 11.293L11 16.5859V5C11 4.44771 11.4478 4 12 4C12.5523 4 13 4.44771 13 5V16.5859L18.293 11.293C18.6835 10.9024 19.3166 10.9024 19.7071 11.293C20.0976 11.6835 20.0976 12.3165 19.7071 12.707L12.7071 19.707C12.3166 20.0976 11.6835 20.0976 11.293 19.707L4.29302 12.707Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 493 B

@@ -1,4 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.72632 5.87305C6.05743 5.87305 6.37785 6.02513 6.68725 6.3291L9.46362 9.09766C9.63725 9.2659 9.72436 9.4587 9.72436 9.67578C9.72427 9.89265 9.64282 10.0824 9.48022 10.2451C9.31195 10.4188 9.12161 10.5035 8.90991 10.498C8.69831 10.4872 8.50542 10.3977 8.33179 10.2295L7.67261 9.57812L6.52417 8.29395V16.8164C6.52422 17.2124 6.6252 17.5078 6.82593 17.7031C7.02677 17.8985 7.31705 17.9971 7.69702 17.9971H13.9578C14.2235 17.9971 14.4328 18.0671 14.5847 18.208C14.7367 18.3546 14.8156 18.5531 14.821 18.8027C14.821 19.047 14.7421 19.2397 14.5847 19.3809C14.4327 19.5273 14.2236 19.6006 13.9578 19.6006H7.59936C6.72001 19.6006 6.05478 19.3753 5.60425 18.9248C5.15378 18.4797 4.92847 17.8225 4.92847 16.9541V8.29395L3.78003 9.57812L3.11304 10.2295C2.95022 10.3977 2.7598 10.4872 2.54272 10.498C2.33114 10.5034 2.14159 10.4187 1.97339 10.2451C1.80521 10.0824 1.72295 9.89275 1.72827 9.67578C1.7337 9.45878 1.82092 9.26586 1.98901 9.09766L4.77417 6.3291C5.07806 6.02525 5.3953 5.8731 5.72632 5.87305Z" fill="#222631"/>
<path d="M16.4167 4.39941C17.2907 4.39941 17.9535 4.62228 18.4041 5.06738C18.8544 5.51248 19.0798 6.17219 19.0798 7.0459V15.6963L20.2273 14.4141L20.8874 13.7627C21.0556 13.6001 21.2453 13.5163 21.4568 13.5107C21.6683 13.4999 21.8613 13.5781 22.0349 13.7461C22.1977 13.9143 22.2766 14.1072 22.2712 14.3242C22.2712 14.5358 22.1874 14.7263 22.0193 14.8945L19.2429 17.6631C18.9336 17.967 18.613 18.1191 18.282 18.1191C17.9509 18.1246 17.6304 17.9724 17.321 17.6631L14.5447 14.8945C14.371 14.7263 14.2816 14.5359 14.2761 14.3242C14.2762 14.1072 14.3575 13.9143 14.5203 13.7461C14.6939 13.578 14.8868 13.4999 15.0984 13.5107C15.3153 13.5162 15.505 13.6001 15.6677 13.7627L16.3357 14.4141L17.4841 15.6963V7.17578C17.4841 6.78507 17.3831 6.49229 17.1824 6.29688C16.9815 6.10146 16.6912 6.00293 16.3113 6.00293H10.0583C9.78694 6.00293 9.57459 5.93312 9.42261 5.79199C9.27064 5.64544 9.19269 5.44693 9.18725 5.19727C9.18725 4.95303 9.26284 4.76027 9.41479 4.61914C9.57221 4.47258 9.78694 4.39941 10.0583 4.39941H16.4167Z" fill="#222631"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 15" fill="none">
<path
d="M12.6667 7.33328C13.0349 7.33328 13.3333 7.63176 13.3333 7.99995V8.66662C13.3333 9.55065 12.9819 10.3983 12.3568 11.0234C11.7317 11.6485 10.884 11.9999 10 11.9999H2.27604L3.80469 13.5286C4.06501 13.7889 4.06503 14.211 3.80469 14.4713C3.54434 14.7316 3.12232 14.7316 2.86198 14.4713L0.195312 11.8046C0.174555 11.7839 0.156119 11.7615 0.138672 11.7389C0.129486 11.7269 0.120355 11.715 0.111979 11.7024C0.0946366 11.6764 0.0797059 11.6494 0.0664062 11.6217C0.0516892 11.5911 0.0386186 11.5596 0.0286458 11.5266C0.0253349 11.5157 0.0229172 11.5045 0.0201823 11.4934C0.00749696 11.4421 4.07536e-06 11.3886 0 11.3333C0 11.2802 0.00649173 11.2285 0.0182292 11.179C0.0215374 11.165 0.025077 11.1511 0.0292969 11.1373C0.0391902 11.1051 0.0519643 11.0742 0.0664062 11.0442C0.0791817 11.0177 0.0935322 10.9917 0.110026 10.9667C0.134945 10.929 0.163441 10.8938 0.195312 10.8619L2.86198 8.19526C3.12233 7.93491 3.54434 7.93491 3.80469 8.19526C4.06501 8.45561 4.06503 8.87763 3.80469 9.13797L2.27604 10.6666H10C10.5304 10.6666 11.039 10.4557 11.4141 10.0807C11.7891 9.70562 12 9.19703 12 8.66662V7.99995C12 7.63176 12.2985 7.33328 12.6667 7.33328Z"
fill="currentColor"
/>
<path
d="M9.52865 0.195262C9.789 -0.0650874 10.211 -0.0650874 10.4714 0.195262L13.138 2.86193C13.1699 2.8938 13.1984 2.92897 13.2233 2.96675C13.2398 2.99174 13.2542 3.01769 13.2669 3.04422C13.2815 3.07435 13.2941 3.10553 13.304 3.13797C13.3082 3.1515 13.3112 3.16527 13.3145 3.17899C13.3262 3.22856 13.3333 3.28011 13.3333 3.33328C13.3333 3.38862 13.3252 3.44203 13.3125 3.49344C13.3097 3.50473 13.3074 3.51613 13.304 3.52729C13.2941 3.55996 13.2815 3.59137 13.2669 3.62169C13.2536 3.64947 13.2381 3.67634 13.2207 3.70242C13.1963 3.73904 13.169 3.77362 13.138 3.80464L10.4714 6.4713C10.211 6.73162 9.78899 6.73162 9.52865 6.4713C9.2683 6.21096 9.26832 5.78895 9.52865 5.5286L11.0573 3.99995H3.33333C2.8029 3.99995 2.29434 4.21081 1.91927 4.58589C1.54421 4.96096 1.33333 5.46953 1.33333 5.99995V6.66662C1.33331 7.03478 1.03484 7.33328 0.666667 7.33328C0.298494 7.33328 2.71431e-05 7.03478 0 6.66662V5.99995C0 5.11591 0.351457 4.2683 0.976562 3.64318C1.60168 3.01806 2.44928 2.66662 3.33333 2.66662H11.0573L9.52865 1.13797C9.2683 0.877629 9.26832 0.455614 9.52865 0.195262Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -1,3 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.7071 11.2929C20.0976 11.6834 20.0976 12.3164 19.7071 12.707C19.3166 13.0975 18.6835 13.0975 18.293 12.707L13 7.41399V18.9999C13 19.5522 12.5523 19.9999 12 19.9999C11.4478 19.9999 11 19.5522 11 18.9999V7.41399L5.70708 12.707C5.31655 13.0975 4.68354 13.0975 4.29302 12.707C3.90249 12.3164 3.90249 11.6834 4.29302 11.2929L11.293 4.29289C11.6835 3.90237 12.3166 3.90237 12.7071 4.29289L19.7071 11.2929Z" fill="#222631"/>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.7071 11.2929C20.0976 11.6834 20.0976 12.3164 19.7071 12.707C19.3166 13.0975 18.6835 13.0975 18.293 12.707L13 7.41399V18.9999C13 19.5522 12.5523 19.9999 12 19.9999C11.4478 19.9999 11 19.5522 11 18.9999V7.41399L5.70708 12.707C5.31655 13.0975 4.68354 13.0975 4.29302 12.707C3.90249 12.3164 3.90249 11.6834 4.29302 11.2929L11.293 4.29289C11.6835 3.90237 12.3166 3.90237 12.7071 4.29289L19.7071 11.2929Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 533 B

After

Width:  |  Height:  |  Size: 528 B

@@ -1,15 +1,25 @@
import { Button, type ButtonProps } from '@gouvfr-lasuite/cunningham-react';
import type { ComponentProps } from 'react';
import CloseIcon from '@/assets/icons/ui-kit/x-mark.svg';
import CloseIcon from '@/icons/x-mark.svg';
export const ButtonCloseModal = (props: ButtonProps) => {
type ButtonCloseModalProps = ButtonProps & {
iconProps?: Omit<ComponentProps<typeof CloseIcon>, 'children'>;
};
export const ButtonCloseModal = ({
iconProps,
...props
}: ButtonCloseModalProps) => {
return (
<Button
type="button"
size="small"
color="neutral"
variant="tertiary"
icon={<CloseIcon width="24" height="24" aria-hidden="true" />}
icon={
<CloseIcon width="24" height="24" aria-hidden="true" {...iconProps} />
}
{...props}
/>
);
@@ -18,6 +18,7 @@ import {
useCreateBlockNote,
} from '@blocknote/react';
import { HocuspocusProvider } from '@hocuspocus/provider';
import { FindAndReplace } from '@tiptap/extension-find-and-replace';
import { useEffect, useMemo, useRef } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
@@ -32,6 +33,7 @@ import {
useCommentSidebarStore,
useComments,
} from '@/docs/doc-comments';
import { DocsFindReplaceStyle } from '@/docs/doc-find-replace/styles';
import { Doc } from '@/docs/doc-management';
import { avatarUrlFromName, useAuth } from '@/features/auth';
import { useRightPanelStore } from '@/features/right-panel/stores/useRightPanelStore';
@@ -230,6 +232,13 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
CommentsExtension({ threadStore, resolveUsers }),
...(aiExtension ? [aiExtension] : []),
],
_tiptapOptions: {
extensions: [
FindAndReplace.configure({
injectCSS: false,
}),
],
},
visualMedia: {
image: {
maxWidth: 760,
@@ -279,6 +288,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
canSeeComment={canSeeComment}
currentUserAvatarUrl={currentUserAvatarUrl}
/>
<DocsFindReplaceStyle />
{errorAttachment && (
<Box $margin={{ bottom: 'big', top: 'none', horizontal: 'large' }}>
<TextErrors
@@ -2,6 +2,8 @@ import { announce } from '@react-aria/live-announcer';
import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useFindReplaceShortcut } from '@/docs/doc-find-replace/hooks/useFindReplaceShortcut';
import { DocsBlockNoteEditor } from '../types';
const getFormattingShortcutLabel = (
@@ -53,6 +55,7 @@ export const useShortcuts = (
el: HTMLDivElement | null,
) => {
const { t } = useTranslation();
useFindReplaceShortcut(editor);
const handleFormattingShortcut = useCallback(
(event: KeyboardEvent) => {
@@ -0,0 +1,240 @@
import { Button } from '@gouvfr-lasuite/cunningham-react';
import { KeyboardEvent, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled, { css } from 'styled-components';
import { Box, ButtonCloseModal, Card, Text } from '@/components';
import { useEditorStore } from '@/docs/doc-editor/stores/useEditorStore';
import ArrowDownIcon from '@/icons/arrow-down.svg';
import ArrowSquarepathIcon from '@/icons/arrow-squarepath.svg';
import ArrowUpIcon from '@/icons/arrow-up.svg';
import { useFocusStore } from '@/stores/useFocusStore';
import { useFindReplace } from '../hooks/useFindReplace';
import { useFindReplaceStore } from '../stores/useFindReplaceStore';
const Input = styled.input`
flex: 1;
min-width: 0;
border: none;
outline: none;
background: transparent;
font-size: 0.875rem;
color: inherit;
font-family: inherit;
padding: 0 var(--c--globals--spacings--2xs);
`;
export const FindReplace = () => {
const { t } = useTranslation();
const { editor } = useEditorStore();
const { close, openCount } = useFindReplaceStore();
const { restoreFocus } = useFocusStore();
const [isReplaceOpen, setIsReplaceOpen] = useState(false);
const findInputRef = useRef<HTMLInputElement>(null);
const {
query,
setQuery,
replacement,
setReplacement,
matchCount,
activeIndex,
goToNext,
goToPrevious,
replaceCurrent,
replaceAll,
} = useFindReplace(editor);
/**
* Pre-fill the find input with the currently selected
* text in the editor, if any.
* Gives the focus to the find input and selects its content so that
* the user can start typing a new query immediately.
*/
useEffect(() => {
const tiptapEditor = editor?._tiptapEditor;
if (tiptapEditor) {
const { from, to } = tiptapEditor.state.selection;
const selectedText =
from !== to ? tiptapEditor.state.doc.textBetween(from, to, ' ') : '';
if (selectedText) {
setQuery(selectedText);
}
}
findInputRef.current?.focus();
findInputRef.current?.select();
}, [editor?._tiptapEditor, setQuery, openCount]);
const handleClose = () => {
close();
restoreFocus();
};
const handlePanelKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
event.preventDefault();
handleClose();
}
};
const handleFindKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key !== 'Enter') {
return;
}
event.preventDefault();
if (event.shiftKey) {
goToPrevious();
} else {
goToNext();
}
};
const handleReplaceKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key !== 'Enter') {
return;
}
event.preventDefault();
replaceCurrent();
};
const hasMatches = matchCount > 0;
const counterLabel = hasMatches
? `${activeIndex + 1} / ${matchCount}`
: '0 / 0';
return (
<Card
role="dialog"
aria-label={t('Find and replace')}
onKeyDown={handlePanelKeyDown}
$radius="var(--c--globals--spacings--xs)"
$width="331px"
$css={css`
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05);
`}
$padding="none"
>
<Box
$direction="row"
$align="center"
$height="40px"
$gap="3xs"
$padding={{ vertical: '3xs', horizontal: 'xs' }}
>
<Input
ref={findInputRef}
type="text"
value={query}
onChange={(event) => setQuery(event.target.value)}
onKeyDown={handleFindKeyDown}
placeholder={t('Find, replace...')}
aria-label={t('Find in document')}
/>
{query && (
<>
<Text
$size="xs"
$color="var(--c--contextuals--content--semantic--neutral--tertiary)"
$css={css`
white-space: nowrap;
font-variant-numeric: tabular-nums;
`}
>
{counterLabel}
</Text>
<Button
aria-label={t('Previous match')}
onClick={goToPrevious}
disabled={!hasMatches}
icon={<ArrowUpIcon aria-hidden="true" width="16" height="16" />}
color="neutral"
variant="tertiary"
size="nano"
/>
<Button
aria-label={t('Next match')}
onClick={goToNext}
disabled={!hasMatches}
icon={<ArrowDownIcon aria-hidden="true" width="16" height="16" />}
color="neutral"
variant="tertiary"
size="nano"
/>
<Box
$background="var(--c--contextuals--border--surface--primary)"
$width="1px"
$height="16px"
$margin={{ horizontal: '4xs' }}
/>
</>
)}
<Button
aria-label={t('Toggle replace')}
aria-expanded={isReplaceOpen}
color={isReplaceOpen ? 'brand' : 'neutral'}
variant={isReplaceOpen ? 'secondary' : 'tertiary'}
size="nano"
onClick={() => setIsReplaceOpen((prev) => !prev)}
icon={
<ArrowSquarepathIcon aria-hidden="true" width="16" height="16" />
}
/>
<ButtonCloseModal
aria-label={t('Close find and replace')}
onClick={handleClose}
size="nano"
iconProps={{ width: '16', height: '16' }}
/>
</Box>
{isReplaceOpen && (
<Box
$direction="row"
$align="center"
$height="40px"
$padding={{ vertical: '3xs', horizontal: 'xs' }}
$gap="3xs"
$background="var(--c--contextuals--background--surface--tertiary)"
$css={css`
border-top: 1px solid
var(--c--contextuals--border--surface--primary);
`}
$radius="0 0 var(--c--globals--spacings--xs) var(--c--globals--spacings--xs)"
>
<Input
type="text"
value={replacement}
onChange={(event) => setReplacement(event.target.value)}
onKeyDown={handleReplaceKeyDown}
placeholder={t('Replace by...')}
aria-label={t('Replace with')}
/>
<Button
aria-label={t('Replace all')}
onClick={replaceAll}
color="neutral"
variant="tertiary"
size="nano"
disabled={!hasMatches}
>
{t('Replace all')}
</Button>
<Button
aria-label={t('Replace')}
onClick={replaceCurrent}
color="brand"
variant="primary"
size="nano"
disabled={!hasMatches}
>
{t('Replace')}
</Button>
</Box>
)}
</Card>
);
};
@@ -0,0 +1,125 @@
import { FormattingToolbarExtension } from '@blocknote/core/extensions';
import { useCallback, useEffect, useState } from 'react';
import { DocsBlockNoteEditor } from '@/docs/doc-editor/types';
export const useFindReplace = (editor: DocsBlockNoteEditor | undefined) => {
const [query, setQueryState] = useState('');
const [replacement, setReplacementState] = useState('');
const [matchCount, setMatchCount] = useState(0);
const [activeIndex, setActiveIndex] = useState(-1);
const tiptapEditor = editor?._tiptapEditor;
const syncFromStorage = useCallback(() => {
if (!tiptapEditor) {
return;
}
const { results, currentIndex } = tiptapEditor.storage.findAndReplace;
setMatchCount(results.length);
setActiveIndex(currentIndex ?? -1);
}, [tiptapEditor]);
// The extension only calls ProseMirror's tr.scrollIntoView(), which doesn't
// reliably reach the active match through BlockNote's scroll container, so
// we scroll to it manually instead.
const scrollActiveIntoView = useCallback(() => {
if (!tiptapEditor) {
return;
}
const { results, currentIndex } = tiptapEditor.storage.findAndReplace;
const match = currentIndex !== null ? results[currentIndex] : undefined;
if (!match) {
return;
}
const { view } = tiptapEditor;
const domInfo = view.domAtPos(match.from);
const el =
domInfo.node.nodeType === 1
? (domInfo.node as HTMLElement)
: domInfo.node.parentElement;
el?.scrollIntoView({ block: 'center', behavior: 'smooth' });
}, [tiptapEditor]);
// Navigating/replacing matches moves the real editor selection, which
// triggers BlockNote's formatting toolbar. Force it closed since it isn't
// relevant while using find & replace.
const hideFormattingToolbar = useCallback(() => {
editor?.getExtension(FormattingToolbarExtension)?.store.setState(false);
}, [editor]);
// Reset the extension's state when the panel opens and subscribe to
// transactions to keep match count/active index in sync while it's open.
useEffect(() => {
if (!tiptapEditor) {
return;
}
tiptapEditor?.commands.clearSearch();
tiptapEditor?.commands.setReplaceTerm('');
syncFromStorage();
tiptapEditor.on('transaction', syncFromStorage);
return () => {
tiptapEditor.off('transaction', syncFromStorage);
tiptapEditor?.commands.clearSearch();
};
}, [tiptapEditor, syncFromStorage]);
const setQuery = useCallback(
(value: string) => {
setQueryState(value);
tiptapEditor?.commands.setSearchTerm(value);
requestAnimationFrame(scrollActiveIntoView);
},
[tiptapEditor, scrollActiveIntoView],
);
const setReplacement = useCallback(
(value: string) => {
setReplacementState(value);
tiptapEditor?.commands.setReplaceTerm(value);
},
[tiptapEditor],
);
const goToNext = useCallback(() => {
tiptapEditor?.commands.goToNextResult();
hideFormattingToolbar();
scrollActiveIntoView();
}, [tiptapEditor, scrollActiveIntoView, hideFormattingToolbar]);
const goToPrevious = useCallback(() => {
tiptapEditor?.commands.goToPreviousResult();
hideFormattingToolbar();
scrollActiveIntoView();
}, [tiptapEditor, scrollActiveIntoView, hideFormattingToolbar]);
const replaceCurrent = useCallback(() => {
tiptapEditor?.commands.replace();
hideFormattingToolbar();
requestAnimationFrame(scrollActiveIntoView);
}, [tiptapEditor, scrollActiveIntoView, hideFormattingToolbar]);
const replaceAll = useCallback(() => {
tiptapEditor?.commands.replaceAll();
}, [tiptapEditor]);
return {
query,
setQuery,
replacement,
setReplacement,
matchCount,
activeIndex,
goToNext,
goToPrevious,
replaceCurrent,
replaceAll,
};
};
@@ -0,0 +1,40 @@
import { useEffect } from 'react';
import { DocsBlockNoteEditor } from '@/docs/doc-editor/types';
import { useFindReplaceStore } from '../stores/useFindReplaceStore';
/**
* Binds Cmd/Ctrl+F to open the in-editor Find & Replace panel instead of the
* browser's native find bar.
*/
export const useFindReplaceShortcut = (
editor: DocsBlockNoteEditor | undefined,
) => {
useEffect(() => {
if (!editor) {
return;
}
const handleKeyDown = (event: KeyboardEvent) => {
const isFindShortcut =
(event.metaKey || event.ctrlKey) &&
!event.shiftKey &&
!event.altKey &&
event.key.toLowerCase() === 'f';
if (!isFindShortcut) {
return;
}
event.preventDefault();
useFindReplaceStore.getState().open();
};
document.addEventListener('keydown', handleKeyDown, true);
return () => {
document.removeEventListener('keydown', handleKeyDown, true);
};
}, [editor]);
};
@@ -0,0 +1,22 @@
import { create } from 'zustand';
interface UseFindReplaceStore {
isOpen: boolean;
/**
* `openCount` gives us a way to rerender the FindReplace
* component when it is opened multiple times in a row.
* We use this to reset the input fields when the user
* opens the panel again.
*/
openCount: number;
open: () => void;
close: () => void;
}
export const useFindReplaceStore = create<UseFindReplaceStore>((set) => ({
isOpen: false,
openCount: 0,
open: () =>
set((state) => ({ isOpen: true, openCount: state.openCount + 1 })),
close: () => set({ isOpen: false }),
}));
@@ -0,0 +1,18 @@
import { createGlobalStyle } from 'styled-components';
export const DocsFindReplaceStyle = createGlobalStyle`
.bn-root {
.find-and-replace-result {
border-radius: var(--c--globals--spacings--xxxs);
background: color-mix(
in srgb,
var(--c--contextuals--background--palette--yellow--tertiary) 35%,
transparent
);
mix-blend-mode: darken;
}
.find-and-replace-result-current {
background: var(--c--contextuals--background--palette--yellow--tertiary);
}
}
`;
@@ -1,26 +1,33 @@
import { Box } from '@/components';
import { CardFloatingBar, FloatingBar } from '@/components/FloatingBar';
import { FindReplace } from '@/docs/doc-find-replace/components/FindReplace';
import { useFindReplaceStore } from '@/docs/doc-find-replace/stores/useFindReplaceStore';
import { useDocStore } from '@/docs/doc-management/stores/useDocStore';
import { DocShareButton } from '@/features/docs/doc-share/components/DocShareButton';
import { DocShareButton } from '@/docs/doc-share/components/DocShareButton';
import { RightPanelCollapseButton } from '@/features/right-panel/components/RightPanelCollapseButton';
import { DocLeftPanelCollapseButton } from './DocLeftPanelCollapseButton';
import { DocToolBox } from './DocToolBox';
export const DocFloatingBar = () => {
const { currentDoc } = useDocStore();
const currentDoc = useDocStore((state) => state.currentDoc);
const isDeletedDoc = !!currentDoc?.deleted_at;
const isFindReplaceOpen = useFindReplaceStore((state) => state.isOpen);
return (
<FloatingBar>
<DocLeftPanelCollapseButton />
<Box $direction="row" $align="center" $gap="2xs">
{!isDeletedDoc && currentDoc && <DocShareButton doc={currentDoc} />}
<CardFloatingBar>
<RightPanelCollapseButton />
{!isDeletedDoc && currentDoc && <DocToolBox doc={currentDoc} />}
</CardFloatingBar>
</Box>
{isFindReplaceOpen ? (
<FindReplace />
) : (
<Box $direction="row" $align="center" $gap="2xs">
{!isDeletedDoc && currentDoc && <DocShareButton doc={currentDoc} />}
<CardFloatingBar>
<RightPanelCollapseButton />
{!isDeletedDoc && currentDoc && <DocToolBox doc={currentDoc} />}
</CardFloatingBar>
</Box>
)}
</FloatingBar>
);
};
@@ -46,7 +46,7 @@ const SkeletonEditorHeader = () => {
return (
<Box
$padding={{ horizontal: isDesktop ? '54px' : 'base', top: '55px' }}
$padding={{ horizontal: isDesktop ? '54px' : 'base', top: '24px' }}
className="--docs--doc-editor-header-skeleton"
$width="100%"
$gap="base"
@@ -99,8 +99,6 @@ export const SkeletonEditorCore = ({
$css,
...props
}: SkeletonEditorCoreProps) => {
const { isDesktop } = useResponsiveStore();
return (
<Box
$direction="row"
@@ -112,7 +110,7 @@ export const SkeletonEditorCore = ({
<Box
$position="relative"
$width="100%"
$padding={{ horizontal: isDesktop ? '54px' : 'base', top: 'md' }}
$padding={{ horizontal: '54px', top: '45px' }}
$flex="1"
$css={css`
${$css}
+4
View File
@@ -33,7 +33,9 @@
},
"resolutions": {
"@gouvfr-lasuite/cunningham-react": "4.4.0",
"@tiptap/core": "3.29.2",
"@tiptap/extensions": "3.29.2",
"@tiptap/pm": "3.29.2",
"@types/node": "24.13.3",
"@types/react": "19.2.17",
"@types/react-dom": "19.2.3",
@@ -41,6 +43,8 @@
"eslint": "10.8.0",
"js-yaml": "5.2.2",
"postcss": "8.5.25",
"prosemirror-model": "1.25.11",
"prosemirror-view": "1.42.2",
"react": "19.2.8",
"react-dom": "19.2.8",
"serialize-javascript": "7.0.7",
+83 -169
View File
@@ -5204,11 +5204,6 @@
"@react-types/overlays" "^3.9.4"
"@react-types/shared" "^3.33.1"
"@remirror/core-constants@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f"
integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==
"@rolldown/binding-android-arm64@1.1.5":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz#f58cb9a0a8128ed0582282720528547fc5c035f3"
@@ -6033,10 +6028,10 @@
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149"
integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==
"@tiptap/core@^3.13.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.14.0.tgz#86e5f35971ea7c2d0695a9947efb57aed5cb5f1d"
integrity sha512-nm0VWVA1Vq/jaKY3wyRXViL/kf78yMdH7qETpv4qZXDQLU+pdWV3IGoRTQTKESc7d8L1wL/2uCeByLNUJfrSIw==
"@tiptap/core@3.29.2", "@tiptap/core@^3.13.0":
version "3.29.2"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.29.2.tgz#90d24591a9e7fb450ffb95ed6a42f529348e3e9e"
integrity sha512-oKUkiPUB7noilVYxI9lNzUD4rX17sHub+PYjMfHMWHG9A3nvIy+FdePIVIIhThKWF7ijhr3eIqHY51Bn+GAFtw==
"@tiptap/extension-bold@^3.13.0":
version "3.14.0"
@@ -6055,6 +6050,13 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.14.0.tgz#452ca88d58e769133f08e1827928fba7a7088755"
integrity sha512-Sx9yLorzS+oqNmXID4jt0G5tDnsEgU0HtEXPLD3KNt/ltVxWJU0AXwCsp1/Dg0HIDL868vWpJ2jC1t/4oaf9kA==
"@tiptap/extension-find-and-replace@3.29.2":
version "3.29.2"
resolved "https://registry.yarnpkg.com/@tiptap/extension-find-and-replace/-/extension-find-and-replace-3.29.2.tgz#2f0121c9e9367ebc823b65396e0a62c33d32092e"
integrity sha512-HiFDlWpHIqVtUYl1la9nVWxAPfdvHyV7ayVO5vkFvCceiNSYPZhjCIg3FFt80ms7K3UWkFUKHHfQ8bX3CYZ/dA==
dependencies:
re2js "^2.8.6"
"@tiptap/extension-floating-menu@^3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.14.0.tgz#7c178962667888bc036f311a2ac434db6d2638f2"
@@ -6095,29 +6097,24 @@
resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.29.2.tgz#207c6ed79db1a5baec13f3fd8653b437fca2c8b6"
integrity sha512-BCz+FCAChSYtUe4BFj97HEO+nSK+J7GxbJgZG4Hg7DT/gI+hRyeNndU8efiQAx3WGdzsFi3UxRpcF1tTQM7iMQ==
"@tiptap/pm@^3.13.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.14.0.tgz#4536c351ec8ab2b7d44a8b4f0eba4b8c46437532"
integrity sha512-xrZmqI5jl4yMeAsu8p8gVP9S3An5h2MBi8BQHNnZmpyzkUrlpd40vlT6u13SWIqVi5ZWhBZ6U3rL7mkVLZuRKg==
"@tiptap/pm@3.29.2", "@tiptap/pm@^3.13.0":
version "3.29.2"
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.29.2.tgz#de461c6f8986ef807082f467cde2d8fdf1cce4bc"
integrity sha512-GCOme7xHaS+DSoaA4CDcAD3l6JyBlvZhvCyfsy2Vp6j8tEoBkZWio7soYVosmlyn7zq8/64VeFZP5s47yfG7fQ==
dependencies:
prosemirror-changeset "^2.3.0"
prosemirror-collab "^1.3.1"
prosemirror-commands "^1.6.2"
prosemirror-dropcursor "^1.8.1"
prosemirror-gapcursor "^1.3.2"
prosemirror-history "^1.4.1"
prosemirror-inputrules "^1.4.0"
prosemirror-keymap "^1.2.2"
prosemirror-markdown "^1.13.1"
prosemirror-menu "^1.2.4"
prosemirror-model "^1.24.1"
prosemirror-schema-basic "^1.2.3"
prosemirror-schema-list "^1.5.0"
prosemirror-state "^1.4.3"
prosemirror-tables "^1.6.4"
prosemirror-trailing-node "^3.0.0"
prosemirror-transform "^1.10.2"
prosemirror-view "^1.38.1"
prosemirror-changeset "^2.4.1"
prosemirror-commands "^1.7.1"
prosemirror-dropcursor "^1.8.2"
prosemirror-gapcursor "^1.4.1"
prosemirror-history "^1.5.0"
prosemirror-inputrules "^1.5.1"
prosemirror-keymap "^1.2.3"
prosemirror-model "^1.25.11"
prosemirror-schema-list "^1.5.1"
prosemirror-state "^1.4.4"
prosemirror-tables "^1.8.5"
prosemirror-transform "^1.12.0"
prosemirror-view "^1.41.9"
"@tiptap/react@^3.13.0":
version "3.14.0"
@@ -6354,11 +6351,6 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/linkify-it@^5":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76"
integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==
"@types/lodash@4.17.24":
version "4.17.24"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f"
@@ -6369,14 +6361,6 @@
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.7.2.tgz#082bb2adf54bc6fe1d514a0d80a82ac6a75cfaac"
integrity sha512-gW+Oib+vUtGJBtNC8V9Reww0oIpusw+4m81uncg9REGZAJfqOQHfo/nkabnc7w0QReXyPqjrbWMJk6NuAkiX3Q==
"@types/markdown-it@^14.0.0":
version "14.1.2"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61"
integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==
dependencies:
"@types/linkify-it" "^5"
"@types/mdurl" "^2"
"@types/mdast@^4.0.0":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
@@ -6384,11 +6368,6 @@
dependencies:
"@types/unist" "*"
"@types/mdurl@^2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd"
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
"@types/methods@^1.1.4":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547"
@@ -8408,11 +8387,6 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
crelt@^1.0.0:
version "1.0.6"
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
cross-env@10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-10.1.0.tgz#cfd2a6200df9ed75bfb9cb3d7ce609c13ea21783"
@@ -11732,13 +11706,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
linkify-it@^5.0.1:
version "5.0.2"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.2.tgz#d3be0a693af3da9df3883f1e346a0e97461a8c19"
integrity sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==
dependencies:
uc.micro "^2.0.0"
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
@@ -11884,18 +11851,6 @@ makeerror@1.0.12:
dependencies:
tmpl "1.0.5"
markdown-it@^14.0.0:
version "14.2.0"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.2.0.tgz#06d48d9035e77d5b1c85adb315482fc8240289ef"
integrity sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==
dependencies:
argparse "^2.0.1"
entities "^4.4.0"
linkify-it "^5.0.1"
mdurl "^2.0.0"
punycode.js "^2.3.1"
uc.micro "^2.1.0"
matcher-collection@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-2.0.1.tgz#90be1a4cf58d6f2949864f65bb3b0f3e41303b29"
@@ -11949,11 +11904,6 @@ mdn-data@2.27.1:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.27.1.tgz#e37b9c50880b75366c4d40ac63d9bbcacdb61f0e"
integrity sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==
mdurl@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0"
integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==
media-engine@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/media-engine/-/media-engine-1.0.3.tgz#be3188f6cd243ea2a40804a35de5a5b032f58dad"
@@ -12855,13 +12805,6 @@ property-information@^7.0.0:
resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d"
integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==
prosemirror-changeset@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz#eee3299cfabc7a027694e9abdc4e85505e9dd5e7"
integrity sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==
dependencies:
prosemirror-transform "^1.0.0"
prosemirror-changeset@^2.3.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz#8d8ea0290cb9545c298ec427ac3a8f298c39170f"
@@ -12869,35 +12812,35 @@ prosemirror-changeset@^2.3.1:
dependencies:
prosemirror-transform "^1.0.0"
prosemirror-collab@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33"
integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==
prosemirror-changeset@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz#685091245bf3299cd1cae2b8983cf9b0342e6b39"
integrity sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==
dependencies:
prosemirror-state "^1.0.0"
prosemirror-transform "^1.0.0"
prosemirror-commands@^1.0.0, prosemirror-commands@^1.6.2:
version "1.7.1"
resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz#d101fef85618b1be53d5b99ea17bee5600781b38"
integrity sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==
prosemirror-commands@^1.7.1:
version "1.7.2"
resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.7.2.tgz#cbef3b0d722dc51e71cc936c87c4ca27d81df3a6"
integrity sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==
dependencies:
prosemirror-model "^1.0.0"
prosemirror-state "^1.0.0"
prosemirror-transform "^1.10.2"
prosemirror-dropcursor@^1.8.1:
version "1.8.2"
resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz#2ed30c4796109ddeb1cf7282372b3850528b7228"
integrity sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==
prosemirror-dropcursor@^1.8.2:
version "1.8.3"
resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz#726ae97baa251097306b0f7194a217a5ad9e8f9f"
integrity sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==
dependencies:
prosemirror-state "^1.0.0"
prosemirror-transform "^1.1.0"
prosemirror-view "^1.1.0"
prosemirror-gapcursor@^1.3.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz#e1144a83b79db7ed0ec32cd0e915a0364220af43"
integrity sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==
prosemirror-gapcursor@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz#da33c905fece147df577342c06f4929b25d365ee"
integrity sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==
dependencies:
prosemirror-keymap "^1.0.0"
prosemirror-model "^1.0.0"
@@ -12909,7 +12852,7 @@ prosemirror-highlight@^0.15.1:
resolved "https://registry.yarnpkg.com/prosemirror-highlight/-/prosemirror-highlight-0.15.1.tgz#27523155005b4652fea84c7a9a2f60b03d10bf57"
integrity sha512-KcJUGNgqLED+eK/cisNtY3M+eDNLkZyWCdyi7B3RoW3rKHnhkKawnJAcr9p1F/e3q+oDB5Y5OiIrC11bxP7tFA==
prosemirror-history@^1.0.0, prosemirror-history@^1.4.1:
prosemirror-history@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.4.1.tgz#cc370a46fb629e83a33946a0e12612e934ab8b98"
integrity sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==
@@ -12919,7 +12862,17 @@ prosemirror-history@^1.0.0, prosemirror-history@^1.4.1:
prosemirror-view "^1.31.0"
rope-sequence "^1.3.0"
prosemirror-inputrules@^1.4.0:
prosemirror-history@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.5.0.tgz#ee21fc5de85a1473e3e3752015ffd6d649a06859"
integrity sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==
dependencies:
prosemirror-state "^1.2.2"
prosemirror-transform "^1.0.0"
prosemirror-view "^1.31.0"
rope-sequence "^1.3.0"
prosemirror-inputrules@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz#d2e935f6086e3801486b09222638f61dae89a570"
integrity sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==
@@ -12927,7 +12880,7 @@ prosemirror-inputrules@^1.4.0:
prosemirror-state "^1.0.0"
prosemirror-transform "^1.0.0"
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2, prosemirror-keymap@^1.2.3:
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz#c0f6ab95f75c0b82c97e44eb6aaf29cbfc150472"
integrity sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==
@@ -12935,40 +12888,14 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2, prosemirror-keymap@^1.2.3:
prosemirror-state "^1.0.0"
w3c-keyname "^2.2.0"
prosemirror-markdown@^1.13.1:
version "1.13.2"
resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz#863eb3fd5f57a444e4378174622b562735b1c503"
integrity sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==
dependencies:
"@types/markdown-it" "^14.0.0"
markdown-it "^14.0.0"
prosemirror-model "^1.25.0"
prosemirror-menu@^1.2.4:
version "1.2.5"
resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz#dea00e7b623cea89f4d76963bee22d2ac2343250"
integrity sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==
dependencies:
crelt "^1.0.0"
prosemirror-commands "^1.0.0"
prosemirror-history "^1.0.0"
prosemirror-state "^1.0.0"
prosemirror-model@^1.0.0, prosemirror-model@^1.21.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0, prosemirror-model@^1.25.4, prosemirror-model@^1.25.8:
version "1.25.9"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.9.tgz#1e9f2faa5d824d65c5265f01768fcfa70bfc39dd"
integrity sha512-pRTklkDDMMRopyoAcrr9wV/8g/RYgrLHBuJAb5hlEuYZRdm5yqmPjWId83fpBwPpSFqEdja0H7Dfd7z1X/npcA==
prosemirror-model@1.25.11, prosemirror-model@^1.0.0, prosemirror-model@^1.21.0, prosemirror-model@^1.25.11, prosemirror-model@^1.25.4, prosemirror-model@^1.25.8:
version "1.25.11"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.11.tgz#2ac01dce5176094a52cc1b889c20f3849563aba9"
integrity sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==
dependencies:
orderedmap "^2.0.0"
prosemirror-schema-basic@^1.2.3:
version "1.2.4"
resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz#389ce1ec09b8a30ea9bbb92c58569cb690c2d695"
integrity sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==
dependencies:
prosemirror-model "^1.25.0"
prosemirror-schema-list@^1.5.0:
prosemirror-schema-list@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz#5869c8f749e8745c394548bb11820b0feb1e32f5"
integrity sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==
@@ -12977,7 +12904,7 @@ prosemirror-schema-list@^1.5.0:
prosemirror-state "^1.0.0"
prosemirror-transform "^1.7.3"
prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3, prosemirror-state@^1.4.4:
prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.4.tgz#72b5e926f9e92dcee12b62a05fcc8a2de3bf5b39"
integrity sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==
@@ -12986,17 +12913,6 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3, pr
prosemirror-transform "^1.0.0"
prosemirror-view "^1.27.0"
prosemirror-tables@^1.6.4:
version "1.8.1"
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.1.tgz#896a234e3e18240b629b747a871369dae78c8a9a"
integrity sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==
dependencies:
prosemirror-keymap "^1.2.2"
prosemirror-model "^1.25.0"
prosemirror-state "^1.4.3"
prosemirror-transform "^1.10.3"
prosemirror-view "^1.39.1"
prosemirror-tables@^1.8.3:
version "1.8.4"
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.4.tgz#4fd7efaf89b96cbab5b40eba658fc5e6fd1c5b6b"
@@ -13008,25 +12924,28 @@ prosemirror-tables@^1.8.3:
prosemirror-transform "^1.10.5"
prosemirror-view "^1.41.4"
prosemirror-trailing-node@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz#5bc223d4fc1e8d9145e4079ec77a932b54e19e04"
integrity sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==
prosemirror-tables@^1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz#104427012e5a5da1d2a38c122efee8d66bdd5104"
integrity sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==
dependencies:
"@remirror/core-constants" "3.0.0"
escape-string-regexp "^4.0.0"
prosemirror-keymap "^1.2.3"
prosemirror-model "^1.25.4"
prosemirror-state "^1.4.4"
prosemirror-transform "^1.10.5"
prosemirror-view "^1.41.4"
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.3, prosemirror-transform@^1.10.5, prosemirror-transform@^1.11.0, prosemirror-transform@^1.7.3:
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.5, prosemirror-transform@^1.11.0, prosemirror-transform@^1.12.0, prosemirror-transform@^1.7.3:
version "1.12.0"
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz#0239288d0e98d91e6af3dd269a8968466be406d7"
integrity sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==
dependencies:
prosemirror-model "^1.21.0"
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.38.1, prosemirror-view@^1.39.1, prosemirror-view@^1.41.4:
version "1.41.9"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.9.tgz#e756493e7116f7df7c73aaf050f60871cab42100"
integrity sha512-clTunTX+eaLbr87L1V1QPheRlEQJyTlL3gXe9x3jQIk3rL0RVWxviDGz8tFaydwIVm+hKhYCyr+R/zBtWr9s6A==
prosemirror-view@1.42.2, prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.41.4, prosemirror-view@^1.41.9:
version "1.42.2"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.42.2.tgz#81c2c70a3c72c188b258449339f5184d946ddb40"
integrity sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==
dependencies:
prosemirror-model "^1.25.8"
prosemirror-state "^1.0.0"
@@ -13055,11 +12974,6 @@ pstree.remy@^1.1.8:
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
punycode.js@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7"
integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==
punycode@^2.1.0, punycode@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
@@ -13145,6 +13059,11 @@ raw-body@^3.0.2:
iconv-lite "~0.7.0"
unpipe "~1.0.0"
re2js@^2.8.6:
version "2.8.6"
resolved "https://registry.yarnpkg.com/re2js/-/re2js-2.8.6.tgz#acbd17a1ad0e98c1f2ee0a2adc17ba0903d9ca95"
integrity sha512-xLgQil4kIUCrAzVk9fRSkxkFNwmygLFjVxXrLc65aE1F0+Zsb8rxumFBy4XKyvgMCTL6kilDq3EZ0piE2dP/Dg==
react-arborist@3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/react-arborist/-/react-arborist-3.4.3.tgz#982791a07d1e279f279be88162c920112f3cee90"
@@ -15248,11 +15167,6 @@ typescript@*, typescript@6.0.3, typescript@^5.0.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21"
integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==
uc.micro@^2.0.0, uc.micro@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee"
integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==
uglify-js@^3.1.4:
version "3.19.3"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f"