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 all commits
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
98 changes: 98 additions & 0 deletions packages/core/src/blocks/QuoteBlockContent/QuoteBlockContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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";
import { InputRule } from "@tiptap/core";

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

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

addInputRules() {
return [
// Creates a block quote when starting with ">".
new InputRule({
find: new RegExp(`^>\\s$`),
handler: ({ state, chain, range }) => {
const blockInfo = getBlockInfoFromSelection(state);
if (
!blockInfo.isBlockContainer ||
blockInfo.blockContent.node.type.spec.content !== "inline*"
) {
return;
}

chain()
.command(
updateBlockCommand(
this.options.editor,
blockInfo.bnBlock.beforePos,
{
type: "quote",
props: {},
}
)
)
// Removes the ">" character used to set the list.
.deleteRange({ from: range.from, to: range.to });
},
}),
];
},

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
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
Loading
Loading