Skip to content

Commit 033ee3d

Browse files
committed
Add gfm table support
1 parent b7cada7 commit 033ee3d

File tree

7 files changed

+151
-5
lines changed

7 files changed

+151
-5
lines changed

examples/index.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ function log() {
88
return this
99
}
1010

11-
const App = () => md({ h1: 'h2' })`
11+
const App = () => md({
12+
h1: 'h2',
13+
th: ({ children, style }) =>
14+
<th style={{ ...style, fontWeight: 'bold' }}>{children}</th>
15+
})`
1216
# title
1317
1418
This is some text <span style=${{ fontWeight: 'bold' }}> we _here_ </span>
@@ -43,6 +47,21 @@ This is an H2
4347
## This is an H2
4448
4549
###### This is an H6
50+
51+
Table
52+
53+
| Left-aligned | Center-aligned | Right-aligned |
54+
| :--- | :---: | ---: |
55+
| git status | git status | git status |
56+
| git diff | git diff | git diff |
57+
58+
Table without pipes
59+
60+
Markdown | Less | Pretty
61+
--- | --- | ---
62+
*Still* | \`renders\` | **nicely**
63+
1 | <span style=${{ fontWeight: 'bold' }}>2</span> | 3
64+
4665
`::log()
4766

4867

src/babel.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as babylon from 'babylon'
44

55
import commonmark from 'commonmark'
66
import JSXRenderer from './jsx'
7-
7+
import { replaceTablesWithHTML, replaceTableStubs } from './table'
88

