Skip to content

feat: Block quote #1563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/pages/docs/editor-basics/default-schema.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ type HeadingBlock = {

`level:` The heading level, representing a title (`level: 1`), heading (`level: 2`), and subheading (`level: 3`).

#### Quote

**Type & Props**

```typescript
type QuoteBlock = {
id: string;
type: "quote";
props: DefaultProps;
content: InlineContent[];
children: Block[];
};
```

#### Bullet List Item

**Type & Props**
Expand Down
4 changes: 4 additions & 0 deletions examples/01-basic/04-default-blocks/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export default function App() {
type: "heading",
content: "Heading",
},
{
type: "quote",
content: "Quote",
},
{
type: "bulletListItem",
content: "Bullet List Item",
Expand Down
65 changes: 65 additions & 0 deletions packages/core/src/blocks/QuoteBlockContent/QuoteBlockContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
createBlockSpecFromStronglyTypedTiptapNode,
createStronglyTypedTiptapNode,
} from "../../schema/index.js";
import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
import { defaultProps } from "../defaultProps.js";
import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js";
import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js";

export const quotePropSchema = {
...defaultProps,
};

export const QuoteBlockContent = createStronglyTypedTiptapNode({
name: "quote",
content: "inline*",
group: "blockContent",

addKeyboardShortcuts() {
return {
"Mod-Alt-q": () => {
const blockInfo = getBlockInfoFromSelection(this.editor.state);
if (
!blockInfo.isBlockContainer ||
blockInfo.blockContent.node.type.spec.content !== "inline*"
) {
return true;
}

return this.editor.commands.command(
updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, {
type: "quote",
})
);
},
};
},

parseHTML() {
return [
{ tag: "div[data-content-type=" + this.name + "]" },
{
tag: "blockquote",
node: "quote",
},
];
},

renderHTML({ HTMLAttributes }) {
return createDefaultBlockDOMOutputSpec(
this.name,
"blockquote",
{
...(this.options.domAttributes?.blockContent || {}),
...HTMLAttributes,
},
this.options.domAttributes?.inlineContent || {}
);
},
});

export const Quote = createBlockSpecFromStronglyTypedTiptapNode(
QuoteBlockContent,
quotePropSchema
);
2 changes: 2 additions & 0 deletions packages/core/src/blocks/defaultBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { BulletListItem } from "./ListItemBlockContent/BulletListItemBlockConten
import { CheckListItem } from "./ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js";
import { NumberedListItem } from "./ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js";
import { Paragraph } from "./ParagraphBlockContent/ParagraphBlockContent.js";
import { Quote } from "./QuoteBlockContent/QuoteBlockContent.js";
import { Table } from "./TableBlockContent/TableBlockContent.js";
import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js";

Expand All @@ -37,6 +38,7 @@ export { customizeCodeBlock } from "./CodeBlockContent/CodeBlockContent.js";
export const defaultBlockSpecs = {
paragraph: Paragraph,
heading: Heading,
quote: Quote,
codeBlock: CodeBlock,
bulletListItem: BulletListItem,
numberedListItem: NumberedListItem,
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/editor/Block.css
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ NESTED BLOCKS
font-weight: bold;
}

/* QUOTES */
[data-content-type="quote"] blockquote {
border-left: 2px solid rgb(125, 121, 122);
color: rgb(125, 121, 122);
margin: 0;
padding-left: 1em;
}

/* LISTS */

