Skip to content

Commit

Permalink
Merge pull request #477 from thematters/feat/journal
Browse files Browse the repository at this point in the history
feat: support squeezing soft breaks
  • Loading branch information
robertu7 authored Jun 6, 2024
2 parents 19fb457 + 77f0d46 commit aa64312
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 95 deletions.
2 changes: 1 addition & 1 deletion src/transformers/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './rehypeSqueezeParagraphs'
export * from './rehypeSqueezeBreaks'
168 changes: 168 additions & 0 deletions src/transformers/lib/rehypeSqueezeBreaks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { type ElementContent, type Root, type RootContent } from 'hast'

export interface RehypeSqueezeBreaksOptions {
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 isEmptyParagraph = (nodes: ElementContent[]) => {
// - <p></p>
// - <p> </p>
// - <p><br></p>
// - <p> <br></p>
return nodes.length === 0 || nodes.every((n) => isBr(n) || isEmptyText(n))
}

const squeezeSoftBreaks = ({
children,
maxSoftBreaks,
}: { children: ElementContent[] } & Pick<
RehypeSqueezeBreaksOptions,
'maxSoftBreaks'
>) => {
const newChildren: ElementContent[] = []
const isRetainAll = maxSoftBreaks === -1
let breakCount = 0

children.forEach((node) => {
if (!isBr(node)) {
breakCount = 0
newChildren.push(node)
return
}

// cap empty paragraphs or retain all by adding <br>
breakCount++
if (isRetainAll || (maxSoftBreaks && breakCount <= maxSoftBreaks)) {
newChildren.push({
type: 'element',
tagName: 'br',
properties: {},
children: [],
})
}
})

return newChildren
}

const squeezeHardBreaks = ({
children,
maxHardBreaks,
maxSoftBreaks,
}: {
children: Array<RootContent | ElementContent>
} & 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)) {
newChildren.push(node)
return
}

// skip non-element nodes
if (node.type !== 'element') {
breakCount = 0
newChildren.push(node)
return
}

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: 'p',
properties: node.properties,
children: squeezeSoftBreaks({
children: node.children,
maxSoftBreaks,
}),
})
break
}

// cap empty paragraphs or retain all by adding <br>
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)
}
})

return newChildren
}

/**
* Squeeze hard and soft breaks to a maximum of N
*
* e.g.
* <p></p><p></p><p></p><p></p><p></p><p></p>
* =>
* <p><br></p><p><br></p>
*
*/
export const rehypeSqueezeBreaks =
(props: RehypeSqueezeBreaksOptions) => (tree: Root) => {
if (tree.type !== 'root') {
return
}

if (
typeof props.maxHardBreaks !== 'number' &&
typeof props.maxSoftBreaks !== 'number'
) {
return
}

tree.children = squeezeHardBreaks({ children: tree.children, ...props })
}
75 changes: 0 additions & 75 deletions src/transformers/lib/rehypeSqueezeParagraphs.ts

This file was deleted.

105 changes: 102 additions & 3 deletions src/transformers/normalize-sanitize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ const expectProcessCommentHTML = (

describe('Sanitize and normalize article', () => {
test('squeeze empty paragraphs', () => {
expectProcessArticleHTML(
stripIndent`
<p>1</p>
<p></p>
<p>2</p>
<p></p>
<p></p>
<p>3</p>
`,
stripIndent`
<p>1</p>
<p>2</p>
<p>3</p>
`,
{ maxHardBreaks: 0 },
)

expectProcessArticleHTML(
stripIndent`
<p>1</p>
Expand All @@ -54,7 +71,27 @@ describe('Sanitize and normalize article', () => {
<p><br class="smart"></p>
<p>3</p>
`,
{ maxEmptyParagraphs: 1 },
{ maxHardBreaks: 1 },
)

expectProcessArticleHTML(
stripIndent`
<blockquote>
<p>1</p>
<p>2</p>
<p></p>
<p>3</p>
</blockquote>
`,
stripIndent`
<blockquote>
<p>1</p>
<p>2</p>
<p><br class="smart"></p>
<p>3</p>
</blockquote>
`,
{ maxHardBreaks: 1 },
)

expectProcessArticleHTML(
Expand Down Expand Up @@ -88,7 +125,7 @@ describe('Sanitize and normalize article', () => {
<p><br class="smart"></p>
<p><br class="smart"></p>
`,
{ maxEmptyParagraphs: 2 },
{ maxHardBreaks: 2 },
)
})

Expand Down Expand Up @@ -128,7 +165,7 @@ describe('Sanitize and normalize article', () => {
<p><br class="smart"></p>
<p><br class="smart"></p>
`,
{ maxEmptyParagraphs: -1 },
{ maxHardBreaks: -1 },
)
})
})
Expand Down Expand Up @@ -172,4 +209,66 @@ describe('Sanitize and normalize comment', () => {
`,
)
})

test('squeeze <br>', () => {
expectProcessCommentHTML(
stripIndent`
<p>1</p>
<p>2</p>
<p>1<br>2</p>
<p>1<br><br>2</p>
<p>1<br><br></p>
`,
stripIndent`
<p>1</p>
<p>2</p>
<p>12</p>
<p>12</p>
<p>1</p>
`,
{ maxHardBreaks: 0, maxSoftBreaks: 0 },
)

// max 1 soft break
expectProcessCommentHTML(
stripIndent`
<p>1</p>
<p>2</p>
<p>1<br>2</p>
<p>1<br><br>2</p>
<p>1<br><br></p>
`,
stripIndent`
<p>1</p>
<p>2</p>
<p>1<br class="smart">2</p>
<p>1<br class="smart">2</p>
<p>1<br class="smart"></p>
`,
{ maxHardBreaks: 0, maxSoftBreaks: 1 },
)

// blockquote
expectProcessCommentHTML(
stripIndent`
<blockquote>
<p>1</p>
<p>2</p>
<p>1<br>2</p>
<p>1<br><br>2</p>
<p>1<br><br></p>
</blockquote>
`,
stripIndent`
<blockquote>
<p>1</p>
<p>2</p>
<p>12</p>
<p>12</p>
<p>1</p>
</blockquote>
`,
{ maxHardBreaks: 0, maxSoftBreaks: 0 },
)
})
})
Loading

0 comments on commit aa64312

Please sign in to comment.