99
module.exports = {
1010
visitor: {
@@ -81,10 +81,12 @@ module.exports = {
8181
}
8282
return arr
8383
}, []).join('')
84-
let parsed = reader.parse(src)
84+
let parsed = reader.parse(replaceTablesWithHTML(src))
8585
let intermediateSrc = writer.render(parsed)
8686
// replace with stubs
87-
let newSrc = intermediateSrc.replace(/spur\-[0-9]+/gm, x => `{${stubCtx[x]}}`)
87+
let newSrc = replaceTableStubs(
88+
intermediateSrc.replace(/spur\-[0-9]+/gm, x => `{${stubCtx[x]}}`)
89+
)
8890
let transformed = babylon.parse(`${tagName}(${
8991
path.node.tag.type === 'CallExpression' ?
9092
code.substring(path.node.tag.arguments[0].start, path.node.tag.arguments[0].end) + ', ' :

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ const markdown = (o, fn) => {
1313
'br': 'br', 'a': 'a', 'img': 'img', 'em': 'em', 'strong': 'strong', 'p': 'p',
1414
'h1': 'h1', 'h2': 'h2', 'h3': 'h3', 'h4': 'h4', 'h5': 'h5', 'h6': 'h6',
1515
'code': 'code', 'pre': 'pre', 'hr': 'hr', 'blockquote': 'blockquote',
16-
'ul': 'ul', 'ol': 'ol', 'li': 'li', ...o
16+
'ul': 'ul', 'ol': 'ol', 'li': 'li', 'table': 'table', 'thead': 'thead',
17+
'tbody': 'tbody', 'tr': 'tr', 'td': 'td', 'th': 'th', ...o
1718
})
1819
}
1920
}

src/jsx.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ export default class JSXRenderer extends Renderer {
108108
paragraph(node, entering) {
109109
let grandparent = node.parent.parent
110110
, attrs = this.attrs(node)
111+
112+
if (node.firstChild &&
113+
node.firstChild.literal &&
114+
node.firstChild.literal.match('spur-element-table')) {
115+
return
116+
}
111117
if (grandparent !== null &&
112118
grandparent.type === 'list') {
113119
if (grandparent.listTight) {

src/table/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { re, parseTable, parseNpTable } from './parser'
2+
import render from './renderer'
3+
4+
export const replaceTablesWithHTML = src => src
5+
.replace(re.table, (match, ...capture) => render(parseTable(...capture)))
6+
.replace(re.nptable, (match, ...capture) => render(parseNpTable(...capture)))
7+
8+
export const replaceTableStubs = src => src
9+
.replace(/spur\-element\-([a-zA-Z])/gm, (x, element) => `_m_.${element}`)
10+
.replace(/spur\-align\-(left|right|center)/gm, (x, align) => `style={{textAlign: '${align}'}}`)

src/table/parser.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* marked - a markdown parser
3+
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
4+
* https://github.com/chjj/marked
5+
*/
6+
7+
export const re = {
8+
nptable: / *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
9+
table: / *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
10+
}
11+
12+
export const parseTable = (header, align, cells) => {
13+
const item = {
14+
header: header.replace(/^ *| *\| *$/g, '').split(/ *\| */),
15+
align: align.replace(/^ *|\| *$/g, '').split(/ *\| */),
16+
cells: cells.replace(/(?: *\| *)?\n$/, '').split('\n')
17+
}
18+
19+
for (let i = 0; i < item.align.length; i++) {
20+
if (/^ *-+: *$/.test(item.align[i])) {
21+
item.align[i] = 'right'
22+
} else if (/^ *:-+: *$/.test(item.align[i])) {
23+
item.align[i] = 'center'
24+
} else if (/^ *:-+ *$/.test(item.align[i])) {
25+
item.align[i] = 'left'
26+
} else {
27+
item.align[i] = null
28+
}
29+
}
30+
31+
for (let i = 0; i < item.cells.length; i++) {
32+
item.cells[i] = item.cells[i]
33+
.replace(/^ *\| *| *\| *$/g, '')
34+
.split(/ *\| */)
35+
}
36+
37+
return item
38+
}
39+
40+
export const parseNpTable = (header, align, cells) => {
41+
const item = {
42+
header: header.replace(/^ *| *\| *$/g, '').split(/ *\| */),
43+
align: align.replace(/^ *|\| *$/g, '').split(/ *\| */),
44+
cells: cells.replace(/\n$/, '').split('\n')
45+
}
46+
47+
for (let i = 0; i < item.align.length; i++) {
48+
if (/^ *-+: *$/.test(item.align[i])) {
49+
item.align[i] = 'right'
50+
} else if (/^ *:-+: *$/.test(item.align[i])) {
51+
item.align[i] = 'center'
52+
} else if (/^ *:-+ *$/.test(item.align[i])) {
53+
item.align[i] = 'left'
54+
} else {
55+
item.align[i] = null
56+
}
57+
}
58+
59+
for (let i = 0; i < item.cells.length; i++) {
60+
item.cells[i] = item.cells[i].split(/ *\| */)
61+
}
62+
63+
return item
64+
}

src/table/renderer.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Create cell element with alignment
2+
//
3+
// The `align` attribute isn't valid, even though some markdown renderers use it.
4+
// We can't use a style object prior to markdown parsing, and we can't use a
5+
// style string in React. Instead, place a stub for alignment and swap after
6+
// markdown parsing.
7+
const renderCell = (type, align, value) => {
8+
if (align) {
9+
return `<spur-element-${type} spur-align-${align}>${value}</spur-element-${type}>`
10+
} else {
11+
return `<spur-element-${type}>${value}</spur-element-${type}>`
12+
}
13+
}
14+
15+
// Render gfm table markdown to HTML
16+
//
17+
// Use placeholder elements so that we can replace with _m_.element. We can't
18+
// use _m_ directly here, since it isn't a valid HTML tag and will get encoded
19+
// during markdown parsing
20+
export default ({ header, align, cells }) => {
21+
let str = ''
22+
23+
str += '<spur-element-table>'
24+
str += '<spur-element-thead>'
25+
str += '<spur-element-tr>'
26+
str += header.map((value, i) => renderCell('th', align[i], value)).join('')
27+
str += '</spur-element-tr>'
28+
str += '</spur-element-thead>'
29+
str += '<spur-element-tbody>'
30+
31+
for (let i = 0; i < cells.length; i++) {
32+
str += '<spur-element-tr>'
33+
str += cells[i].map((value, j) => renderCell('td', align[j], value)).join('')
34+
str += '</spur-element-tr>'
35+
}
36+
37+
str += '</spur-element-tbody>'
38+
str += '</spur-element-table>'
39+
40+
// Keep table in a separate block from subsequent markdown
41+
str += '\n\n'
42+
43+
return str
44+
}

0 commit comments

Comments
 (0)