Skip to content

Commit 844a801

Browse files
committed
Add interface for spreading flow, fix spread
Closes GH-4.
1 parent d9d0726 commit 844a801

File tree

5 files changed

+215
-42
lines changed

5 files changed

+215
-42
lines changed

lib/index.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ module.exports = toMarkdown
33
var zwitch = require('zwitch')
44
var defaultHandlers = require('./handle')
55
var defaultUnsafePatterns = require('./unsafe')
6+
var defaultJoin = require('./join')
67

7-
// To do (extension)
8-
// - GFM: `tableCellPadding`, `tablePipeAlign`, `stringLength`.
9-
// - Footnotes: `footnote`, `footnoteDefinition`, `footnoteReference`.
108
function toMarkdown(tree, options) {
119
var settings = options || {}
1210
var extensions = configure(settings)
@@ -21,7 +19,8 @@ function toMarkdown(tree, options) {
2119
stack: stack,
2220
enter: enter,
2321
options: settings,
24-
unsafePatterns: extensions.unsafe
22+
unsafePatterns: extensions.unsafe,
23+
join: extensions.join
2524
}
2625
var result = handle(tree, null, context, {before: '\n', after: '\n'})
2726

@@ -55,19 +54,32 @@ function unknown(node) {
5554

5655
function configure(settings) {
5756
var unsafe = defaultUnsafePatterns
57+
var join = defaultJoin
5858
var handlers = Object.assign({}, defaultHandlers)
5959
var extensions = [
60-
{unsafe: settings.unsafe, handlers: settings.handlers}
60+
{unsafe: settings.unsafe, handlers: settings.handlers, join: settings.join}
6161
].concat(settings.extensions || [])
6262
var length = extensions.length
6363
var index = -1
6464
var extension
6565

66+
if (settings.tightDefinitions) {
67+
join = [joinDefinition].concat(join)
68+
}
69+
6670
while (++index < length) {
6771
extension = extensions[index]
6872
unsafe = unsafe.concat(extension.unsafe || [])
73+
join = join.concat(extension.join || [])
6974
handlers = Object.assign(handlers, extension.handlers || {})
7075
}
7176

72-
return {unsafe: unsafe, handlers: handlers}
77+
return {unsafe: unsafe, join: join, handlers: handlers}
78+
}
79+
80+
function joinDefinition(left, right) {
81+
// No blank line between adjacent definitions.
82+
if (left.type === 'definition' && left.type === right.type) {
83+
return 0
84+
}
7385
}

lib/join.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module.exports = [joinDefaults]
2+
3+
var formatCodeAsIndented = require('./util/format-code-as-indented')
4+
var formatHeadingAsSetext = require('./util/format-heading-as-setext')
5+
6+
function joinDefaults(left, right, parent, context) {
7+
if (
8+
// Two lists with the same marker.
9+
(left.type === 'list' &&
10+
right.type === left.type &&
11+
Boolean(left.ordered) === Boolean(right.ordered)) ||
12+
// Adjacent quotes.
13+
(left.type === 'blockquote' && right.type === left.type) ||
14+
// Indented code after list or another indented code.
15+
(right.type === 'code' &&
16+
formatCodeAsIndented(right, context) &&
17+
(left.type === 'list' ||
18+
(left.type === right.type && formatCodeAsIndented(left, context))))
19+
) {
20+
return false
21+
}
22+
23+
// Join children of a list or an item.
24+
// In which case, `parent` has a `spread` field.
25+
if (typeof parent.spread === 'boolean') {
26+
if (
27+
left.type === 'paragraph' &&
28+
// Two paragraphs.
29+
(left.type === right.type ||
30+
right.type === 'definition' ||
31+
// Paragraph followed by a setext heading.
32+
(right.type === 'heading' && formatHeadingAsSetext(right, context)))
33+
) {
34+
return
35+
}
36+
37+
return parent.spread ? 1 : 0
38+
}
39+
}

lib/util/container-flow.js

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,50 @@
11
module.exports = flow
22

3-
var formatCodeAsIndented = require('../util/format-code-as-indented')
3+
var repeat = require('repeat-string')
44

55
function flow(parent, context) {
66
var children = parent.children || []
77
var length = children.length
8-
var index = -1
9-
var results = []
108
var next = children[0]
11-
// Act as if we’re between eols, because we are.
12-
var safeOptions = {before: '\n', after: '\n'}
9+
var results = []
10+
var index = -1
1311
var child
14-
var after
1512

1613
while (++index < length) {
1714
child = next
1815
next = children[index + 1]
1916

20-
results.push(context.handle(child, parent, context, safeOptions))
17+
results.push(
18+
context.handle(child, parent, context, {before: '\n', after: '\n'})
19+
)
2120

2221
if (next) {
23-
if (
24-
child.type === 'list' &&
25-
((next.type === 'code' && formatCodeAsIndented(next, context)) ||
26-
(child.type === next.type &&
27-
Boolean(child.ordered) === Boolean(next.ordered)))
28-
) {
29-
after = '\n\n<!---->\n\n'
30-
} else if (
31-
parent.spread === false ||
32-
(context.options.tightDefinitions &&
33-
child.type === 'definition' &&
34-
child.type === next.type)
35-
) {
36-
after = '\n'
37-
} else {
38-
after = '\n\n'
39-
}
40-
41-
results.push(after)
22+
results.push(between(child, next))
4223
}
4324
}
4425

4526
return results.join('')
27+
28+
function between(left, right) {
29+
var index = -1
30+
var result
31+
32+
while (++index < context.join.length) {
33+
result = context.join[index](left, right, parent, context)
34+
35+
if (result === true || result === 1) {
36+
break
37+
}
38+
39+
if (typeof result === 'number') {
40+
return repeat('\n', 1 + Number(result))
41+
}
42+
43+
if (result === false) {
44+
return '\n\n<!---->\n\n'
45+
}
46+
}
47+
48+
return '\n\n'
49+
}
4650
}

readme.md

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,16 @@ Marker to use to serialize strong (`'*'` or `'_'`, default: `'*'`).
135135

136136
###### `options.tightDefinitions`
137137

138-
Whether to separate definitions with a single line feed (`boolean`, default:
139-
`false`).
138+
Whether to join definitions w/o a blank line (`boolean`, default: `false`).
139+
Shortcut for a join function like so:
140+
141+
```js
142+
function (left, right) {
143+
if (left.type === 'definition' && right.type === 'definition') {
144+
return 0
145+
}
146+
}
147+
```
140148

141149
##### Extension options
142150

@@ -147,18 +155,39 @@ Each `ToMarkdownExtension` is an object with optional `unsafe` and `handlers`
147155
keys, mapping to the values which can also be passed in directly, documented
148156
below.
149157

150-
###### `options.unsafe`
151-
152-
List of patterns to escape.
153-
Useful for syntax extensions.
154-
Take a look at [`lib/unsafe.js`][unsafe] for examples.
155-
156158
###### `options.handlers`
157159

158160
Object mapping node types to custom handlers.
159161
Useful for syntax extensions.
160162
Take a look at [`lib/handle`][handlers] for examples.
161163

164+
###### `options.join`
165+
166+
List of functions used to determine what to place between two flow nodes.
167+
Often, they are joined by one blank line.
168+
In certain cases, it’s nicer to have them next to each other.
169+
Or, they can’t occur together.
170+
These functions receive two adjacent nodes and their parent and can return
171+
`number` or `boolean`, referring to how many blank lines to use between them.
172+
A return value of `true` is as passing `1`.
173+
A return value of `false` means the nodes cannot be joined by a blank line, such
174+
as two adjacent block quotes or indented code after a list, in which case a
175+
comment will be injected to break them up:
176+
177+
```markdown
178+
> Quote 1
179+
180+
<!---->
181+
182+
> Quote 2
183+
```
184+
185+
###### `options.unsafe`
186+
187+
List of patterns to escape.
188+
Useful for syntax extensions.
189+
Take a look at [`lib/unsafe.js`][unsafe] for examples.
190+
162191
##### Returns
163192

164193
`string` — Serialized markdown.

test.js

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ test('core', function (t) {
6666
]
6767
}),
6868
'a\n\n*\n\n<!---->\n\n*\n\n1.\n\n<!---->\n\n1.\n\nd\n',
69-
'should inject HTML comments between lists w/ the same ordered as they’d otherwise run into each other'
69+
'should inject HTML comments between lists w/ the same marker'
7070
)
7171

