From 0512dfb7e3ba7126d6e59cd25da370bdc9fa78ff Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:40:35 +0800 Subject: [PATCH 1/3] feat: support squeezing soft breaks --- src/transformers/lib/index.ts | 2 +- src/transformers/lib/rehypeSqueezeBreaks.ts | 152 ++++++++++++++++++ .../lib/rehypeSqueezeParagraphs.ts | 75 --------- src/transformers/normalize-sanitize.test.ts | 105 +++++++++++- src/transformers/sanitize.test.ts | 113 ++++++++++++- src/transformers/sanitize.ts | 14 +- 6 files changed, 373 insertions(+), 88 deletions(-) create mode 100644 src/transformers/lib/rehypeSqueezeBreaks.ts delete mode 100644 src/transformers/lib/rehypeSqueezeParagraphs.ts diff --git a/src/transformers/lib/index.ts b/src/transformers/lib/index.ts index 594ab68..8fd2598 100644 --- a/src/transformers/lib/index.ts +++ b/src/transformers/lib/index.ts @@ -1 +1 @@ -export * from './rehypeSqueezeParagraphs' +export * from './rehypeSqueezeBreaks' diff --git a/src/transformers/lib/rehypeSqueezeBreaks.ts b/src/transformers/lib/rehypeSqueezeBreaks.ts new file mode 100644 index 0000000..daedaef --- /dev/null +++ b/src/transformers/lib/rehypeSqueezeBreaks.ts @@ -0,0 +1,152 @@ +import { type ElementContent, type Root, type RootContent } from 'hast' + +interface Props { + maxHardBreaks?: number + maxSoftBreaks?: number +} + +const isEmptyText = (node: RootContent) => + node.type === 'text' && node.value.replace(/\s/g, '') === '' + +const isBr = (node: RootContent) => + node.type === 'element' && node.tagName === 'br' + +const squeezeSoftBreaks = ({ + children, + maxSoftBreaks, +}: { children: ElementContent[] } & Pick) => { + const newChildren: ElementContent[] = [] + const isRetainAll = maxSoftBreaks === -1 + let breakCount = 0 + + children.forEach((node) => { + if (node.type !== 'element' || node.tagName !== 'br') { + breakCount = 0 + newChildren.push(node) + return + } + + // cap empty paragraphs or retain all by adding
+ breakCount++ + const shouldRetain = + isRetainAll || (maxSoftBreaks ? breakCount <= maxSoftBreaks : false) + if (shouldRetain) { + newChildren.push({ + type: 'element', + tagName: 'br', + properties: {}, + children: [], + }) + } + }) + + return newChildren +} + +const squeezeHardBreaks = ({ + children, + maxHardBreaks, + maxSoftBreaks, +}: { children: Array } & Props) => { + const newChildren: RootContent[] = [] + const isRetainAll = maxHardBreaks === -1 + let breakCount = 0 + + children.forEach((node) => { + // skip empty text nodes + if (isEmptyText(node)) { + newChildren.push(node) + return + } + + // paragraphs in blockquote + if (node.type === 'element' && node.tagName === 'blockquote') { + newChildren.push({ + type: 'element', + tagName: 'blockquote', + properties: node.properties, + children: squeezeHardBreaks({ + children: node.children, + maxHardBreaks, + maxSoftBreaks, + }) as ElementContent[], + }) + return + } + + // skip non-paragraph node + if (node.type !== 'element' || node.tagName !== 'p') { + breakCount = 0 + newChildren.push(node) + return + } + + // skip non-empty paragraph: + // -

+ // -

+ // -


+ // -


+ const isEmptyParagraph = + node.children.length === 0 || + node.children.every((n) => isBr(n) || isEmptyText(n)) + if (!isEmptyParagraph) { + breakCount = 0 + newChildren.push({ + type: 'element', + tagName: 'p', + properties: node.properties, + children: squeezeSoftBreaks({ + children: node.children, + maxSoftBreaks, + }), + }) + return + } + + // cap empty paragraphs or retain all by adding
+ breakCount++ + const shouldRetain = + isRetainAll || (maxHardBreaks ? breakCount <= maxHardBreaks : false) + if (shouldRetain) { + newChildren.push({ + type: 'element', + tagName: 'p', + properties: {}, + children: [ + { + type: 'element', + tagName: 'br', + properties: {}, + children: [], + }, + ], + }) + } + }) + + return newChildren +} + +/** + * Squeeze hard and soft breaks to a maximum of N + * + * e.g. + *

+ * => + *



+ * + */ +export const rehypeSqueezeBreaks = (props: Props) => (tree: Root) => { + if (tree.type !== 'root') { + return + } + + if ( + typeof props.maxHardBreaks !== 'number' && + typeof props.maxSoftBreaks !== 'number' + ) { + return + } + + tree.children = squeezeHardBreaks({ children: tree.children, ...props }) +} diff --git a/src/transformers/lib/rehypeSqueezeParagraphs.ts b/src/transformers/lib/rehypeSqueezeParagraphs.ts deleted file mode 100644 index f872efb..0000000 --- a/src/transformers/lib/rehypeSqueezeParagraphs.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { type Root, type RootContent } from 'hast' - -/** - * Squeeze empty paragraphs to a maximum of N - * - * e.g. - *

- * => - *



- * - * @param {number} maxCount: maximum number of empty paragraphs, -1 to retain all - * - */ -export const rehypeSqueezeParagraphs = - ({ maxCount }: { maxCount: number }) => - (tree: Root) => { - if (tree.type !== 'root') { - return - } - - const children: RootContent[] = [] - const isRetainAll = maxCount < 0 - let count = 0 - let touched = false - - tree.children.forEach((node) => { - // skip empty text nodes - if (node.type === 'text' && node.value.replace(/\s/g, '') === '') { - children.push(node) - return - } - - // skip non-paragraph nodes - if (node.type !== 'element' || node.tagName !== 'p') { - count = 0 - children.push(node) - return - } - - // skip non-empty paragraphs: - // -

- // -


- const isEmptyParagraph = - node.children.length === 0 || - node.children.every((n) => n.type === 'element' && n.tagName === 'br') - if (!isEmptyParagraph) { - count = 0 - children.push(node) - return - } - - // cap empty paragraphs or retain all by adding
- count++ - if (count <= maxCount || isRetainAll) { - children.push({ - type: 'element', - tagName: 'p', - properties: {}, - children: [ - { - type: 'element', - tagName: 'br', - properties: {}, - children: [], - }, - ], - }) - touched = true - } - }) - - if (touched || isRetainAll) { - tree.children = children - } - } diff --git a/src/transformers/normalize-sanitize.test.ts b/src/transformers/normalize-sanitize.test.ts index 11ef83d..2dddac2 100644 --- a/src/transformers/normalize-sanitize.test.ts +++ b/src/transformers/normalize-sanitize.test.ts @@ -41,6 +41,23 @@ const expectProcessCommentHTML = ( describe('Sanitize and normalize article', () => { test('squeeze empty paragraphs', () => { + expectProcessArticleHTML( + stripIndent` +

1

+

+

2

+

+

+

3

+ `, + stripIndent` +

1

+

2

+

3

+ `, + { maxHardBreaks: 0 }, + ) + expectProcessArticleHTML( stripIndent`

1

@@ -54,7 +71,27 @@ describe('Sanitize and normalize article', () => {


3

`, - { maxEmptyParagraphs: 1 }, + { maxHardBreaks: 1 }, + ) + + expectProcessArticleHTML( + stripIndent` +
+

1

+

2

+

+

3

+
+ `, + stripIndent` +
+

1

+

2

+


+

3

+
+ `, + { maxHardBreaks: 1 }, ) expectProcessArticleHTML( @@ -88,7 +125,69 @@ describe('Sanitize and normalize article', () => {



`, - { maxEmptyParagraphs: 2 }, + { maxHardBreaks: 2 }, + ) + }) + + test('squeeze
', () => { + expectProcessArticleHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

12

+

12

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: 0 }, + ) + + // max 1 soft break + expectProcessArticleHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

1
2

+

1
2

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: 1 }, + ) + + // blockquote + expectProcessArticleHTML( + stripIndent` +
+

1

+

2

+

1
2

+

1

2

+

1

+
+ `, + stripIndent` +
+

1

+

2

+

12

+

12

+

1

+
+ `, + { maxHardBreaks: 0, maxSoftBreaks: 0 }, ) }) @@ -128,7 +227,7 @@ describe('Sanitize and normalize article', () => {



`, - { maxEmptyParagraphs: -1 }, + { maxHardBreaks: -1 }, ) }) }) diff --git a/src/transformers/sanitize.test.ts b/src/transformers/sanitize.test.ts index e83f7a8..a854663 100644 --- a/src/transformers/sanitize.test.ts +++ b/src/transformers/sanitize.test.ts @@ -25,6 +25,50 @@ describe('Sanitization: custom', () => { }) test('squeeze empty paragraphs', () => { + // no empty paragraphs + expectSanitizeHTML( + stripIndent` +

1

+

+

2

+

+

3

+


+

4

+


+

5

+ `, + stripIndent` +

1

+

2

+

3

+

4

+

5

+ `, + { maxHardBreaks: 0 }, + ) + + // blockquote + expectSanitizeHTML( + stripIndent` +
+

1

+

2

+

+

3

+
+ `, + stripIndent` +
+

1

+

2

+

3

+
+ `, + { maxHardBreaks: 0 }, + ) + + // max 1 empty paragraph expectSanitizeHTML( stripIndent`

1

@@ -38,9 +82,10 @@ describe('Sanitization: custom', () => {


3

`, - { maxEmptyParagraphs: 1 }, + { maxHardBreaks: 1 }, ) + // max 2 empty paragraphs expectSanitizeHTML( stripIndent`

abc

@@ -71,7 +116,69 @@ describe('Sanitization: custom', () => {



`, - { maxEmptyParagraphs: 2 }, + { maxHardBreaks: 2 }, + ) + }) + + test('squeeze
', () => { + expectSanitizeHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

12

+

12

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: 0 }, + ) + + // max 1 soft break + expectSanitizeHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

1
2

+

1
2

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: 1 }, + ) + + // blockquote + expectSanitizeHTML( + stripIndent` +
+

1

+

2

+

1
2

+

1

2

+

1

+
+ `, + stripIndent` +
+

1

+

2

+

12

+

12

+

1

+
+ `, + { maxHardBreaks: 0, maxSoftBreaks: 0 }, ) }) @@ -110,7 +217,7 @@ describe('Sanitization: custom', () => {



`, - { maxEmptyParagraphs: -1 }, + { maxHardBreaks: -1 }, ) }) diff --git a/src/transformers/sanitize.ts b/src/transformers/sanitize.ts index debb5d3..5e04ac5 100644 --- a/src/transformers/sanitize.ts +++ b/src/transformers/sanitize.ts @@ -5,7 +5,7 @@ import rehypeSanitize from 'rehype-sanitize' import rehypeStringify from 'rehype-stringify' import { unified } from 'unified' -import { rehypeSqueezeParagraphs } from './lib' +import { rehypeSqueezeBreaks } from './lib' import { rehypeParseOptions, rehypeSanitizeOptions, @@ -13,21 +13,23 @@ import { } from './options' export interface SanitizeHTMLOptions { - maxEmptyParagraphs?: number + maxHardBreaks?: number + maxSoftBreaks?: number } export const sanitizeHTML = ( html: string, - { maxEmptyParagraphs }: SanitizeHTMLOptions = {}, + { maxHardBreaks, maxSoftBreaks }: SanitizeHTMLOptions = {}, ): string => { const formatter = unified() .use(rehypeParse, rehypeParseOptions) .use(rehypeRaw) .use(rehypeSanitize, rehypeSanitizeOptions) - if (maxEmptyParagraphs) { - formatter.use(rehypeSqueezeParagraphs, { - maxCount: maxEmptyParagraphs, + if (typeof maxHardBreaks === 'number' || typeof maxSoftBreaks === 'number') { + formatter.use(rehypeSqueezeBreaks, { + maxHardBreaks, + maxSoftBreaks, }) } From 7019eaf3b8afa8b98c649465aa61e9505b65eebe Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:30:06 +0800 Subject: [PATCH 2/3] test: reorg tests --- src/transformers/lib/rehypeSqueezeBreaks.ts | 2 +- src/transformers/normalize-sanitize.test.ts | 124 +++++++------- src/transformers/sanitize.test.ts | 171 +++++++++++--------- 3 files changed, 159 insertions(+), 138 deletions(-) diff --git a/src/transformers/lib/rehypeSqueezeBreaks.ts b/src/transformers/lib/rehypeSqueezeBreaks.ts index daedaef..2ba14e6 100644 --- a/src/transformers/lib/rehypeSqueezeBreaks.ts +++ b/src/transformers/lib/rehypeSqueezeBreaks.ts @@ -20,7 +20,7 @@ const squeezeSoftBreaks = ({ let breakCount = 0 children.forEach((node) => { - if (node.type !== 'element' || node.tagName !== 'br') { + if (!isBr(node)) { breakCount = 0 newChildren.push(node) return diff --git a/src/transformers/normalize-sanitize.test.ts b/src/transformers/normalize-sanitize.test.ts index 2dddac2..676f745 100644 --- a/src/transformers/normalize-sanitize.test.ts +++ b/src/transformers/normalize-sanitize.test.ts @@ -129,68 +129,6 @@ describe('Sanitize and normalize article', () => { ) }) - test('squeeze
', () => { - expectProcessArticleHTML( - stripIndent` -

1

-

2

-

1
2

-

1

2

-

1

- `, - stripIndent` -

1

-

2

-

12

-

12

-

1

- `, - { maxHardBreaks: 0, maxSoftBreaks: 0 }, - ) - - // max 1 soft break - expectProcessArticleHTML( - stripIndent` -

1

-

2

-

1
2

-

1

2

-

1

- `, - stripIndent` -

1

-

2

-

1
2

-

1
2

-

1

- `, - { maxHardBreaks: 0, maxSoftBreaks: 1 }, - ) - - // blockquote - expectProcessArticleHTML( - stripIndent` -
-

1

-

2

-

1
2

-

1

2

-

1

-
- `, - stripIndent` -
-

1

-

2

-

12

-

12

-

1

-
- `, - { maxHardBreaks: 0, maxSoftBreaks: 0 }, - ) - }) - test('squeeze and retain all empty paragraphs', () => { expectProcessArticleHTML( stripIndent` @@ -271,4 +209,66 @@ describe('Sanitize and normalize comment', () => { `, ) }) + + test('squeeze
', () => { + expectProcessCommentHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

12

+

12

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: 0 }, + ) + + // max 1 soft break + expectProcessCommentHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

1
2

+

1
2

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: 1 }, + ) + + // blockquote + expectProcessCommentHTML( + stripIndent` +
+

1

+

2

+

1
2

+

1

2

+

1

+
+ `, + stripIndent` +
+

1

+

2

+

12

+

12

+

1

+
+ `, + { maxHardBreaks: 0, maxSoftBreaks: 0 }, + ) + }) }) diff --git a/src/transformers/sanitize.test.ts b/src/transformers/sanitize.test.ts index a854663..4d94f59 100644 --- a/src/transformers/sanitize.test.ts +++ b/src/transformers/sanitize.test.ts @@ -48,26 +48,6 @@ describe('Sanitization: custom', () => { { maxHardBreaks: 0 }, ) - // blockquote - expectSanitizeHTML( - stripIndent` -
-

1

-

2

-

-

3

-
- `, - stripIndent` -
-

1

-

2

-

3

-
- `, - { maxHardBreaks: 0 }, - ) - // max 1 empty paragraph expectSanitizeHTML( stripIndent` @@ -120,65 +100,25 @@ describe('Sanitization: custom', () => { ) }) - test('squeeze
', () => { - expectSanitizeHTML( - stripIndent` -

1

-

2

-

1
2

-

1

2

-

1

- `, - stripIndent` -

1

-

2

-

12

-

12

-

1

- `, - { maxHardBreaks: 0, maxSoftBreaks: 0 }, - ) - - // max 1 soft break - expectSanitizeHTML( - stripIndent` -

1

-

2

-

1
2

-

1

2

-

1

- `, - stripIndent` -

1

-

2

-

1
2

-

1
2

-

1

- `, - { maxHardBreaks: 0, maxSoftBreaks: 1 }, - ) - + test('squeeze empty paragraphs in blockquote', () => { // blockquote expectSanitizeHTML( stripIndent` -
-

1

-

2

-

1
2

-

1

2

-

1

-
- `, +
+

1

+

2

+

+

3

+
+ `, stripIndent` -
-

1

-

2

-

12

-

12

-

1

-
- `, - { maxHardBreaks: 0, maxSoftBreaks: 0 }, +
+

1

+

2

+

3

+
+ `, + { maxHardBreaks: 0 }, ) }) @@ -258,6 +198,87 @@ describe('Sanitization: custom', () => { `, ) }) + + test('squeeze
', () => { + expectSanitizeHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

12

+

12

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: 0 }, + ) + + // max 1 soft break + expectSanitizeHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

1
2

+

1
2

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: 1 }, + ) + + // retain all + expectSanitizeHTML( + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + stripIndent` +

1

+

2

+

1
2

+

1

2

+

1

+ `, + { maxHardBreaks: 0, maxSoftBreaks: -1 }, + ) + + // blockquote + expectSanitizeHTML( + stripIndent` +
+

1

+

2

+

1
2

+

1

2

+

1

+
+ `, + stripIndent` +
+

1

+

2

+

12

+

12

+

1

+
+ `, + { maxHardBreaks: 0, maxSoftBreaks: 0 }, + ) + }) }) // via https://github.com/leizongmin/js-xss/blob/master/test/test_xss.js From 77f0d46be1c5497d2a7dfeed3037f7e81ac1f3aa Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:10:06 +0800 Subject: [PATCH 3/3] feat: clean code --- src/transformers/lib/rehypeSqueezeBreaks.ts | 164 +++++++++++--------- src/transformers/sanitize.ts | 19 +-- 2 files changed, 95 insertions(+), 88 deletions(-) diff --git a/src/transformers/lib/rehypeSqueezeBreaks.ts b/src/transformers/lib/rehypeSqueezeBreaks.ts index 2ba14e6..031e6e4 100644 --- a/src/transformers/lib/rehypeSqueezeBreaks.ts +++ b/src/transformers/lib/rehypeSqueezeBreaks.ts @@ -1,6 +1,6 @@ import { type ElementContent, type Root, type RootContent } from 'hast' -interface Props { +export interface RehypeSqueezeBreaksOptions { maxHardBreaks?: number maxSoftBreaks?: number } @@ -11,10 +11,21 @@ const isEmptyText = (node: RootContent) => const isBr = (node: RootContent) => node.type === 'element' && node.tagName === 'br' +const isEmptyParagraph = (nodes: ElementContent[]) => { + // -

+ // -

+ // -


+ // -


+ return nodes.length === 0 || nodes.every((n) => isBr(n) || isEmptyText(n)) +} + const squeezeSoftBreaks = ({ children, maxSoftBreaks, -}: { children: ElementContent[] } & Pick) => { +}: { children: ElementContent[] } & Pick< + RehypeSqueezeBreaksOptions, + 'maxSoftBreaks' +>) => { const newChildren: ElementContent[] = [] const isRetainAll = maxSoftBreaks === -1 let breakCount = 0 @@ -28,9 +39,7 @@ const squeezeSoftBreaks = ({ // cap empty paragraphs or retain all by adding
breakCount++ - const shouldRetain = - isRetainAll || (maxSoftBreaks ? breakCount <= maxSoftBreaks : false) - if (shouldRetain) { + if (isRetainAll || (maxSoftBreaks && breakCount <= maxSoftBreaks)) { newChildren.push({ type: 'element', tagName: 'br', @@ -47,11 +56,17 @@ const squeezeHardBreaks = ({ children, maxHardBreaks, maxSoftBreaks, -}: { children: Array } & Props) => { +}: { + children: Array +} & RehypeSqueezeBreaksOptions) => { const newChildren: RootContent[] = [] const isRetainAll = maxHardBreaks === -1 let breakCount = 0 + if (maxHardBreaks === undefined) { + return children + } + children.forEach((node) => { // skip empty text nodes if (isEmptyText(node)) { @@ -59,68 +74,68 @@ const squeezeHardBreaks = ({ return } - // paragraphs in blockquote - if (node.type === 'element' && node.tagName === 'blockquote') { - newChildren.push({ - type: 'element', - tagName: 'blockquote', - properties: node.properties, - children: squeezeHardBreaks({ - children: node.children, - maxHardBreaks, - maxSoftBreaks, - }) as ElementContent[], - }) - return - } - - // skip non-paragraph node - if (node.type !== 'element' || node.tagName !== 'p') { + // skip non-element nodes + if (node.type !== 'element') { breakCount = 0 newChildren.push(node) return } - // skip non-empty paragraph: - // -

- // -

- // -


- // -


- const isEmptyParagraph = - node.children.length === 0 || - node.children.every((n) => isBr(n) || isEmptyText(n)) - if (!isEmptyParagraph) { - breakCount = 0 - newChildren.push({ - type: 'element', - tagName: 'p', - properties: node.properties, - children: squeezeSoftBreaks({ - children: node.children, - maxSoftBreaks, - }), - }) - return - } - - // cap empty paragraphs or retain all by adding
- breakCount++ - const shouldRetain = - isRetainAll || (maxHardBreaks ? breakCount <= maxHardBreaks : false) - if (shouldRetain) { - newChildren.push({ - type: 'element', - tagName: 'p', - properties: {}, - children: [ - { + switch (node.tagName) { + case 'blockquote': + newChildren.push({ + type: 'element', + tagName: 'blockquote', + properties: node.properties, + children: squeezeHardBreaks({ + children: node.children, + maxHardBreaks, + maxSoftBreaks, + }) as ElementContent[], + }) + break + case 'p': + // skip non-empty paragraph: + if (!isEmptyParagraph(node.children)) { + breakCount = 0 + newChildren.push({ type: 'element', - tagName: 'br', - properties: {}, - children: [], - }, - ], - }) + tagName: 'p', + properties: node.properties, + children: squeezeSoftBreaks({ + children: node.children, + maxSoftBreaks, + }), + }) + break + } + + // cap empty paragraphs or retain all by adding
+ breakCount++ + + if (!isRetainAll && !(maxHardBreaks && breakCount <= maxHardBreaks)) { + break + } + + newChildren.push({ + type: 'element', + tagName: 'p', + properties: {}, + children: [ + { + type: 'element', + tagName: 'br', + properties: {}, + children: [], + }, + ], + }) + break + + // skip non-paragraph node + default: + breakCount = 0 + newChildren.push(node) } }) @@ -136,17 +151,18 @@ const squeezeHardBreaks = ({ *



* */ -export const rehypeSqueezeBreaks = (props: Props) => (tree: Root) => { - if (tree.type !== 'root') { - return - } +export const rehypeSqueezeBreaks = + (props: RehypeSqueezeBreaksOptions) => (tree: Root) => { + if (tree.type !== 'root') { + return + } - if ( - typeof props.maxHardBreaks !== 'number' && - typeof props.maxSoftBreaks !== 'number' - ) { - return - } + if ( + typeof props.maxHardBreaks !== 'number' && + typeof props.maxSoftBreaks !== 'number' + ) { + return + } - tree.children = squeezeHardBreaks({ children: tree.children, ...props }) -} + tree.children = squeezeHardBreaks({ children: tree.children, ...props }) + } diff --git a/src/transformers/sanitize.ts b/src/transformers/sanitize.ts index 5e04ac5..e9d7c43 100644 --- a/src/transformers/sanitize.ts +++ b/src/transformers/sanitize.ts @@ -5,17 +5,14 @@ import rehypeSanitize from 'rehype-sanitize' import rehypeStringify from 'rehype-stringify' import { unified } from 'unified' -import { rehypeSqueezeBreaks } from './lib' +import { rehypeSqueezeBreaks, type RehypeSqueezeBreaksOptions } from './lib' import { rehypeParseOptions, rehypeSanitizeOptions, rehypeStringifyOptions, } from './options' -export interface SanitizeHTMLOptions { - maxHardBreaks?: number - maxSoftBreaks?: number -} +export type SanitizeHTMLOptions = RehypeSqueezeBreaksOptions export const sanitizeHTML = ( html: string, @@ -25,15 +22,9 @@ export const sanitizeHTML = ( .use(rehypeParse, rehypeParseOptions) .use(rehypeRaw) .use(rehypeSanitize, rehypeSanitizeOptions) - - if (typeof maxHardBreaks === 'number' || typeof maxSoftBreaks === 'number') { - formatter.use(rehypeSqueezeBreaks, { - maxHardBreaks, - maxSoftBreaks, - }) - } - - formatter.use(rehypeFormat).use(rehypeStringify, rehypeStringifyOptions) + .use(rehypeSqueezeBreaks, { maxHardBreaks, maxSoftBreaks }) + .use(rehypeFormat) + .use(rehypeStringify, rehypeStringifyOptions) const result = formatter.processSync(html) return String(result)