mirror of
https://github.com/suitenumerique/docs.git
synced 2026-05-06 07:02:03 +02:00
Compare commits
2 Commits
v2.6.0
...
feature/cu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0815af4977 | ||
|
|
bf0b1591b3 |
@@ -11,6 +11,8 @@ and this project adheres to
|
||||
|
||||
## Added
|
||||
- 📝(doc) Add security.md and codeofconduct.md #604
|
||||
- ✨(frontend) add Alert, Quote, and Divider blocks to the editor #566
|
||||
|
||||
|
||||
## [2.1.0] - 2025-01-29
|
||||
|
||||
|
||||
@@ -368,4 +368,84 @@ test.describe('Doc Editor', () => {
|
||||
|
||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||
});
|
||||
|
||||
test('it checks the divider block', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'divider-block', browserName, 1);
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
// Trigger slash menu to show menu
|
||||
await editor.click();
|
||||
await editor.fill('/');
|
||||
await page.getByText('Divider', { exact: true }).click();
|
||||
|
||||
await expect(
|
||||
editor.locator('.bn-block-content[data-content-type="divider"]'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('it checks the quote block', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'divider-block', browserName, 1);
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
// Trigger slash menu to show menu
|
||||
await editor.click();
|
||||
await editor.fill('/');
|
||||
await page.getByText('Quote', { exact: true }).click();
|
||||
|
||||
await expect(
|
||||
editor.locator('.bn-block-content[data-content-type="quote"]'),
|
||||
).toBeVisible();
|
||||
|
||||
await editor.fill('Hello World');
|
||||
|
||||
await expect(editor.getByText('Hello World')).toHaveCSS(
|
||||
'font-style',
|
||||
'italic',
|
||||
);
|
||||
});
|
||||
|
||||
test('it checks the alert block', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'divider-block', browserName, 1);
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
// Trigger slash menu to show menu
|
||||
await editor.click();
|
||||
await editor.fill('/');
|
||||
await page.getByText('Alert', { exact: true }).click();
|
||||
|
||||
const alertBlock = editor.locator(
|
||||
'.bn-block-content[data-content-type="alert"]',
|
||||
);
|
||||
await expect(
|
||||
alertBlock.locator('div[data-alert-type="warning"]'),
|
||||
).toBeVisible();
|
||||
await editor.fill('My alert');
|
||||
await expect(alertBlock.getByText('My alert')).toBeVisible();
|
||||
|
||||
await alertBlock.getByText('warning').click();
|
||||
|
||||
await expect(
|
||||
alertBlock.getByRole('menuitem', { name: 'warning Warning' }),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
alertBlock.getByRole('menuitem', { name: 'error Error' }),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
alertBlock.getByRole('menuitem', { name: 'info Info' }),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
alertBlock.getByRole('menuitem', { name: 'check_circle Success' }),
|
||||
).toBeVisible();
|
||||
|
||||
await alertBlock
|
||||
.getByRole('menuitem', { name: 'check_circle Success' })
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
alertBlock.locator('div[data-alert-type="success"]'),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Dictionary, locales } from '@blocknote/core';
|
||||
import {
|
||||
BlockNoteEditor as BlockNoteEditorCore,
|
||||
BlockNoteSchema,
|
||||
Dictionary,
|
||||
defaultBlockSpecs,
|
||||
locales,
|
||||
} from '@blocknote/core';
|
||||
import '@blocknote/core/fonts/inter.css';
|
||||
import { BlockNoteView } from '@blocknote/mantine';
|
||||
import '@blocknote/mantine/style.css';
|
||||
@@ -6,7 +12,6 @@ import { useCreateBlockNote } from '@blocknote/react';
|
||||
import { HocuspocusProvider } from '@hocuspocus/provider';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { Box, TextErrors } from '@/components';
|
||||
@@ -17,95 +22,27 @@ import { useUploadFile } from '../hook';
|
||||
import { useHeadings } from '../hook/useHeadings';
|
||||
import useSaveDoc from '../hook/useSaveDoc';
|
||||
import { useEditorStore } from '../stores';
|
||||
import { cssEditor } from '../styles';
|
||||
import { randomColor } from '../utils';
|
||||
|
||||
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
|
||||
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
||||
import { AlertBlock, DividerBlock, QuoteBlock } from './custom-blocks';
|
||||
|
||||
const cssEditor = (readonly: boolean) => css`
|
||||
&,
|
||||
& > .bn-container,
|
||||
& .ProseMirror {
|
||||
height: 100%;
|
||||
export const schema = BlockNoteSchema.create({
|
||||
blockSpecs: {
|
||||
...defaultBlockSpecs,
|
||||
alert: AlertBlock,
|
||||
quote: QuoteBlock,
|
||||
divider: DividerBlock,
|
||||
},
|
||||
});
|
||||
|
||||
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
||||
height: 50px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
||||
height: 43px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
||||
height: 35px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
a {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
cursor: pointer;
|
||||
}
|
||||
.bn-block-group
|
||||
.bn-block-group
|
||||
.bn-block-outer:not([data-prev-depth-changed]):before {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bn-editor {
|
||||
color: var(--c--theme--colors--greyscale-700);
|
||||
}
|
||||
|
||||
.bn-block-outer:not(:first-child) {
|
||||
&:has(h1) {
|
||||
padding-top: 32px;
|
||||
}
|
||||
&:has(h2) {
|
||||
padding-top: 24px;
|
||||
}
|
||||
&:has(h3) {
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
& .bn-inline-content code {
|
||||
background-color: gainsboro;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media screen and (width <= 560px) {
|
||||
& .bn-editor {
|
||||
${readonly && `padding-left: 10px;`}
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
||||
height: 46px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
||||
height: 40px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
||||
height: 40px;
|
||||
}
|
||||
& .bn-editor h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
& .bn-editor h2 {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
& .bn-editor h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
|
||||
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type DocsBlockNoteEditor = BlockNoteEditorCore<
|
||||
typeof schema.blockSchema,
|
||||
typeof schema.inlineContentSchema,
|
||||
typeof schema.styleSchema
|
||||
>;
|
||||
|
||||
interface BlockNoteEditorProps {
|
||||
doc: Doc;
|
||||
@@ -164,6 +101,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
},
|
||||
},
|
||||
dictionary: locales[lang as keyof typeof locales] as Dictionary,
|
||||
schema,
|
||||
uploadFile,
|
||||
},
|
||||
[collabName, lang, provider, uploadFile],
|
||||
@@ -198,8 +136,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
editor={editor}
|
||||
formattingToolbar={false}
|
||||
editable={!readOnly}
|
||||
slashMenu={false}
|
||||
theme="light"
|
||||
>
|
||||
<BlockNoteSuggestionMenu />
|
||||
<BlockNoteToolbar />
|
||||
</BlockNoteView>
|
||||
</Box>
|
||||
@@ -225,6 +165,7 @@ export const BlockNoteEditorVersion = ({
|
||||
},
|
||||
provider: undefined,
|
||||
},
|
||||
schema,
|
||||
},
|
||||
[initialContent],
|
||||
);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { combineByGroup, filterSuggestionItems } from '@blocknote/core';
|
||||
import '@blocknote/mantine/style.css';
|
||||
import {
|
||||
SuggestionMenuController,
|
||||
getDefaultReactSlashMenuItems,
|
||||
useBlockNoteEditor,
|
||||
} from '@blocknote/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DocsBlockNoteEditor } from './BlockNoteEditor';
|
||||
import { insertAlert, insertDivider, insertQuote } from './custom-blocks';
|
||||
|
||||
export const BlockNoteSuggestionMenu = () => {
|
||||
const editor = useBlockNoteEditor() as DocsBlockNoteEditor;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getSlashMenuItems = useMemo(() => {
|
||||
return async (query: string) =>
|
||||
Promise.resolve(
|
||||
filterSuggestionItems(
|
||||
combineByGroup(
|
||||
getDefaultReactSlashMenuItems(editor),
|
||||
[insertAlert(editor, t)],
|
||||
[insertQuote(editor, t)],
|
||||
[insertDivider(editor, t)],
|
||||
),
|
||||
query,
|
||||
),
|
||||
);
|
||||
}, [editor, t]);
|
||||
|
||||
return (
|
||||
<SuggestionMenuController
|
||||
triggerCharacter="/"
|
||||
getItems={getSlashMenuItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,179 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
|
||||
import { createReactBlockSpec } from '@blocknote/react';
|
||||
import { Menu } from '@mantine/core';
|
||||
import { TFunction } from 'i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../BlockNoteEditor';
|
||||
|
||||
// The types of alerts that users can choose from.
|
||||
export const alertTypes = [
|
||||
{
|
||||
title: 'Warning',
|
||||
value: 'warning',
|
||||
icon: 'warning',
|
||||
color: 'warning-500',
|
||||
backgroundColor: 'warning-300',
|
||||
},
|
||||
{
|
||||
title: 'Error',
|
||||
value: 'danger',
|
||||
icon: 'error',
|
||||
color: 'danger-500',
|
||||
backgroundColor: 'danger-300',
|
||||
},
|
||||
{
|
||||
title: 'Info',
|
||||
value: 'info',
|
||||
icon: 'info',
|
||||
color: 'info-500',
|
||||
backgroundColor: 'info-300',
|
||||
},
|
||||
{
|
||||
title: 'Success',
|
||||
value: 'success',
|
||||
icon: 'check_circle',
|
||||
color: 'success-500',
|
||||
backgroundColor: 'success-100',
|
||||
},
|
||||
] as const;
|
||||
|
||||
// The Alert block.
|
||||
export const AlertBlock = createReactBlockSpec(
|
||||
{
|
||||
type: 'alert',
|
||||
propSchema: {
|
||||
textAlignment: defaultProps.textAlignment,
|
||||
textColor: defaultProps.textColor,
|
||||
type: {
|
||||
default: 'warning',
|
||||
values: ['warning', 'danger', 'info', 'success'],
|
||||
},
|
||||
},
|
||||
content: 'inline',
|
||||
},
|
||||
{
|
||||
render: (props) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const { t } = useTranslation();
|
||||
let alertType = alertTypes.find(
|
||||
(a) => a.value === props.block.props.type,
|
||||
);
|
||||
|
||||
if (!alertType) {
|
||||
alertType = alertTypes[0];
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="alert"
|
||||
data-alert-type={props.block.props.type}
|
||||
$direction="row"
|
||||
$justify="center"
|
||||
$align="center"
|
||||
$radius="4px"
|
||||
$padding="4px"
|
||||
$background={colorsTokens()[alertType.backgroundColor]}
|
||||
$minHeight="48px"
|
||||
$css={css`
|
||||
flex-grow: 1;
|
||||
`}
|
||||
>
|
||||
<Menu withinPortal={false}>
|
||||
<Menu.Target>
|
||||
<Box
|
||||
className="alert-icon-wrapper"
|
||||
$margin={{ horizontal: '12px' }}
|
||||
$radius="16px"
|
||||
$justify="center"
|
||||
$align="center"
|
||||
$height="24px"
|
||||
$width="24px"
|
||||
contentEditable={false}
|
||||
$css="user-select: none; cursor: pointer;"
|
||||
>
|
||||
<Text
|
||||
$isMaterialIcon
|
||||
$theme={alertType.value}
|
||||
$variation="500"
|
||||
$size="20px"
|
||||
>
|
||||
{alertType.icon}
|
||||
</Text>
|
||||
</Box>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown style={{ zIndex: 9999 }}>
|
||||
<Menu.Label>{t('Alert Type')}</Menu.Label>
|
||||
<Menu.Divider />
|
||||
{alertTypes.map((type) => (
|
||||
<Menu.Item
|
||||
key={type.value}
|
||||
leftSection={
|
||||
<Text
|
||||
$isMaterialIcon
|
||||
$color={colorsTokens()[type.color]}
|
||||
$size="16px"
|
||||
>
|
||||
{type.icon}
|
||||
</Text>
|
||||
}
|
||||
onClick={() =>
|
||||
props.editor.updateBlock(props.block, {
|
||||
type: 'alert',
|
||||
props: { type: type.value },
|
||||
})
|
||||
}
|
||||
>
|
||||
{t(type.title)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
<Box
|
||||
className="inline-content"
|
||||
$css={css`
|
||||
flex-grow: 1;
|
||||
& * {
|
||||
color: ${colorsTokens()[alertType.color]};
|
||||
}
|
||||
`}
|
||||
ref={props.contentRef}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const insertAlert = (
|
||||
editor: DocsBlockNoteEditor,
|
||||
t: TFunction<'translation', undefined>,
|
||||
) => ({
|
||||
title: t('Alert'),
|
||||
onItemClick: () => {
|
||||
insertOrUpdateBlock(editor, {
|
||||
type: 'alert',
|
||||
});
|
||||
},
|
||||
aliases: [
|
||||
'alert',
|
||||
'notification',
|
||||
'emphasize',
|
||||
'warning',
|
||||
'error',
|
||||
'info',
|
||||
'success',
|
||||
],
|
||||
group: t('Others'),
|
||||
icon: (
|
||||
<Text $isMaterialIcon $size="18px">
|
||||
warning
|
||||
</Text>
|
||||
),
|
||||
subtext: t('Add a colored alert box'),
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
|
||||
import { createReactBlockSpec } from '@blocknote/react';
|
||||
import { TFunction } from 'i18next';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../BlockNoteEditor';
|
||||
|
||||
export const DividerBlock = createReactBlockSpec(
|
||||
{
|
||||
type: 'divider',
|
||||
propSchema: {
|
||||
textAlignment: defaultProps.textAlignment,
|
||||
textColor: defaultProps.textColor,
|
||||
},
|
||||
content: 'none',
|
||||
},
|
||||
{
|
||||
render: () => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '2px',
|
||||
backgroundColor: colorsTokens()['greyscale-300'],
|
||||
margin: '1rem 0',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const insertDivider = (
|
||||
editor: DocsBlockNoteEditor,
|
||||
t: TFunction<'translation', undefined>,
|
||||
) => ({
|
||||
title: t('Divider'),
|
||||
onItemClick: () => {
|
||||
insertOrUpdateBlock(editor, {
|
||||
type: 'divider',
|
||||
});
|
||||
},
|
||||
aliases: ['divider', 'hr', 'horizontal rule', 'line', 'separator'],
|
||||
group: t('Others'),
|
||||
icon: (
|
||||
<span className="material-icons" style={{ fontSize: '18px' }}>
|
||||
remove
|
||||
</span>
|
||||
),
|
||||
subtext: t('Add a horizontal line'),
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
|
||||
import { createReactBlockSpec } from '@blocknote/react';
|
||||
import { TFunction } from 'i18next';
|
||||
import React from 'react';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../BlockNoteEditor';
|
||||
|
||||
export const QuoteBlock = createReactBlockSpec(
|
||||
{
|
||||
type: 'quote',
|
||||
propSchema: {
|
||||
textAlignment: defaultProps.textAlignment,
|
||||
textColor: defaultProps.textColor,
|
||||
},
|
||||
content: 'inline',
|
||||
},
|
||||
{
|
||||
render: (props) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="inline-content"
|
||||
style={{
|
||||
borderLeft: `4px solid ${colorsTokens()['greyscale-300']}`,
|
||||
margin: '0 0 1rem 0',
|
||||
padding: '0.5rem 1rem',
|
||||
color: colorsTokens()['greyscale-600'],
|
||||
fontStyle: 'italic',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
ref={props.contentRef}
|
||||
/>
|
||||
);
|
||||
},
|
||||
parse: () => {
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const insertQuote = (
|
||||
editor: DocsBlockNoteEditor,
|
||||
t: TFunction<'translation', undefined>,
|
||||
) => ({
|
||||
title: t('Quote'),
|
||||
onItemClick: () => {
|
||||
insertOrUpdateBlock(editor, {
|
||||
type: 'quote',
|
||||
});
|
||||
},
|
||||
aliases: ['quote', 'blockquote', 'citation'],
|
||||
group: t('Others'),
|
||||
icon: (
|
||||
<span className="material-icons" style={{ fontSize: '18px' }}>
|
||||
format_quote
|
||||
</span>
|
||||
),
|
||||
subtext: t('Add a quote block'),
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './AlertBlock';
|
||||
export * from './DividerBlock';
|
||||
export * from './QuoteBlock';
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../components/BlockNoteEditor';
|
||||
import { useHeadingStore } from '../stores';
|
||||
|
||||
export const useHeadings = (editor: BlockNoteEditor) => {
|
||||
export const useHeadings = (editor: DocsBlockNoteEditor) => {
|
||||
const { setHeadings, resetHeadings } = useHeadingStore();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../components/BlockNoteEditor';
|
||||
|
||||
export interface UseEditorstore {
|
||||
editor?: BlockNoteEditor;
|
||||
setEditor: (editor: BlockNoteEditor | undefined) => void;
|
||||
editor?: DocsBlockNoteEditor;
|
||||
setEditor: (editor: DocsBlockNoteEditor | undefined) => void;
|
||||
}
|
||||
|
||||
export const useEditorStore = create<UseEditorstore>((set) => ({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../components/BlockNoteEditor';
|
||||
import { HeadingBlock } from '../types';
|
||||
|
||||
const recursiveTextContent = (content: HeadingBlock['content']): string => {
|
||||
@@ -21,7 +21,7 @@ const recursiveTextContent = (content: HeadingBlock['content']): string => {
|
||||
|
||||
export interface UseHeadingStore {
|
||||
headings: HeadingBlock[];
|
||||
setHeadings: (editor: BlockNoteEditor) => void;
|
||||
setHeadings: (editor: DocsBlockNoteEditor) => void;
|
||||
resetHeadings: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { css } from 'styled-components';
|
||||
|
||||
export const cssEditor = (readonly: boolean) => css`
|
||||
&,
|
||||
& > .bn-container,
|
||||
& .ProseMirror {
|
||||
height: 100%;
|
||||
|
||||
.bn-side-menu[data-block-type='alert'] {
|
||||
height: 55px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='divider'] {
|
||||
height: 40px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='quote'] {
|
||||
height: 46px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
||||
height: 50px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
||||
height: 43px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
||||
height: 35px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
a {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
cursor: pointer;
|
||||
}
|
||||
.bn-block-group
|
||||
.bn-block-group
|
||||
.bn-block-outer:not([data-prev-depth-changed]):before {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bn-editor {
|
||||
color: var(--c--theme--colors--greyscale-700);
|
||||
}
|
||||
|
||||
.bn-block-outer:not(:first-child) {
|
||||
&:has(h1) {
|
||||
padding-top: 32px;
|
||||
}
|
||||
&:has(h2) {
|
||||
padding-top: 24px;
|
||||
}
|
||||
&:has(h3) {
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
& .bn-inline-content code {
|
||||
background-color: gainsboro;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media screen and (width <= 560px) {
|
||||
& .bn-editor {
|
||||
${readonly && `padding-left: 10px;`}
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
||||
height: 46px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
||||
height: 40px;
|
||||
}
|
||||
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
||||
height: 40px;
|
||||
}
|
||||
& .bn-editor h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
& .bn-editor h2 {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
& .bn-editor h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
|
||||
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1,10 +1,11 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { BoxButton, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../../doc-editor/components/BlockNoteEditor';
|
||||
|
||||
const leftPaddingMap: { [key: number]: string } = {
|
||||
3: '1.5rem',
|
||||
2: '0.9rem',
|
||||
@@ -17,7 +18,7 @@ export type HeadingsHighlight = {
|
||||
}[];
|
||||
|
||||
interface HeadingProps {
|
||||
editor: BlockNoteEditor;
|
||||
editor: DocsBlockNoteEditor;
|
||||
level: number;
|
||||
text: string;
|
||||
headingId: string;
|
||||
|
||||
Reference in New Issue
Block a user