diff --git a/package.json b/package.json index 8caca62..85ea180 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@matters/matters-editor", - "version": "0.2.5-alpha.1", + "version": "0.2.5-alpha.2", "description": "Editor for matters.news", "author": "https://github.com/thematters", "homepage": "https://github.com/thematters/matters-editor", diff --git a/src/editors/extensions/index.ts b/src/editors/extensions/index.ts index 8c50cc1..17a3247 100644 --- a/src/editors/extensions/index.ts +++ b/src/editors/extensions/index.ts @@ -1,4 +1,3 @@ -import Blockquote from '@tiptap/extension-blockquote' import BulletList from '@tiptap/extension-bullet-list' import Code from '@tiptap/extension-code' import CodeBlock from '@tiptap/extension-code-block' @@ -21,6 +20,8 @@ import { FigureImage } from './figureImage' import { HorizontalRule } from './horizontalRule' import { Link } from './link' import { Mention, type MentionSuggestion } from './mention' +import { PlainBlockquote } from './plainBlockquote' +import { PlainParagraph } from './plainParagraph' export * from './bold' export * from './figureAudio' @@ -39,7 +40,6 @@ const baseExtensions = (placeholder?: string) => [ // Basic Formats Text, Paragraph, - Blockquote, HardBreak.configure({ HTMLAttributes: { class: 'smart', @@ -47,6 +47,8 @@ const baseExtensions = (placeholder?: string) => [ }), // Custom Formats Link, + PlainParagraph, + PlainBlockquote, ] const baseArticleExtensions = (placeholder?: string) => [ diff --git a/src/editors/extensions/plainBlockquote.ts b/src/editors/extensions/plainBlockquote.ts new file mode 100644 index 0000000..d7d58c8 --- /dev/null +++ b/src/editors/extensions/plainBlockquote.ts @@ -0,0 +1,83 @@ +import { mergeAttributes, Node, wrappingInputRule } from '@tiptap/core' + +export interface BlockquoteOptions { + /** + * HTML attributes to add to the blockquote element + * @default {} + * @example { class: 'foo' } + */ + HTMLAttributes: Record +} + +declare module '@tiptap/core' { + interface Commands { + plainBlockQuote: { + setPlainBlockquote: () => ReturnType + } + } +} + +/** + * Matches a blockquote to a `>` as input. + */ +export const inputRegex = /^\s*>\s$/ + +/** + * This extension allows you to create plain blockquotes, + * contains only plainParagraph. + * + * Forked from: + * @see https://tiptap.dev/api/nodes/blockquote + */ +export const PlainBlockquote = Node.create({ + name: 'plainBlockquote', + + addOptions() { + return { + HTMLAttributes: {}, + } + }, + + group: 'block', + + content: 'plainBlock+', + + defining: true, + + parseHTML() { + return [{ tag: 'blockquote' }] + }, + + renderHTML({ HTMLAttributes }) { + return [ + 'blockquote', + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + 0, + ] + }, + + addCommands() { + return { + setPlainBlockquote: + () => + ({ chain }) => { + return chain().setPlainParagraph().wrapIn(this.name).run() + }, + } + }, + + addKeyboardShortcuts() { + return { + 'Mod-Shift-b': () => this.editor.commands.setPlainBlockquote(), + } + }, + + addInputRules() { + return [ + wrappingInputRule({ + find: inputRegex, + type: this.type, + }), + ] + }, +}) diff --git a/src/editors/extensions/plainParagraph.ts b/src/editors/extensions/plainParagraph.ts new file mode 100644 index 0000000..ac86b7d --- /dev/null +++ b/src/editors/extensions/plainParagraph.ts @@ -0,0 +1,69 @@ +import { mergeAttributes, Node } from '@tiptap/core' + +export interface PlainParagraphOptions { + /** + * The HTML attributes for a plain paragraph node. + * @default {} + * @example { class: 'foo' } + */ + HTMLAttributes: Record +} + +declare module '@tiptap/core' { + interface Commands { + plainParagraph: { + setPlainParagraph: () => ReturnType + } + } +} + +/** + * This extension allows you to create + * plain paragraphs (only support plain text and hard break) + * for plainBlockquote extension. + * + * Forked from: + * @see https://www.tiptap.dev/api/nodes/paragraph + */ +export const PlainParagraph = Node.create({ + name: 'plainParagraph', + + // higher priority to override the default paragraph node + // https://github.com/ueberdosis/tiptap/blob/f635d7b4f511530496377a8ef051875e30e301a4/packages/extension-paragraph/src/paragraph.ts#L31 + priority: 2000, + + addOptions() { + return { + HTMLAttributes: { class: 'plain' }, + } + }, + + group: 'plainBlock', + + content: 'inline*', + + marks: '', + + defining: true, + + parseHTML() { + return [{ tag: 'p[class="plain"]' }] + }, + + renderHTML({ HTMLAttributes }) { + return [ + 'p', + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + 0, + ] + }, + + addCommands() { + return { + setPlainParagraph: + () => + ({ commands }) => + commands.setNode(this.name), + } + }, +}) diff --git a/src/transformers/normalize-sanitize.test.ts b/src/transformers/normalize-sanitize.test.ts index 676f745..3965c24 100644 --- a/src/transformers/normalize-sanitize.test.ts +++ b/src/transformers/normalize-sanitize.test.ts @@ -77,18 +77,18 @@ describe('Sanitize and normalize article', () => { expectProcessArticleHTML( stripIndent`
-