.bn-block-content::before {
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/editor/BlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ export type BlockNoteEditorOptions<
*/
setIdAttribute?: boolean;

/**
* A flag indicating whether to show the grey left border on nested blocks.
*/
showNestingIndicator: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'm not sure I like this.
Should it be on the editor instance? It is purely for styling no?
Why do we need this? Can't this be done with CSS?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this was a suggestion from @YousefED, but I may have misunderstood. You're right though, you can just do the same thing with CSS:

.bn-block-group
.bn-block-group
.bn-block-outer:not([data-prev-depth-changed])::before {
  border-left: none !important;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea was:

  • to make the quoteblock properly work, the lines need to be disabled
  • maybe it makes sense to disable the lines anyway (as per our "follow notion design" guideline)
  • if we disable the lines by default; I think it makes sense to offer (existing) users an easy way to re-add them

Don't feel to strong about this I'll let @nperez0111 decide. We could also defer the border-left change to a later moment (it might make sense to also indent the draghandle when we do this)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll revert the showNestingIndicator changes for now then - if people want to disable the nesting lines we can just paste the CSS styles above as a solution. I think it's a good idea to revisit this alongside the upcoming theming overhaul, as the "follow notion design" guideline will be highly relevant there too.


/**
* The detection mode for showing the side menu - "viewport" always shows the
* side menu for the block next to the mouse cursor, while "editor" only shows
Expand Down Expand Up @@ -494,6 +499,7 @@ export class BlockNoteEditor<
// apply defaults
const newOptions = {
defaultStyles: true,
showNestingIndicator: options.showNestingIndicator || true,
schema: options.schema || BlockNoteSchema.create(),
_headless: false,
...options,
Expand Down Expand Up @@ -651,7 +657,8 @@ export class BlockNoteEditor<
class: mergeCSSClasses(
"bn-editor",
newOptions.defaultStyles ? "bn-default-styles" : "",
newOptions.domAttributes?.editor?.class || ""
newOptions.domAttributes?.editor?.class || "",
newOptions.showNestingIndicator ? "bn-show-nesting-indicator" : ""
),
},
transformPasted,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ export function getDefaultSlashMenuItems<
);
}

if (checkDefaultBlockTypeInSchema("quote", editor)) {
items.push({
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "quote",
});
},
key: "quote",
...editor.dictionary.slash_menu.quote,
});
}

if (checkDefaultBlockTypeInSchema("numberedListItem", editor)) {
items.push({
onItemClick: () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const ar: Dictionary = {
aliases: ["ع3", "عنوان3", "عنوان فرعي"],
group: "العناوين",
},
quote: {
title: "اقتباس",
subtext: "اقتباس أو مقتطف",
aliases: ["quotation", "blockquote", "bq"],
group: "الكتل الأساسية",
},
numbered_list: {
title: "قائمة مرقمة",
subtext: "تستخدم لعرض قائمة مرقمة",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const de: Dictionary = {
aliases: ["h3", "überschrift3", "unterüberschrift"],
group: "Überschriften",
},
quote: {
title: "Zitat",
subtext: "Zitat oder Auszug",
aliases: ["quotation", "blockquote", "bq"],
group: "Grundlegende blöcke",
},
numbered_list: {
title: "Nummerierte Liste",
subtext: "Liste mit nummerierten Elementen",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export const en = {
aliases: ["h3", "heading3", "subheading"],
group: "Headings",
},
quote: {
title: "Quote",
subtext: "Quote or excerpt",
aliases: ["quotation", "blockquote", "bq"],
group: "Basic blocks",
},
numbered_list: {
title: "Numbered List",
subtext: "List with ordered items",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const es: Dictionary = {
aliases: ["h3", "encabezado3", "subencabezado"],
group: "Encabezados",
},
quote: {
title: "Cita",
subtext: "Cita o extracto",
aliases: ["quotation", "blockquote", "bq"],
group: "Bloques básicos",
},
numbered_list: {
title: "Lista Numerada",
subtext: "Lista con elementos ordenados",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export const fr: Dictionary = {
aliases: ["h3", "titre3", "sous-titre"],
group: "Titres",
},
quote: {
title: "Citation",
subtext: "Citation ou extrait",
aliases: ["quotation", "blockquote", "bq"],
group: "Blocs de base",
},
numbered_list: {
title: "Liste Numérotée",
subtext: "Utilisé pour afficher une liste numérotée",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/hr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const hr: Dictionary = {
aliases: ["h3", "naslov3", "podnaslov"],
group: "Naslovi",
},
quote: {
title: "Citat",
subtext: "Citat ili izvadak",
aliases: ["quotation", "blockquote", "bq"],
group: "Osnovni blokovi",
},
numbered_list: {
title: "Numerirani popis",
subtext: "Popis s numeriranim stavkama",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const is: Dictionary = {
aliases: ["h3", "fyrirsogn3", "undirfyrirsogn"],
group: "Fyrirsagnir",
},
quote: {
title: "Tilvitnun",
subtext: "Tilvitnun eða útdráttur",
aliases: ["quotation", "blockquote", "bq"],
group: "Grunnblokkar",
},
numbered_list: {
title: "Númeruð listi",
subtext: "Notað til að birta númeraðan lista",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const it: Dictionary = {
aliases: ["h3", "intestazione3", "sottotitolo"],
group: "Intestazioni",
},
quote: {
title: "Citazione",
subtext: "Citazione o estratto",
aliases: ["quotation", "blockquote", "bq"],
group: "Blocchi Base",
},
numbered_list: {
title: "Elenco Numerato",
subtext: "Elenco con elementi ordinati",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const ja: Dictionary = {
aliases: ["h3", "見出し3", "subheading", "小見出し"],
group: "見出し",
},
quote: {
title: "引用",
subtext: "引用または抜粋",
aliases: ["quotation", "blockquote", "bq"],
group: "基本ブロック",
},
numbered_list: {
title: "番号付リスト",
subtext: "番号付リストを表示するために使用",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const ko: Dictionary = {
aliases: ["h3", "제목3", "subheading"],
group: "제목",
},
quote: {
title: "인용",
subtext: "인용문 또는 발췌",
aliases: ["quotation", "blockquote", "bq"],
group: "기본 블록",
},
numbered_list: {
title: "번호 매기기 목록",
subtext: "번호가 매겨진 목록을 추가합니다.",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const nl: Dictionary = {
aliases: ["h3", "kop3", "subkop"],
group: "Koppen",
},
quote: {
title: "Citaat",
subtext: "Citaat of uittreksel",
aliases: ["quotation", "blockquote", "bq"],
group: "Basisblokken",
},
numbered_list: {
title: "Genummerde Lijst",
subtext: "Gebruikt om een genummerde lijst weer te geven",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/no.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const no: Dictionary = {
aliases: ["h3", "overskrift3", "underoverskrift"],
group: "Overskrifter",
},
quote: {
title: "Sitat",
subtext: "Sitat eller utdrag",
aliases: ["quotation", "blockquote", "bq"],
group: "Grunnleggende blokker",
},
numbered_list: {
title: "Nummerert liste",
subtext: "Liste med ordnede elementer",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const pl: Dictionary = {
aliases: ["h3", "naglowek3", "podnaglowek"],
group: "Nagłówki",
},
quote: {
title: "Cytat",
subtext: "Cytat lub fragment",
aliases: ["quotation", "blockquote", "bq"],
group: "Podstawowe bloki",
},
numbered_list: {
title: "Lista numerowana",
subtext: "Używana do wyświetlania listy numerowanej",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const pt: Dictionary = {
aliases: ["h3", "titulo3", "subtitulo"],
group: "Títulos",
},
quote: {
title: "Citação",
subtext: "Citação ou trecho",
aliases: ["quotation", "blockquote", "bq"],
group: "Blocos básicos",
},
numbered_list: {
title: "Lista Numerada",
subtext: "Usado para exibir uma lista numerada",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const ru: Dictionary = {
aliases: ["h3", "heading3", "subheading", "заголовок3", "подзаголовок"],
group: "Заголовки",
},
quote: {
title: "Цитата",
subtext: "Цитата или отрывок",
aliases: ["quotation", "blockquote", "bq"],
group: "Базовые блоки",
},
numbered_list: {
title: "Нумерованный список",
subtext: "Используется для отображения нумерованного списка",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/uk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const uk: Dictionary = {
aliases: ["h3", "heading3", "subheading", "заголовок3"],
group: "Заголовки",
},
quote: {
title: "Цитата",
subtext: "Цитата або уривок",
aliases: ["quotation", "blockquote", "bq"],
group: "Базові блоки",
},
numbered_list: {
title: "Нумерований список",
subtext: "Список із впорядкованими елементами",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/i18n/locales/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const vi: Dictionary = {
aliases: ["h3", "tieude3", "tieudephu"],
group: "Tiêu đề",
},
quote: {
title: "Trích dẫn",
subtext: "Trích dẫn hoặc đoạn trích",
aliases: ["quotation", "blockquote", "bq"],
group: "Khối cơ bản",
},
numbered_list: {
title: "Danh sách đánh số",
subtext: "Sử dụng để hiển thị danh sách có đánh số",
Expand Down
Loading
Loading