@tiptap/extension-paragraph 3.17.0 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -27,6 +27,8 @@ module.exports = __toCommonJS(index_exports);
27
27
 
28
28
  // src/paragraph.ts
29
29
  var import_core = require("@tiptap/core");
30
+ var EMPTY_PARAGRAPH_MARKDOWN = " ";
31
+ var NBSP_CHAR = "\xA0";
30
32
  var Paragraph = import_core.Node.create({
31
33
  name: "paragraph",
32
34
  priority: 1e3,
@@ -48,18 +50,21 @@ var Paragraph = import_core.Node.create({
48
50
  if (tokens.length === 1 && tokens[0].type === "image") {
49
51
  return helpers.parseChildren([tokens[0]]);
50
52
  }
51
- return helpers.createNode(
52
- "paragraph",
53
- void 0,
54
- // no attributes for paragraph
55
- helpers.parseInline(tokens)
56
- );
53
+ const content = helpers.parseInline(tokens);
54
+ if (content.length === 1 && content[0].type === "text" && (content[0].text === EMPTY_PARAGRAPH_MARKDOWN || content[0].text === NBSP_CHAR)) {
55
+ return helpers.createNode("paragraph", void 0, []);
56
+ }
57
+ return helpers.createNode("paragraph", void 0, content);
57
58
  },
58
59
  renderMarkdown: (node, h) => {
59
- if (!node || !Array.isArray(node.content)) {
60
+ if (!node) {
60
61
  return "";
61
62
  }
62
- return h.renderChildren(node.content);
63
+ const content = Array.isArray(node.content) ? node.content : [];
64
+ if (content.length === 0) {
65
+ return EMPTY_PARAGRAPH_MARKDOWN;
66
+ }
67
+ return h.renderChildren(content);
63
68
  },
64
69
  addCommands() {
65
70
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/paragraph.ts"],"sourcesContent":["import { Paragraph } from './paragraph.js'\n\nexport * from './paragraph.js'\n\nexport default Paragraph\n","import { mergeAttributes, Node } from '@tiptap/core'\n\nexport interface ParagraphOptions {\n /**\n * The HTML attributes for a paragraph node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n paragraph: {\n /**\n * Toggle a paragraph\n * @example editor.commands.toggleParagraph()\n */\n setParagraph: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to create paragraphs.\n * @see https://www.tiptap.dev/api/nodes/paragraph\n */\nexport const Paragraph = Node.create<ParagraphOptions>({\n name: 'paragraph',\n\n priority: 1000,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n group: 'block',\n\n content: 'inline*',\n\n parseHTML() {\n return [{ tag: 'p' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n parseMarkdown: (token, helpers) => {\n const tokens = token.tokens || []\n\n // Special case: if paragraph contains only a single image token,\n // unwrap it to avoid nesting block elements incorrectly\n if (tokens.length === 1 && tokens[0].type === 'image') {\n // Parse the image token directly as a block element\n return helpers.parseChildren([tokens[0]])\n }\n\n // Convert 'paragraph' token to paragraph node\n return helpers.createNode(\n 'paragraph',\n undefined, // no attributes for paragraph\n helpers.parseInline(tokens),\n )\n },\n\n renderMarkdown: (node, h) => {\n if (!node || !Array.isArray(node.content)) {\n return ''\n }\n\n return h.renderChildren(node.content)\n },\n\n addCommands() {\n return {\n setParagraph:\n () =>\n ({ commands }) => {\n return commands.setNode(this.name)\n },\n }\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-Alt-0': () => this.editor.commands.setParagraph(),\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsC;AA2B/B,IAAM,YAAY,iBAAK,OAAyB;AAAA,EACrD,MAAM;AAAA,EAEN,UAAU;AAAA,EAEV,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,EAEP,SAAS;AAAA,EAET,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EACtB;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,WAAO,CAAC,SAAK,6BAAgB,KAAK,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,EAC9E;AAAA,EAEA,eAAe,CAAC,OAAO,YAAY;AACjC,UAAM,SAAS,MAAM,UAAU,CAAC;AAIhC,QAAI,OAAO,WAAW,KAAK,OAAO,CAAC,EAAE,SAAS,SAAS;AAErD,aAAO,QAAQ,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,IAC1C;AAGA,WAAO,QAAQ;AAAA,MACb;AAAA,MACA;AAAA;AAAA,MACA,QAAQ,YAAY,MAAM;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,gBAAgB,CAAC,MAAM,MAAM;AAC3B,QAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,KAAK,OAAO,GAAG;AACzC,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,eAAe,KAAK,OAAO;AAAA,EACtC;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,cACE,MACA,CAAC,EAAE,SAAS,MAAM;AAChB,eAAO,SAAS,QAAQ,KAAK,IAAI;AAAA,MACnC;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,uBAAuB;AACrB,WAAO;AAAA,MACL,aAAa,MAAM,KAAK,OAAO,SAAS,aAAa;AAAA,IACvD;AAAA,EACF;AACF,CAAC;;;ADvFD,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/paragraph.ts"],"sourcesContent":["import { Paragraph } from './paragraph.js'\n\nexport * from './paragraph.js'\n\nexport default Paragraph\n","import { mergeAttributes, Node } from '@tiptap/core'\n\nexport interface ParagraphOptions {\n /**\n * The HTML attributes for a paragraph node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n paragraph: {\n /**\n * Toggle a paragraph\n * @example editor.commands.toggleParagraph()\n */\n setParagraph: () => ReturnType\n }\n }\n}\n\n/**\n * Markdown marker for empty paragraphs to preserve blank lines.\n * Using &nbsp; (non-breaking space HTML entity) ensures the paragraph\n * is not collapsed by markdown parsers while remaining human-readable.\n */\nconst EMPTY_PARAGRAPH_MARKDOWN = '&nbsp;'\n\n/**\n * Unicode character for non-breaking space (U+00A0).\n * Some markdown parsers may convert &nbsp; entities to this literal character.\n */\nconst NBSP_CHAR = '\\u00A0'\n\n/**\n * This extension allows you to create paragraphs.\n * @see https://www.tiptap.dev/api/nodes/paragraph\n */\nexport const Paragraph = Node.create<ParagraphOptions>({\n name: 'paragraph',\n\n priority: 1000,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n group: 'block',\n\n content: 'inline*',\n\n parseHTML() {\n return [{ tag: 'p' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n parseMarkdown: (token, helpers) => {\n const tokens = token.tokens || []\n\n // Special case: if paragraph contains only a single image token,\n // unwrap it to avoid nesting block elements incorrectly\n if (tokens.length === 1 && tokens[0].type === 'image') {\n // Parse the image token directly as a block element\n return helpers.parseChildren([tokens[0]])\n }\n\n // Parse the inline tokens\n const content = helpers.parseInline(tokens)\n\n // Special case: if paragraph contains only &nbsp; (non-breaking space),\n // treat it as an empty paragraph to preserve blank lines\n if (\n content.length === 1 &&\n content[0].type === 'text' &&\n (content[0].text === EMPTY_PARAGRAPH_MARKDOWN || content[0].text === NBSP_CHAR)\n ) {\n return helpers.createNode('paragraph', undefined, [])\n }\n\n // Convert 'paragraph' token to paragraph node\n return helpers.createNode('paragraph', undefined, content)\n },\n\n renderMarkdown: (node, h) => {\n if (!node) {\n return ''\n }\n\n // Normalize content: treat undefined/null as empty array\n const content = Array.isArray(node.content) ? node.content : []\n\n // If the paragraph is empty, render a non-breaking space to preserve blank lines\n if (content.length === 0) {\n return EMPTY_PARAGRAPH_MARKDOWN\n }\n\n return h.renderChildren(content)\n },\n\n addCommands() {\n return {\n setParagraph:\n () =>\n ({ commands }) => {\n return commands.setNode(this.name)\n },\n }\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-Alt-0': () => this.editor.commands.setParagraph(),\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsC;AA4BtC,IAAM,2BAA2B;AAMjC,IAAM,YAAY;AAMX,IAAM,YAAY,iBAAK,OAAyB;AAAA,EACrD,MAAM;AAAA,EAEN,UAAU;AAAA,EAEV,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,EAEP,SAAS;AAAA,EAET,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EACtB;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,WAAO,CAAC,SAAK,6BAAgB,KAAK,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,EAC9E;AAAA,EAEA,eAAe,CAAC,OAAO,YAAY;AACjC,UAAM,SAAS,MAAM,UAAU,CAAC;AAIhC,QAAI,OAAO,WAAW,KAAK,OAAO,CAAC,EAAE,SAAS,SAAS;AAErD,aAAO,QAAQ,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,IAC1C;AAGA,UAAM,UAAU,QAAQ,YAAY,MAAM;AAI1C,QACE,QAAQ,WAAW,KACnB,QAAQ,CAAC,EAAE,SAAS,WACnB,QAAQ,CAAC,EAAE,SAAS,4BAA4B,QAAQ,CAAC,EAAE,SAAS,YACrE;AACA,aAAO,QAAQ,WAAW,aAAa,QAAW,CAAC,CAAC;AAAA,IACtD;AAGA,WAAO,QAAQ,WAAW,aAAa,QAAW,OAAO;AAAA,EAC3D;AAAA,EAEA,gBAAgB,CAAC,MAAM,MAAM;AAC3B,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AAGA,UAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC;AAG9D,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,eAAe,OAAO;AAAA,EACjC;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,cACE,MACA,CAAC,EAAE,SAAS,MAAM;AAChB,eAAO,SAAS,QAAQ,KAAK,IAAI;AAAA,MACnC;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,uBAAuB;AACrB,WAAO;AAAA,MACL,aAAa,MAAM,KAAK,OAAO,SAAS,aAAa;AAAA,IACvD;AAAA,EACF;AACF,CAAC;;;ADrHD,IAAO,gBAAQ;","names":[]}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // src/paragraph.ts
2
2
  import { mergeAttributes, Node } from "@tiptap/core";
3
+ var EMPTY_PARAGRAPH_MARKDOWN = "&nbsp;";
4
+ var NBSP_CHAR = "\xA0";
3
5
  var Paragraph = Node.create({
4
6
  name: "paragraph",
5
7
  priority: 1e3,
@@ -21,18 +23,21 @@ var Paragraph = Node.create({
21
23
  if (tokens.length === 1 && tokens[0].type === "image") {
22
24
  return helpers.parseChildren([tokens[0]]);
23
25
  }
24
- return helpers.createNode(
25
- "paragraph",
26
- void 0,
27
- // no attributes for paragraph
28
- helpers.parseInline(tokens)
29
- );
26
+ const content = helpers.parseInline(tokens);
27
+ if (content.length === 1 && content[0].type === "text" && (content[0].text === EMPTY_PARAGRAPH_MARKDOWN || content[0].text === NBSP_CHAR)) {
28
+ return helpers.createNode("paragraph", void 0, []);
29
+ }
30
+ return helpers.createNode("paragraph", void 0, content);
30
31
  },
31
32
  renderMarkdown: (node, h) => {
32
- if (!node || !Array.isArray(node.content)) {
33
+ if (!node) {
33
34
  return "";
34
35
  }
35
- return h.renderChildren(node.content);
36
+ const content = Array.isArray(node.content) ? node.content : [];
37
+ if (content.length === 0) {
38
+ return EMPTY_PARAGRAPH_MARKDOWN;
39
+ }
40
+ return h.renderChildren(content);
36
41
  },
37
42
  addCommands() {
38
43
  return {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/paragraph.ts","../src/index.ts"],"sourcesContent":["import { mergeAttributes, Node } from '@tiptap/core'\n\nexport interface ParagraphOptions {\n /**\n * The HTML attributes for a paragraph node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n paragraph: {\n /**\n * Toggle a paragraph\n * @example editor.commands.toggleParagraph()\n */\n setParagraph: () => ReturnType\n }\n }\n}\n\n/**\n * This extension allows you to create paragraphs.\n * @see https://www.tiptap.dev/api/nodes/paragraph\n */\nexport const Paragraph = Node.create<ParagraphOptions>({\n name: 'paragraph',\n\n priority: 1000,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n group: 'block',\n\n content: 'inline*',\n\n parseHTML() {\n return [{ tag: 'p' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n parseMarkdown: (token, helpers) => {\n const tokens = token.tokens || []\n\n // Special case: if paragraph contains only a single image token,\n // unwrap it to avoid nesting block elements incorrectly\n if (tokens.length === 1 && tokens[0].type === 'image') {\n // Parse the image token directly as a block element\n return helpers.parseChildren([tokens[0]])\n }\n\n // Convert 'paragraph' token to paragraph node\n return helpers.createNode(\n 'paragraph',\n undefined, // no attributes for paragraph\n helpers.parseInline(tokens),\n )\n },\n\n renderMarkdown: (node, h) => {\n if (!node || !Array.isArray(node.content)) {\n return ''\n }\n\n return h.renderChildren(node.content)\n },\n\n addCommands() {\n return {\n setParagraph:\n () =>\n ({ commands }) => {\n return commands.setNode(this.name)\n },\n }\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-Alt-0': () => this.editor.commands.setParagraph(),\n }\n },\n})\n","import { Paragraph } from './paragraph.js'\n\nexport * from './paragraph.js'\n\nexport default Paragraph\n"],"mappings":";AAAA,SAAS,iBAAiB,YAAY;AA2B/B,IAAM,YAAY,KAAK,OAAyB;AAAA,EACrD,MAAM;AAAA,EAEN,UAAU;AAAA,EAEV,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,EAEP,SAAS;AAAA,EAET,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EACtB;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,WAAO,CAAC,KAAK,gBAAgB,KAAK,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,EAC9E;AAAA,EAEA,eAAe,CAAC,OAAO,YAAY;AACjC,UAAM,SAAS,MAAM,UAAU,CAAC;AAIhC,QAAI,OAAO,WAAW,KAAK,OAAO,CAAC,EAAE,SAAS,SAAS;AAErD,aAAO,QAAQ,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,IAC1C;AAGA,WAAO,QAAQ;AAAA,MACb;AAAA,MACA;AAAA;AAAA,MACA,QAAQ,YAAY,MAAM;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,gBAAgB,CAAC,MAAM,MAAM;AAC3B,QAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,KAAK,OAAO,GAAG;AACzC,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,eAAe,KAAK,OAAO;AAAA,EACtC;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,cACE,MACA,CAAC,EAAE,SAAS,MAAM;AAChB,eAAO,SAAS,QAAQ,KAAK,IAAI;AAAA,MACnC;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,uBAAuB;AACrB,WAAO;AAAA,MACL,aAAa,MAAM,KAAK,OAAO,SAAS,aAAa;AAAA,IACvD;AAAA,EACF;AACF,CAAC;;;ACvFD,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/paragraph.ts","../src/index.ts"],"sourcesContent":["import { mergeAttributes, Node } from '@tiptap/core'\n\nexport interface ParagraphOptions {\n /**\n * The HTML attributes for a paragraph node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n paragraph: {\n /**\n * Toggle a paragraph\n * @example editor.commands.toggleParagraph()\n */\n setParagraph: () => ReturnType\n }\n }\n}\n\n/**\n * Markdown marker for empty paragraphs to preserve blank lines.\n * Using &nbsp; (non-breaking space HTML entity) ensures the paragraph\n * is not collapsed by markdown parsers while remaining human-readable.\n */\nconst EMPTY_PARAGRAPH_MARKDOWN = '&nbsp;'\n\n/**\n * Unicode character for non-breaking space (U+00A0).\n * Some markdown parsers may convert &nbsp; entities to this literal character.\n */\nconst NBSP_CHAR = '\\u00A0'\n\n/**\n * This extension allows you to create paragraphs.\n * @see https://www.tiptap.dev/api/nodes/paragraph\n */\nexport const Paragraph = Node.create<ParagraphOptions>({\n name: 'paragraph',\n\n priority: 1000,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n group: 'block',\n\n content: 'inline*',\n\n parseHTML() {\n return [{ tag: 'p' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n parseMarkdown: (token, helpers) => {\n const tokens = token.tokens || []\n\n // Special case: if paragraph contains only a single image token,\n // unwrap it to avoid nesting block elements incorrectly\n if (tokens.length === 1 && tokens[0].type === 'image') {\n // Parse the image token directly as a block element\n return helpers.parseChildren([tokens[0]])\n }\n\n // Parse the inline tokens\n const content = helpers.parseInline(tokens)\n\n // Special case: if paragraph contains only &nbsp; (non-breaking space),\n // treat it as an empty paragraph to preserve blank lines\n if (\n content.length === 1 &&\n content[0].type === 'text' &&\n (content[0].text === EMPTY_PARAGRAPH_MARKDOWN || content[0].text === NBSP_CHAR)\n ) {\n return helpers.createNode('paragraph', undefined, [])\n }\n\n // Convert 'paragraph' token to paragraph node\n return helpers.createNode('paragraph', undefined, content)\n },\n\n renderMarkdown: (node, h) => {\n if (!node) {\n return ''\n }\n\n // Normalize content: treat undefined/null as empty array\n const content = Array.isArray(node.content) ? node.content : []\n\n // If the paragraph is empty, render a non-breaking space to preserve blank lines\n if (content.length === 0) {\n return EMPTY_PARAGRAPH_MARKDOWN\n }\n\n return h.renderChildren(content)\n },\n\n addCommands() {\n return {\n setParagraph:\n () =>\n ({ commands }) => {\n return commands.setNode(this.name)\n },\n }\n },\n\n addKeyboardShortcuts() {\n return {\n 'Mod-Alt-0': () => this.editor.commands.setParagraph(),\n }\n },\n})\n","import { Paragraph } from './paragraph.js'\n\nexport * from './paragraph.js'\n\nexport default Paragraph\n"],"mappings":";AAAA,SAAS,iBAAiB,YAAY;AA4BtC,IAAM,2BAA2B;AAMjC,IAAM,YAAY;AAMX,IAAM,YAAY,KAAK,OAAyB;AAAA,EACrD,MAAM;AAAA,EAEN,UAAU;AAAA,EAEV,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,EAEP,SAAS;AAAA,EAET,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EACtB;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,WAAO,CAAC,KAAK,gBAAgB,KAAK,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,EAC9E;AAAA,EAEA,eAAe,CAAC,OAAO,YAAY;AACjC,UAAM,SAAS,MAAM,UAAU,CAAC;AAIhC,QAAI,OAAO,WAAW,KAAK,OAAO,CAAC,EAAE,SAAS,SAAS;AAErD,aAAO,QAAQ,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,IAC1C;AAGA,UAAM,UAAU,QAAQ,YAAY,MAAM;AAI1C,QACE,QAAQ,WAAW,KACnB,QAAQ,CAAC,EAAE,SAAS,WACnB,QAAQ,CAAC,EAAE,SAAS,4BAA4B,QAAQ,CAAC,EAAE,SAAS,YACrE;AACA,aAAO,QAAQ,WAAW,aAAa,QAAW,CAAC,CAAC;AAAA,IACtD;AAGA,WAAO,QAAQ,WAAW,aAAa,QAAW,OAAO;AAAA,EAC3D;AAAA,EAEA,gBAAgB,CAAC,MAAM,MAAM;AAC3B,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AAGA,UAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC;AAG9D,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,eAAe,OAAO;AAAA,EACjC;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,cACE,MACA,CAAC,EAAE,SAAS,MAAM;AAChB,eAAO,SAAS,QAAQ,KAAK,IAAI;AAAA,MACnC;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,uBAAuB;AACrB,WAAO;AAAA,MACL,aAAa,MAAM,KAAK,OAAO,SAAS,aAAa;AAAA,IACvD;AAAA,EACF;AACF,CAAC;;;ACrHD,IAAO,gBAAQ;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/extension-paragraph",
3
3
  "description": "paragraph extension for tiptap",
4
- "version": "3.17.0",
4
+ "version": "3.18.0",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -31,10 +31,10 @@
31
31
  "dist"
32
32
  ],
33
33
  "devDependencies": {
34
- "@tiptap/core": "^3.17.0"
34
+ "@tiptap/core": "^3.18.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "@tiptap/core": "^3.17.0"
37
+ "@tiptap/core": "^3.18.0"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",
package/src/paragraph.ts CHANGED
@@ -21,6 +21,19 @@ declare module '@tiptap/core' {
21
21
  }
22
22
  }
23
23
 
24
+ /**
25
+ * Markdown marker for empty paragraphs to preserve blank lines.
26
+ * Using &nbsp; (non-breaking space HTML entity) ensures the paragraph
27
+ * is not collapsed by markdown parsers while remaining human-readable.
28
+ */
29
+ const EMPTY_PARAGRAPH_MARKDOWN = '&nbsp;'
30
+
31
+ /**
32
+ * Unicode character for non-breaking space (U+00A0).
33
+ * Some markdown parsers may convert &nbsp; entities to this literal character.
34
+ */
35
+ const NBSP_CHAR = '\u00A0'
36
+
24
37
  /**
25
38
  * This extension allows you to create paragraphs.
26
39
  * @see https://www.tiptap.dev/api/nodes/paragraph
@@ -58,20 +71,37 @@ export const Paragraph = Node.create<ParagraphOptions>({
58
71
  return helpers.parseChildren([tokens[0]])
59
72
  }
60
73
 
74
+ // Parse the inline tokens
75
+ const content = helpers.parseInline(tokens)
76
+
77
+ // Special case: if paragraph contains only &nbsp; (non-breaking space),
78
+ // treat it as an empty paragraph to preserve blank lines
79
+ if (
80
+ content.length === 1 &&
81
+ content[0].type === 'text' &&
82
+ (content[0].text === EMPTY_PARAGRAPH_MARKDOWN || content[0].text === NBSP_CHAR)
83
+ ) {
84
+ return helpers.createNode('paragraph', undefined, [])
85
+ }
86
+
61
87
  // Convert 'paragraph' token to paragraph node
62
- return helpers.createNode(
63
- 'paragraph',
64
- undefined, // no attributes for paragraph
65
- helpers.parseInline(tokens),
66
- )
88
+ return helpers.createNode('paragraph', undefined, content)
67
89
  },
68
90
 
69
91
  renderMarkdown: (node, h) => {
70
- if (!node || !Array.isArray(node.content)) {
92
+ if (!node) {
71
93
  return ''
72
94
  }
73
95
 
74
- return h.renderChildren(node.content)
96
+ // Normalize content: treat undefined/null as empty array
97
+ const content = Array.isArray(node.content) ? node.content : []
98
+
99
+ // If the paragraph is empty, render a non-breaking space to preserve blank lines
100
+ if (content.length === 0) {
101
+ return EMPTY_PARAGRAPH_MARKDOWN
102
+ }
103
+
104
+ return h.renderChildren(content)
75
105
  },
76
106
 
77
107
  addCommands() {