diff --git a/index.js b/index.js index 085b713..ceba638 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ module.exports = toMdast var minify = require('rehype-minify-whitespace') +var visit = require('unist-util-visit') var xtend = require('xtend') var one = require('./lib/one') var handlers = require('./lib/handlers') @@ -10,7 +11,9 @@ var handlers = require('./lib/handlers') function toMdast(tree, options) { var settings = options || {} var opts = {newlines: settings.newlines === true} + var byId = {} + h.nodeById = byId h.baseFound = false h.frozenBaseURL = null @@ -18,6 +21,8 @@ function toMdast(tree, options) { h.augment = augment h.document = settings.document + visit(tree, onvisit) + return one(h, minify(opts)(tree), null) function h(node, type, props, children) { @@ -51,4 +56,13 @@ function toMdast(tree, options) { return right } + + function onvisit(node) { + var props = node.properties || {} + var id = props.id + + if (id && !(id in node)) { + byId[id] = node + } + } } diff --git a/lib/handlers/data-list.js b/lib/handlers/dl.js similarity index 100% rename from lib/handlers/data-list.js rename to lib/handlers/dl.js diff --git a/lib/handlers/index.js b/lib/handlers/index.js index fe40fdb..d995be6 100644 --- a/lib/handlers/index.js +++ b/lib/handlers/index.js @@ -8,13 +8,14 @@ var br = require('./break') var cell = require('./table-cell') var code = require('./code') var comment = require('./comment') -var dataList = require('./data-list') +var dl = require('./dl') var del = require('./delete') var emphasis = require('./emphasis') var heading = require('./heading') var iframe = require('./iframe') var image = require('./image') var inlineCode = require('./inline-code') +var input = require('./input') var link = require('./link') var list = require('./list') var listItem = require('./list-item') @@ -23,9 +24,11 @@ var paragraph = require('./paragraph') var quote = require('./q') var root = require('./root') var row = require('./table-row') +var select = require('./select') var strong = require('./strong') var table = require('./table') var text = require('./text') +var textarea = require('./textarea') var thematicBreak = require('./thematic-break') var wbr = require('./wbr') @@ -49,7 +52,6 @@ exports.element = ignore exports.embed = ignore exports.frame = ignore exports.frameset = ignore -exports.input = ignore exports.isindex = ignore exports.keygen = ignore exports.link = ignore @@ -66,7 +68,6 @@ exports.optgroup = ignore exports.option = ignore exports.param = ignore exports.script = ignore -exports.select = ignore exports.shadow = ignore exports.source = ignore exports.spacer = ignore @@ -82,6 +83,7 @@ exports.bdi = all exports.bdo = all exports.big = all exports.blink = all +exports.button = all exports.canvas = all exports.cite = all exports.data = all @@ -89,6 +91,7 @@ exports.details = all exports.dfn = all exports.font = all exports.ins = all +exports.label = all exports.marquee = all exports.meter = all exports.nobr = all @@ -117,12 +120,15 @@ exports.aside = wrapped exports.body = wrapped exports.center = wrapped exports.div = wrapped +exports.fieldset = wrapped exports.figcaption = wrapped exports.figure = wrapped +exports.form = wrapped exports.footer = wrapped exports.header = wrapped exports.hgroup = wrapped exports.html = wrapped +exports.legend = wrapped exports.main = wrapped exports.multicol = wrapped exports.nav = wrapped @@ -137,7 +143,7 @@ exports.blockquote = blockquote exports.br = br exports.code = inlineCode exports.dir = list -exports.dl = dataList +exports.dl = dl exports.dt = listItem exports.dd = listItem exports.del = del @@ -153,6 +159,7 @@ exports.i = emphasis exports.iframe = iframe exports.img = image exports.image = image +exports.input = input exports.kbd = inlineCode exports.li = listItem exports.listing = code @@ -164,11 +171,13 @@ exports.pre = code exports.q = quote exports.s = del exports.samp = inlineCode +exports.select = select exports.strike = del exports.strong = strong exports.summary = paragraph exports.table = table exports.td = cell +exports.textarea = textarea exports.th = cell exports.tr = row exports.tt = inlineCode diff --git a/lib/handlers/input.js b/lib/handlers/input.js new file mode 100644 index 0000000..a02f845 --- /dev/null +++ b/lib/handlers/input.js @@ -0,0 +1,97 @@ +'use strict' + +var repeat = require('repeat-string') +var is = require('hast-util-is-element') +var resolve = require('../util/resolve') +var findSelectedOptions = require('../util/find-selected-options') + +module.exports = input + +// eslint-disable-next-line complexity +function input(h, node) { + var byId = h.nodeById + var props = node.properties + var value = props.value || props.placeholder + var list = props.list + var type = props.type + var values = [] + var length + var index + var results + var url + var text + + if (props.disabled || props.type === 'hidden' || props.type === 'file') { + return + } + + if (type === 'checkbox' || type === 'radio') { + return {type: 'text', value: '[' + (props.checked ? 'x' : ' ') + ']'} + } + + if (type === 'image' && props.alt) { + values = [[props.alt]] + } else if (value) { + values = [[value]] + } else if ( + list && + type !== 'password' && // `list` is not supported on `password` + type !== 'file' && // …or `file` + type !== 'submit' && // …or `submit` + type !== 'reset' && // …or `reset` + type !== 'button' && // …or `button` + list in byId && + is(byId[list], 'datalist') + ) { + values = findSelectedOptions(byId[list], props) + } + + if (values.length === 0) { + return + } + + // Passwords don’t support `list`. + if (type === 'password') { + values[0] = [repeat('•', values[0][0].length)] + } + + // Images don’t support `list`. + if (type === 'image') { + return h(node, 'image', { + url: resolve(h, props.src), + title: props.title || null, + alt: values[0][0] + }) + } + + length = values.length + index = -1 + results = [] + + if (type !== 'url' && type !== 'email') { + while (++index < length) { + value = values[index] + results.push(value[1] ? value[1] + ' (' + value[0] + ')' : value[0]) + } + + return h.augment(node, {type: 'text', value: results.join(', ')}) + } + + while (++index < length) { + value = values[index] + text = resolve(h, value[0]) + url = type === 'email' ? 'mailto:' + text : text + + results.push( + h(node, 'link', {title: null, url: url}, [ + {type: 'text', value: value[1] || text} + ]) + ) + + if (index !== length - 1) { + results.push({type: 'text', value: ', '}) + } + } + + return results +} diff --git a/lib/handlers/list-item.js b/lib/handlers/list-item.js index 7160472..68e43e5 100644 --- a/lib/handlers/list-item.js +++ b/lib/handlers/list-item.js @@ -5,6 +5,7 @@ module.exports = listItem var is = require('hast-util-is-element') var wrapChildren = require('../util/wrap-children') +// eslint-disable-next-line complexity function listItem(h, node) { var children = node.children var head = children[0] @@ -21,7 +22,8 @@ function listItem(h, node) { if ( checkbox && is(checkbox, 'input') && - checkbox.properties.type === 'checkbox' + (checkbox.properties.type === 'checkbox' || + checkbox.properties.type === 'radio') ) { checked = Boolean(checkbox.properties.checked) } @@ -29,9 +31,23 @@ function listItem(h, node) { content = wrapChildren(h, node) - // Remove initial spacing if we previously found a checkbox. if (checked !== null) { grandchildren = content[0] && content[0].children + + // Remove text checkbox (enabled inputs are mapped to textual checkboxes). + head = grandchildren && grandchildren[0] + + if ( + head && + head.type === 'text' && + head.value.length === 3 && + head.value.charAt(0) === '[' && + head.value.charAt(2) === ']' + ) { + grandchildren.shift() + } + + // Remove initial spacing if we previously found a checkbox. head = grandchildren && grandchildren[0] if (head && head.type === 'text' && head.value.charAt(0) === ' ') { diff --git a/lib/handlers/select.js b/lib/handlers/select.js new file mode 100644 index 0000000..dec6be8 --- /dev/null +++ b/lib/handlers/select.js @@ -0,0 +1,25 @@ +'use strict' + +var findSelectedOptions = require('../util/find-selected-options') + +module.exports = select + +function select(h, node) { + var values = findSelectedOptions(node) + var length = values.length + var index = -1 + var results = [] + var value + + while (++index < length) { + value = values[index] + results.push(value[1] ? value[1] + ' (' + value[0] + ')' : value[0]) + } + + if (results.length !== 0) { + return h.augment(node, { + type: 'text', + value: results.join(', ') + }) + } +} diff --git a/lib/handlers/textarea.js b/lib/handlers/textarea.js new file mode 100644 index 0000000..39c29cc --- /dev/null +++ b/lib/handlers/textarea.js @@ -0,0 +1,9 @@ +'use strict' + +var toText = require('hast-util-to-text') + +module.exports = textarea + +function textarea(h, node) { + return h.augment(node, {type: 'text', value: toText(node)}) +} diff --git a/lib/util/find-selected-options.js b/lib/util/find-selected-options.js new file mode 100644 index 0000000..6a90b4f --- /dev/null +++ b/lib/util/find-selected-options.js @@ -0,0 +1,69 @@ +'use strict' + +var is = require('hast-util-is-element') +var has = require('hast-util-has-property') +var toText = require('hast-util-to-text') + +module.exports = findSelectedOptions + +function findSelectedOptions(node, properties) { + var props = properties || node.properties + var multiple = props.multiple + var size = Math.min(parseInt(props.size, 10), 0) || (multiple ? 4 : 1) + var options = findOptions(node) + var length = options.length + var index = -1 + var selectedOptions = [] + var values = [] + var option + var list + var content + var label + var value + + while (++index < length) { + option = options[index] + + if (option.properties.selected) { + selectedOptions.push(option) + } + } + + list = selectedOptions.length === 0 ? options : selectedOptions + options = list.slice(0, size) + length = options.length + index = -1 + + while (++index < length) { + option = options[index] + content = toText(option) + label = content || option.properties.label + value = option.properties.value || content + + values.push([value, label === value ? null : label]) + } + + return values +} + +function findOptions(node) { + var children = node.children + var length = children.length + var index = -1 + var results = [] + var child + + while (++index < length) { + child = children[index] + + if (is(child, 'option')) { + if (!has(child, 'disabled')) { + results.push(child) + } + } else if ('children' in child) { + results = results.concat(findOptions(child)) + } + } + + return results +} diff --git a/package.json b/package.json index 344d7fd..1877775 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "mdast-util-phrasing": "^1.0.0", "mdast-util-to-string": "^1.0.4", "rehype-minify-whitespace": "^2.0.3", + "repeat-string": "^1.6.1", "trim-trailing-lines": "^1.1.0", "unist-util-visit": "^1.1.1", "xtend": "^4.0.1" diff --git a/test/fixtures/button/index.html b/test/fixtures/button/index.html new file mode 100644 index 0000000..a0ad624 --- /dev/null +++ b/test/fixtures/button/index.html @@ -0,0 +1,29 @@ + +
+ + + + + + + + + + + ++ +
++ +
diff --git a/test/fixtures/button/index.json b/test/fixtures/button/index.json new file mode 100644 index 0000000..a875830 --- /dev/null +++ b/test/fixtures/button/index.json @@ -0,0 +1,3 @@ +{ + "fragment": true +} diff --git a/test/fixtures/button/index.md b/test/fixtures/button/index.md new file mode 100644 index 0000000..030b294 --- /dev/null +++ b/test/fixtures/button/index.md @@ -0,0 +1,19 @@ + + +Show hint + + + +Fire + + + +← Back Next → + + + + + +![R](red)![G](green)![B](blue) + +![C](cyan)![M](magenta)![Y](yellow)![K](black) diff --git a/test/fixtures/datalist-input-option/index.md b/test/fixtures/datalist-input-option/index.md index 59405bc..92a2c98 100644 --- a/test/fixtures/datalist-input-option/index.md +++ b/test/fixtures/datalist-input-option/index.md @@ -1 +1 @@ -Choose a browser from this list:· +Choose a browser from this list: Chrome diff --git a/test/fixtures/fieldset/index.html b/test/fixtures/fieldset/index.html new file mode 100644 index 0000000..6f1e67f --- /dev/null +++ b/test/fixtures/fieldset/index.html @@ -0,0 +1,53 @@ + + + + + + + + + + diff --git a/test/fixtures/fieldset/index.json b/test/fixtures/fieldset/index.json new file mode 100644 index 0000000..8925b99 --- /dev/null +++ b/test/fixtures/fieldset/index.json @@ -0,0 +1,4 @@ +{ + "fragment": true, + "tree": false +} diff --git a/test/fixtures/fieldset/index.md b/test/fixtures/fieldset/index.md new file mode 100644 index 0000000..56df159 --- /dev/null +++ b/test/fixtures/fieldset/index.md @@ -0,0 +1,45 @@ +Display + +\[x] Black on White + +\[ ] White on Black + +\[ ] Use grayscale + +Enhance contrast 0 + +Destination + +Airport: Atlanta (ATL) + +Departure time:· + +Did the movie pass the Bechdel test? + +\[ ] No, there are not even two female characters in the movie. + +\[ ] No, the female characters never talk to each other. + +\[ ] No, when female characters talk to each other it’s always about a male character. + +\[ ] Yes. + +\[ ] I don’t know. + +Mail Account + +Name: John Ratzenberger + +Address: [john@example.net](mailto:john@example.net) + +Password:· + +Description: My Email Account + +\[ ] Use Club Card + +Name on card:· + +Card number:· + +Expiry date:· diff --git a/test/fixtures/form/index.html b/test/fixtures/form/index.html new file mode 100644 index 0000000..6b47a50 --- /dev/null +++ b/test/fixtures/form/index.html @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + +- - Stem: + Value: + stems to:
diff --git a/test/fixtures/output/index.md b/test/fixtures/output/index.md index ad0319e..70ebcc2 100644 --- a/test/fixtures/output/index.md +++ b/test/fixtures/output/index.md @@ -1 +1 @@ -·Stem: consider +Value: considerations stems to: consider diff --git a/test/fixtures/select-optgroup-option/index.html b/test/fixtures/select-optgroup-option/index.html index e2911cb..27a1096 100644 --- a/test/fixtures/select-optgroup-option/index.html +++ b/test/fixtures/select-optgroup-option/index.html @@ -17,10 +17,45 @@- And these: + This one: + +
+ ++ Or this: + +
+ ++ Or this: + +
+ ++ Or this: + +
+ ++ Or this:
diff --git a/test/fixtures/select-optgroup-option/index.md b/test/fixtures/select-optgroup-option/index.md index b656cbb..9124d7f 100644 --- a/test/fixtures/select-optgroup-option/index.md +++ b/test/fixtures/select-optgroup-option/index.md @@ -1,3 +1,11 @@ -Check out these options:· +Check out these options: Option 1.1 -And these:· +This one: Value 2 + +Or this: Value 2 + +Or this: Value 1 + +Or this: Value 2, Value 3 + +Or this:· diff --git a/test/fixtures/textarea/index.html b/test/fixtures/textarea/index.html new file mode 100644 index 0000000..78ec9f5 --- /dev/null +++ b/test/fixtures/textarea/index.html @@ -0,0 +1,20 @@ ++ If you have any short comments, please let us know: + +
+ ++ If you have any comments, please let us know: + +
+ ++ If you have any comments, please let us know: + +
diff --git a/test/fixtures/textarea/index.json b/test/fixtures/textarea/index.json new file mode 100644 index 0000000..cdb2b19 --- /dev/null +++ b/test/fixtures/textarea/index.json @@ -0,0 +1,5 @@ +{ + "fragment": true, + "#": "TODO: `tree` is false because remark-stringify should escape blank lines in text so as not to generate separate paragraphs", + "tree": false +} diff --git a/test/fixtures/textarea/index.md b/test/fixtures/textarea/index.md new file mode 100644 index 0000000..d580e26 --- /dev/null +++ b/test/fixtures/textarea/index.md @@ -0,0 +1,11 @@ +If you have any short comments, please let us know:· + +If you have any comments, please let us know: You rock! + +If you have any comments, please let us know: Dear Francine, + +They closed the parks this week, so we won’t be able to +meet your there. Should we just have dinner? + +Love, +Daddy