1

-

2

-

-

3

+

1

+

2

+

+

3

`, stripIndent`
-

1

-

2

-


-

3

+

1

+

2

+


+

3

`, { maxHardBreaks: 1 }, @@ -252,20 +252,20 @@ describe('Sanitize and normalize comment', () => { expectProcessCommentHTML( stripIndent`
-

1

-

2

-

1
2

-

1

2

-

1

+

1

+

2

+

1
2

+

1

2

+

1

`, stripIndent`
-

1

-

2

-

12

-

12

-

1

+

1

+

2

+

12

+

12

+

1

`, { maxHardBreaks: 0, maxSoftBreaks: 0 }, diff --git a/src/transformers/normalize.test.ts b/src/transformers/normalize.test.ts index 7e5a9f6..45e3f98 100644 --- a/src/transformers/normalize.test.ts +++ b/src/transformers/normalize.test.ts @@ -1,3 +1,4 @@ +import { stripIndent } from 'common-tags' import { describe, expect, test } from 'vitest' import { normalizeArticleHTML, normalizeCommentHTML } from './normalize' @@ -288,8 +289,27 @@ describe('Normalization: Article', () => { describe('Normalization: Comment', () => { test('quote', () => { expectNormalizeCommentHTML( - '

abc

', - '

abc

', + stripIndent` +
+

hello,
world

+

how are you today

+

strong

+

normal paragraph

+

heading

hello,world +
+ `, + '

hello,
world

how are you today

strong

normal paragraph

heading

hello,world

', + ) + + expectNormalizeCommentHTML( + stripIndent` +
+

1

+

2

+

3

+
+ `, + '

1 2 3

', ) }) diff --git a/src/transformers/options.ts b/src/transformers/options.ts index 314df95..aef7c70 100644 --- a/src/transformers/options.ts +++ b/src/transformers/options.ts @@ -104,8 +104,12 @@ export const rehypeSanitizeOptions: Schema = { }, attributes: { ...defaultSchema.attributes, + p: [ + // for plainParagraph extension + ['className', 'plain'], + ], a: [ - // classes + // for mention extension ['className', 'mention'], 'href', 'ref', diff --git a/src/transformers/sanitize.test.ts b/src/transformers/sanitize.test.ts index 4d94f59..1ba9fd3 100644 --- a/src/transformers/sanitize.test.ts +++ b/src/transformers/sanitize.test.ts @@ -104,20 +104,20 @@ describe('Sanitization: custom', () => { // blockquote expectSanitizeHTML( stripIndent` -
-

1

-

2

-

-

3

-
- `, +
+

1

+

2

+

+

3

+
+ `, stripIndent` -
-

1

-

2

-

3

-
- `, +
+

1

+

2

+

3

+
+ `, { maxHardBreaks: 0 }, ) })