7272
t.equal(
@@ -79,7 +79,96 @@ test('core', function (t) {
7979
]
8080
}),
8181
' a\n\n*\n\n<!---->\n\n b\n',
82-
'should inject HTML comments between lists and an indented code as they’d otherwise run into each other'
82+
'should inject HTML comments between lists and an indented code'
83+
)
84+
85+
t.equal(
86+
to({
87+
type: 'root',
88+
children: [
89+
{type: 'code', value: 'a'},
90+
{type: 'code', value: 'b'}
91+
]
92+
}),
93+
' a\n\n<!---->\n\n b\n',
94+
'should inject HTML comments between adjacent indented code'
95+
)
96+
97+
t.equal(
98+
to({
99+
type: 'root',
100+
children: [
101+
{
102+
type: 'blockquote',
103+
children: [
104+
{type: 'paragraph', children: [{type: 'text', value: 'a'}]}
105+
]
106+
},
107+
{
108+
type: 'blockquote',
109+
children: [
110+
{type: 'paragraph', children: [{type: 'text', value: 'b'}]}
111+
]
112+
}
113+
]
114+
}),
115+
'> a\n\n<!---->\n\n> b\n',
116+
'should inject HTML comments between two block quotes'
117+
)
118+
119+
t.equal(
120+
to({
121+
type: 'listItem',
122+
spread: false,
123+
children: [
124+
{type: 'paragraph', children: [{type: 'text', value: 'a'}]},
125+
{type: 'paragraph', children: [{type: 'text', value: 'b'}]}
126+
]
127+
}),
128+
'* a\n\n b\n',
129+
'should not honour `spread: false` for two paragraphs'
130+
)
131+
132+
t.equal(
133+
to({
134+
type: 'listItem',
135+
spread: false,
136+
children: [
137+
{type: 'paragraph', children: [{type: 'text', value: 'a'}]},
138+
{type: 'definition', label: 'b', url: 'c'}
139+
]
140+
}),
141+
'* a\n\n [b]: c\n',
142+
'should not honour `spread: false` for a paragraph and a definition'
143+
)
144+
145+
t.equal(
146+
to({
147+
type: 'listItem',
148+
spread: false,
149+
children: [
150+
{type: 'paragraph', children: [{type: 'text', value: 'a'}]},
151+
{type: 'heading', depth: 1, children: [{type: 'text', value: 'b'}]}
152+
]
153+
}),
154+
'* a\n # b\n',
155+
'should honour `spread: false` for a paragraph and a heading'
156+
)
157+
158+
t.equal(
159+
to(
160+
{
161+
type: 'listItem',
162+
spread: false,
163+
children: [
164+
{type: 'paragraph', children: [{type: 'text', value: 'a'}]},
165+
{type: 'heading', depth: 1, children: [{type: 'text', value: 'b'}]}
166+
]
167+
},
168+
{setext: true}
169+
),
170+
'* a\n\n b\n =\n',
171+
'should not honour `spread: false` for a paragraph and a setext heading'
83172
)
84173

85174
t.throws(

0 commit comments

Comments
 (0)