Skip to content

Commit

Permalink
Add support for interpreting <code> as a preformatted inline element …
Browse files Browse the repository at this point in the history
…and rendering Markdown accordingly. Fix mixmark-io#318.
  • Loading branch information
martincizek committed Jul 6, 2020
1 parent c787982 commit 85a6fd7
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 18 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var turndownService = new TurndownService({ option: 'value' })
| `strongDelimiter` | `**` or `__` | `**` |
| `linkStyle` | `inlined` or `referenced` | `inlined` |
| `linkReferenceStyle` | `full`, `collapsed`, or `shortcut` | `full` |
| `preformattedCode` | `false` or [`true`](https://github.com/lucthev/collapse-whitespace/issues/16) | `false` |

### Advanced Options

Expand Down
15 changes: 9 additions & 6 deletions src/collapse-whitespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function collapseWhitespace (options) {
if (!element.firstChild || isPre(element)) return

var prevText = null
var prevVoid = false
var keepLeadingWs = false

var prev = null
var node = next(prev, element, isPre)
Expand All @@ -51,7 +51,7 @@ function collapseWhitespace (options) {
var text = node.data.replace(/[ \r\n\t]+/g, ' ')

if ((!prevText || / $/.test(prevText.data)) &&
!prevVoid && text[0] === ' ') {
!keepLeadingWs && text[0] === ' ') {
text = text.substr(1)
}

Expand All @@ -71,11 +71,14 @@ function collapseWhitespace (options) {
}

prevText = null
prevVoid = false
} else if (isVoid(node)) {
// Avoid trimming space around non-block, non-BR void elements.
keepLeadingWs = false
} else if (isVoid(node) || isPre(node)) {
// Avoid trimming space around non-block, non-BR void elements and inline PRE.
prevText = null
prevVoid = true
keepLeadingWs = true
} else if (prevText) {
// Drop protection if set previously.
keepLeadingWs = false
}
} else {
node = remove(node)
Expand Down
20 changes: 12 additions & 8 deletions src/node.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { isBlock, isVoid, hasVoid, isMeaningfulWhenBlank, hasMeaningfulWhenBlank } from './utilities'

export default function Node (node) {
export default function Node (node, options) {
node.isBlock = isBlock(node)
node.isCode = node.nodeName.toLowerCase() === 'code' || node.parentNode.isCode
node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode
node.isBlank = isBlank(node)
node.flankingWhitespace = flankingWhitespace(node)
node.flankingWhitespace = flankingWhitespace(node, options)
return node
}

Expand All @@ -18,18 +18,20 @@ function isBlank (node) {
)
}

function flankingWhitespace (node) {
if (node.isBlock) return { leading: '', trailing: '' }
function flankingWhitespace (node, options) {
if (node.isBlock || (options.preformattedCode && node.isCode)) {
return { leading: '', trailing: '' }
}

var edges = edgeWhitespace(node.textContent)

// abandon leading ASCII WS if left-flanked by ASCII WS
if (edges.leadingAscii && isFlankedByWhitespace('left', node)) {
if (edges.leadingAscii && isFlankedByWhitespace('left', node, options)) {
edges.leading = edges.leadingNonAscii
}

// abandon trailing ASCII WS if right-flanked by ASCII WS
if (edges.trailingAscii && isFlankedByWhitespace('right', node)) {
if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) {
edges.trailing = edges.trailingNonAscii
}

Expand All @@ -48,7 +50,7 @@ function edgeWhitespace (string) {
}
}

function isFlankedByWhitespace (side, node) {
function isFlankedByWhitespace (side, node, options) {
var sibling
var regExp
var isFlanked
Expand All @@ -64,6 +66,8 @@ function isFlankedByWhitespace (side, node) {
if (sibling) {
if (sibling.nodeType === 3) {
isFlanked = regExp.test(sibling.nodeValue)
} else if (options.preformattedCode && sibling.nodeName === 'CODE') {
isFlanked = false
} else if (sibling.nodeType === 1 && !isBlock(sibling)) {
isFlanked = regExp.test(sibling.textContent)
}
Expand Down
9 changes: 7 additions & 2 deletions src/root-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import collapseWhitespace from './collapse-whitespace'
import HTMLParser from './html-parser'
import { isBlock, isVoid } from './utilities'

export default function RootNode (input) {
export default function RootNode (input, options) {
var root
if (typeof input === 'string') {
var doc = htmlParser().parseFromString(
Expand All @@ -19,7 +19,8 @@ export default function RootNode (input) {
collapseWhitespace({
element: root,
isBlock: isBlock,
isVoid: isVoid
isVoid: isVoid,
isPre: options.preformattedCode ? isPreOrCode : null
})

return root
Expand All @@ -30,3 +31,7 @@ function htmlParser () {
_htmlParser = _htmlParser || new HTMLParser()
return _htmlParser
}

function isPreOrCode (node) {
return node.nodeName === 'PRE' || node.nodeName === 'CODE'
}
5 changes: 3 additions & 2 deletions src/turndown.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default function TurndownService (options) {
linkStyle: 'inlined',
linkReferenceStyle: 'full',
br: ' ',
preformattedCode: false,
blankReplacement: function (content, node) {
return node.isBlock ? '\n\n' : ''
},
Expand Down Expand Up @@ -69,7 +70,7 @@ TurndownService.prototype = {

if (input === '') return ''

var output = process.call(this, new RootNode(input))
var output = process.call(this, new RootNode(input, this.options))
return postProcess.call(this, output)
},

Expand Down Expand Up @@ -158,7 +159,7 @@ TurndownService.prototype = {
function process (parentNode) {
var self = this
return reduce.call(parentNode.childNodes, function (output, node) {
node = new Node(node)
node = new Node(node, self.options)

var replacement = ''
if (node.nodeType === 3) {
Expand Down
34 changes: 34 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,40 @@ <h2>This is a header.</h2>
<pre class="expected">foo &nbsp;_bar_</pre>
</div>

<!-- Behavior of `<code>` with CSS set as `white-space: pre-wrap;`, e.g. in GitLab -->
<div class="case" data-name="preformatted code with leading whitespace" data-options='{"preformattedCode": true}'>
<div class="input">Four spaces <code> make an indented code block in Markdown</code></div>
<pre class="expected">Four spaces ` make an indented code block in Markdown`</pre>
</div>

<div class="case" data-name="preformatted code with trailing whitespace" data-options='{"preformattedCode": true}'>
<div class="input"><code>A line break </code> <b> note the spaces</b></div>
<pre class="expected">`A line break ` **note the spaces**</pre>
</div>

<div class="case" data-name="preformatted code tightly surrounded" data-options='{"preformattedCode": true}'>
<div class="input"><b>tight</b><code>code</code><b>wrap</b></div>
<pre class="expected">**tight**`code`**wrap**</pre>
</div>

<div class="case" data-name="preformatted code loosely surrounded" data-options='{"preformattedCode": true}'>
<div class="input"><b>not so tight </b><code>code</code><b> wrap</b></div>
<pre class="expected">**not so tight** `code` **wrap**</pre>
</div>

<!-- newlines become spaces + extra space must be added -->
<div class="case" data-name="preformatted code with newlines" data-options='{"preformattedCode": true}'>
<div class="input">
<code>

nasty
code

</code>
</div>
<pre class="expected">` nasty code `</pre>
</div>

<!-- /TEST CASES -->

<script src="turndown-test.browser.js"></script>
Expand Down

0 comments on commit 85a6fd7

Please sign in to comment.