Skip to content

Commit dadad8f

Browse files
authored
Add support for form elements
Closes GH-3. Closes GH-50.
1 parent 785b144 commit dadad8f

File tree

29 files changed

+1181
-16
lines changed

29 files changed

+1181
-16
lines changed

index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,26 @@
33
module.exports = toMdast
44

55
var minify = require('rehype-minify-whitespace')
6+
var visit = require('unist-util-visit')
67
var xtend = require('xtend')
78
var one = require('./lib/one')
89
var handlers = require('./lib/handlers')
910

1011
function toMdast(tree, options) {
1112
var settings = options || {}
1213
var opts = {newlines: settings.newlines === true}
14+
var byId = {}
1315

16+
h.nodeById = byId
1417
h.baseFound = false
1518
h.frozenBaseURL = null
1619

1720
h.handlers = xtend(handlers, settings.handlers || {})
1821
h.augment = augment
1922
h.document = settings.document
2023

24+
visit(tree, onvisit)
25+
2126
return one(h, minify(opts)(tree), null)
2227

2328
function h(node, type, props, children) {
@@ -51,4 +56,13 @@ function toMdast(tree, options) {
5156

5257
return right
5358
}
59+
60+
function onvisit(node) {
61+
var props = node.properties || {}
62+
var id = props.id
63+
64+
if (id && !(id in node)) {
65+
byId[id] = node
66+
}
67+
}
5468
}
File renamed without changes.

lib/handlers/index.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ var br = require('./break')
88
var cell = require('./table-cell')
99
var code = require('./code')
1010
var comment = require('./comment')
11-
var dataList = require('./data-list')
11+
var dl = require('./dl')
1212
var del = require('./delete')
1313
var emphasis = require('./emphasis')
1414
var heading = require('./heading')
1515
var iframe = require('./iframe')
1616
var image = require('./image')
1717
var inlineCode = require('./inline-code')
18+
var input = require('./input')
1819
var link = require('./link')
1920
var list = require('./list')
2021
var listItem = require('./list-item')
@@ -23,9 +24,11 @@ var paragraph = require('./paragraph')
2324
var quote = require('./q')
2425
var root = require('./root')
2526
var row = require('./table-row')
27+
var select = require('./select')
2628
var strong = require('./strong')
2729
var table = require('./table')
2830
var text = require('./text')
31+
var textarea = require('./textarea')
2932
var thematicBreak = require('./thematic-break')
3033
var wbr = require('./wbr')
3134

@@ -49,7 +52,6 @@ exports.element = ignore
4952
exports.embed = ignore
5053
exports.frame = ignore
5154
exports.frameset = ignore
52-
exports.input = ignore
5355
exports.isindex = ignore
5456
exports.keygen = ignore
5557
exports.link = ignore
@@ -66,7 +68,6 @@ exports.optgroup = ignore
6668
exports.option = ignore
6769
exports.param = ignore
6870
exports.script = ignore
69-
exports.select = ignore
7071
exports.shadow = ignore
7172
exports.source = ignore
7273
exports.spacer = ignore
@@ -82,13 +83,15 @@ exports.bdi = all
8283
exports.bdo = all
8384
exports.big = all
8485
exports.blink = all
86+
exports.button = all
8587
exports.canvas = all
8688
exports.cite = all
8789
exports.data = all
8890
exports.details = all
8991
exports.dfn = all
9092
exports.font = all
9193
exports.ins = all
94+
exports.label = all
9295
exports.marquee = all
9396
exports.meter = all
9497
exports.nobr = all
@@ -117,12 +120,15 @@ exports.aside = wrapped
117120
exports.body = wrapped
118121
exports.center = wrapped
119122
exports.div = wrapped
123+
exports.fieldset = wrapped
120124
exports.figcaption = wrapped
121125
exports.figure = wrapped
126+
exports.form = wrapped
122127
exports.footer = wrapped
123128
exports.header = wrapped
124129
exports.hgroup = wrapped
125130
exports.html = wrapped
131+
exports.legend = wrapped
126132
exports.main = wrapped
127133
exports.multicol = wrapped
128134
exports.nav = wrapped
@@ -137,7 +143,7 @@ exports.blockquote = blockquote
137143
exports.br = br
138144
exports.code = inlineCode
139145
exports.dir = list
140-
exports.dl = dataList
146+
exports.dl = dl
141147
exports.dt = listItem
142148
exports.dd = listItem
143149
exports.del = del
@@ -153,6 +159,7 @@ exports.i = emphasis
153159
exports.iframe = iframe
154160
exports.img = image
155161
exports.image = image
162+
exports.input = input
156163
exports.kbd = inlineCode
157164
exports.li = listItem
158165
exports.listing = code
@@ -164,11 +171,13 @@ exports.pre = code
164171
exports.q = quote
165172
exports.s = del
166173
exports.samp = inlineCode
174+
exports.select = select
167175
exports.strike = del
168176
exports.strong = strong
169177
exports.summary = paragraph
170178
exports.table = table
171179
exports.td = cell
180+
exports.textarea = textarea
172181
exports.th = cell
173182
exports.tr = row
174183
exports.tt = inlineCode

lib/handlers/input.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use strict'
2+
3+
var repeat = require('repeat-string')
4+
var is = require('hast-util-is-element')
5+
var resolve = require('../util/resolve')
6+
var findSelectedOptions = require('../util/find-selected-options')
7+
8+
module.exports = input
9+
10+
// eslint-disable-next-line complexity
11+
function input(h, node) {
12+
var byId = h.nodeById
13+
var props = node.properties
14+
var value = props.value || props.placeholder
15+
var list = props.list
16+
var type = props.type
17+
var values = []
18+
var length
19+
var index
20+
var results
21+
var url
22+
var text
23+
24+
if (props.disabled || props.type === 'hidden' || props.type === 'file') {
25+
return
26+
}
27+
28+
if (type === 'checkbox' || type === 'radio') {
29+
return {type: 'text', value: '[' + (props.checked ? 'x' : ' ') + ']'}
30+
}
31+
32+
if (type === 'image' && props.alt) {
33+
values = [[props.alt]]
34+
} else if (value) {
35+
values = [[value]]
36+
} else if (
37+
list &&
38+
type !== 'password' && // `list` is not supported on `password`
39+
type !== 'file' && // …or `file`
40+
type !== 'submit' && // …or `submit`
41+
type !== 'reset' && // …or `reset`
42+
type !== 'button' && // …or `button`
43+
list in byId &&
44+
is(byId[list], 'datalist')
45+
) {
46+
values = findSelectedOptions(byId[list], props)
47+
}
48+
49+
if (values.length === 0) {
50+
return
51+
}
52+
53+
// Passwords don’t support `list`.
54+
if (type === 'password') {
55+
values[0] = [repeat('•', values[0][0].length)]
56+
}
57+
58+
// Images don’t support `list`.
59+
if (type === 'image') {
60+
return h(node, 'image', {
61+
url: resolve(h, props.src),
62+
title: props.title || null,
63+
alt: values[0][0]
64+
})
65+
}
66+
67+
length = values.length
68+
index = -1
69+
results = []
70+
71+
if (type !== 'url' && type !== 'email') {
72+
while (++index < length) {
73+
value = values[index]
74+
results.push(value[1] ? value[1] + ' (' + value[0] + ')' : value[0])
75+
}
76+
77+
return h.augment(node, {type: 'text', value: results.join(', ')})
78+
}
79+
80+
while (++index < length) {
81+
value = values[index]
82+
text = resolve(h, value[0])
83+
url = type === 'email' ? 'mailto:' + text : text
84+
85+
results.push(
86+
h(node, 'link', {title: null, url: url}, [
87+
{type: 'text', value: value[1] || text}
88+
])
89+
)
90+
91+
if (index !== length - 1) {
92+
results.push({type: 'text', value: ', '})
93+
}
94+
}
95+
96+
return results
97+
}

lib/handlers/list-item.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = listItem
55
var is = require('hast-util-is-element')
66
var wrapChildren = require('../util/wrap-children')
77

8+
// eslint-disable-next-line complexity
89
function listItem(h, node) {
910
var children = node.children
1011
var head = children[0]
@@ -21,17 +22,32 @@ function listItem(h, node) {
2122
if (
2223
checkbox &&
2324
is(checkbox, 'input') &&
24-
checkbox.properties.type === 'checkbox'
25+
(checkbox.properties.type === 'checkbox' ||
26+
checkbox.properties.type === 'radio')
2527
) {
2628
checked = Boolean(checkbox.properties.checked)
2729
}
2830
}
2931

3032
content = wrapChildren(h, node)
3133

32-
// Remove initial spacing if we previously found a checkbox.
3334
if (checked !== null) {
3435
grandchildren = content[0] && content[0].children
36+
37+
// Remove text checkbox (enabled inputs are mapped to textual checkboxes).
38+
head = grandchildren && grandchildren[0]
39+
40+
if (
41+
head &&
42+
head.type === 'text' &&
43+
head.value.length === 3 &&
44+
head.value.charAt(0) === '[' &&
45+
head.value.charAt(2) === ']'
46+
) {
47+
grandchildren.shift()
48+
}
49+
50+
// Remove initial spacing if we previously found a checkbox.
3551
head = grandchildren && grandchildren[0]
3652

3753
if (head && head.type === 'text' && head.value.charAt(0) === ' ') {

lib/handlers/select.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict'
2+
3+
var findSelectedOptions = require('../util/find-selected-options')
4+
5+
module.exports = select
6+
7+
function select(h, node) {
8+
var values = findSelectedOptions(node)
9+
var length = values.length
10+
var index = -1
11+
var results = []
12+
var value
13+
14+
while (++index < length) {
15+
value = values[index]
16+
results.push(value[1] ? value[1] + ' (' + value[0] + ')' : value[0])
17+
}
18+
19+
if (results.length !== 0) {
20+
return h.augment(node, {
21+
type: 'text',
22+
value: results.join(', ')
23+
})
24+
}
25+
}

lib/handlers/textarea.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict'
2+
3+
var toText = require('hast-util-to-text')
4+
5+
module.exports = textarea
6+
7+
function textarea(h, node) {
8+
return h.augment(node, {type: 'text', value: toText(node)})
9+
}

lib/util/find-selected-options.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict'
2+
3+
var is = require('hast-util-is-element')
4+
var has = require('hast-util-has-property')
5+
var toText = require('hast-util-to-text')
6+
7+
module.exports = findSelectedOptions
8+
9+
function findSelectedOptions(node, properties) {
10+
var props = properties || node.properties
11+
var multiple = props.multiple
12+
var size = Math.min(parseInt(props.size, 10), 0) || (multiple ? 4 : 1)
13+
var options = findOptions(node)
14+
var length = options.length
15+
var index = -1
16+
var selectedOptions = []
17+
var values = []
18+
var option
19+
var list
20+
var content
21+
var label
22+
var value
23+
24+
while (++index < length) {
25+
option = options[index]
26+
27+
if (option.properties.selected) {
28+
selectedOptions.push(option)
29+
}
30+
}
31+
32+
list = selectedOptions.length === 0 ? options : selectedOptions
33+
options = list.slice(0, size)
34+
length = options.length
35+
index = -1
36+
37+
while (++index < length) {
38+
option = options[index]
39+
content = toText(option)
40+
label = content || option.properties.label
41+
value = option.properties.value || content
42+
43+
values.push([value, label === value ? null : label])
44+
}
45+
46+
return values
47+
}
48+
49+
function findOptions(node) {
50+
var children = node.children
51+
var length = children.length
52+
var index = -1
53+
var results = []
54+
var child
55+
56+
while (++index < length) {
57+
child = children[index]
58+
59+
if (is(child, 'option')) {
60+
if (!has(child, 'disabled')) {
61+
results.push(child)
62+
}
63+
} else if ('children' in child) {
64+
results = results.concat(findOptions(child))
65+
}
66+
}
67+
68+
return results
69+
}

0 commit comments

Comments
 (0)