init
This commit is contained in:
commit
7a10e1949d
16 changed files with 2103 additions and 0 deletions
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules/
|
17
.eslintrc.js
Normal file
17
.eslintrc.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'google',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'arrow-parens': ['error', 'as-needed'],
|
||||
'indent': ['error', 2, {'MemberExpression': 1}],
|
||||
},
|
||||
};
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules/
|
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2021 sup39[サポミク]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
212
README.md
Normal file
212
README.md
Normal file
|
@ -0,0 +1,212 @@
|
|||
# markdown-it-attr
|
||||
A [markdown-it](https://github.com/markdown-it/markdown-it) plugin
|
||||
to write id, classes, and attributes.
|
||||
|
||||
## Syntax
|
||||
`{#id .class1 .class2 attr=val attr1='val"s"' attr2="val's" attr3}`
|
||||
|
||||
### Where to put `{...}`
|
||||
|type|where|example|
|
||||
|:-:|:-:|--|
|
||||
|inline tag|**AFTER** tag|em / strong|
|
||||
|inline block|beginning / end|li / td / th|
|
||||
|block|**BEFORE** block|h1 / ul / table|
|
||||
|
||||
Note: There is no way to add attributes to `tr` without extension.
|
||||
See [Extension: Attributes for tr](#tr-extension) for more info.
|
||||
|
||||
## Examples
|
||||
### Attributes for inline tag
|
||||
Add `{...}` **AFTER** the inline tag.
|
||||
|
||||
#### em / strong
|
||||
Example Input:
|
||||
```markdown
|
||||
*x*{.a} **y**{.b} ***z***{.c}
|
||||
```
|
||||
Output:
|
||||
```html
|
||||
<p><em class="a">x</em> <strong class="b">y</strong> <em class="c"><strong>z</strong></em></p>
|
||||
```
|
||||
|
||||
### Attributes for inline block
|
||||
Add `{...}` at the **beginning** or the **end** in the inline block.
|
||||
|
||||
#### list item
|
||||
Example Input:
|
||||
```markdown
|
||||
- {.a} x1
|
||||
- x2 {.b}
|
||||
- x3
|
||||
```
|
||||
Output:
|
||||
```html
|
||||
<ul>
|
||||
<li class="a">x1</li>
|
||||
<li class="b">x2</li>
|
||||
<li>x3</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
#### th / td
|
||||
Example Input:
|
||||
```markdown
|
||||
|h1{.a}|h2{.b}|
|
||||
|------|------|
|
||||
|d1{.c}|d2{.d}|
|
||||
```
|
||||
Output:
|
||||
```html
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="a">h1</th>
|
||||
<th class="b">h2</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="c">d1</td>
|
||||
<td class="d">d2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
### Attributes for block
|
||||
Add `{...}` **BEFORE** the block.
|
||||
|
||||
#### header
|
||||
Example Input:
|
||||
```markdown
|
||||
{.a}
|
||||
# h1
|
||||
```
|
||||
Output:
|
||||
```html
|
||||
<h1 class="a">h1</h1>
|
||||
```
|
||||
|
||||
#### list
|
||||
Example Input:
|
||||
```markdown
|
||||
{.b}
|
||||
- l1
|
||||
- l2
|
||||
```
|
||||
Output:
|
||||
```html
|
||||
<ul class="b">
|
||||
<li>l1</li>
|
||||
<li>l2</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
#### table
|
||||
Example Input:
|
||||
```markdown
|
||||
{.c}
|
||||
|h1|h2|
|
||||
|--|--|
|
||||
|d1|d2|
|
||||
```
|
||||
Output:
|
||||
```html
|
||||
<table class="c">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>h1</th>
|
||||
<th>h2</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>d1</td>
|
||||
<td>d2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
## Usage
|
||||
```js
|
||||
const md = require('markdown-it')();
|
||||
const mia = require('@sup39/markdown-it-attrs');
|
||||
|
||||
console.log(md.use(mia).render(`
|
||||
{#head-id}
|
||||
# head
|
||||
`));
|
||||
```
|
||||
Expected output:
|
||||
```html
|
||||
<h1 id="head-id">head</h1>
|
||||
```
|
||||
|
||||
<h2 id="tr-extension">Extension: Attributes for tr</h2>
|
||||
To make adding attributes to `tr` work, it is required to use
|
||||
[@sup39/markdown-it-raw-table](https://github.com/sup39/markdown-it-raw-table)
|
||||
plugin, in order to prevent forcing td count to be equal to th count,
|
||||
which eliminates the attributes of `tr`.
|
||||
|
||||
```js
|
||||
const md = require('markdown-it')();
|
||||
const mia = require('markdown-it-attrs');
|
||||
const mrt = require('@sup39/markdown-it-raw-table');
|
||||
|
||||
// enable raw_table_tr rule
|
||||
mia.inlineAttrsApplyRules.find(e=>e.name==='raw_table_tr').disabled = false;
|
||||
|
||||
console.log(md.use(mia).use(mrt).render(`
|
||||
| h1 | h2 | h3 {.ch} |
|
||||
| -- | -- | -- |
|
||||
| x1 | x2 {.c2} | x3 {rowspan=2} | {.r1}
|
||||
| x4 {colspan=2 .c4} | {.r2}
|
||||
`));
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```html
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>h1</th>
|
||||
<th>h2</th>
|
||||
<th class="ch">h3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="r1">
|
||||
<td>x1</td>
|
||||
<td class="c2">x2</td>
|
||||
<td rowspan="2" class="c3">x3</td>
|
||||
</tr>
|
||||
<tr class="r2">
|
||||
<td colspan="2" class="c4">x4</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>h1</th>
|
||||
<th>h2</th>
|
||||
<th class="ch">h3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="r1">
|
||||
<td>x1</td>
|
||||
<td class="c2">x2</td>
|
||||
<td rowspan="2" class="c3">x3</td>
|
||||
</tr>
|
||||
<tr class="r2">
|
||||
<td colspan="2" class="c4">x4</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Note that adding attributes to `tr` of the `th` row is NOT available.
|
101
lib/index.js
Normal file
101
lib/index.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
const {parseAttrs, attrConcat} = require('./utils.js');
|
||||
const {inlineAttrsApplyRules} = require('./rules.js');
|
||||
|
||||
/**
|
||||
* @param {MarkdownIt} md
|
||||
* @param {{deliminator: string, re: RegExp}} opts
|
||||
*/
|
||||
function pluginAttr(md, opts={}) {
|
||||
const deliminator = opts.deliminator || '{';
|
||||
const deliminatorLength = deliminator.length;
|
||||
/* eslint-disable-next-line max-len */
|
||||
const reDefault = /\#(?<id>[^\s#.="'}]+)|\.(?<class>[^\s#.="'}]+)|(?<attr>[^\s#.="'}]+)(?:\=(?<val>[^\s#.="'}]+|(?<q>["']).*?\k<q>))?|}/g;
|
||||
const reRaw = opts.re;
|
||||
const re = reRaw ?
|
||||
(reRaw.sticky || reRaw.global) ? reRaw :
|
||||
new RegExp(reRaw, reRaw.flags+'g') :
|
||||
reDefault;
|
||||
|
||||
md.block.ruler.before('table', 'block_attrs', (state, l0, l1, silent) => {
|
||||
const {src, bMarks, eMarks, tShift} = state;
|
||||
// guard thisLine.sCount == nextLine.sCount
|
||||
if (state.sCount[l0] !== state.sCount[l0+1]) return false;
|
||||
// disallow contiguous two { }
|
||||
if (
|
||||
l1 > l0+2 &&
|
||||
state.sCount[l0+1] === state.sCount[l0+2] &&
|
||||
src.startsWith(deliminator, bMarks[l0+1]+tShift[l0+1])
|
||||
) return false;
|
||||
// parse
|
||||
const indexStart = bMarks[l0]+tShift[l0];
|
||||
const indexEnd = eMarks[l0];
|
||||
if (src.startsWith(deliminator, indexStart)) {
|
||||
// parse attrs
|
||||
re.lastIndex = indexStart+deliminatorLength;
|
||||
const {attrs, index} = parseAttrs(src, re) || {};
|
||||
// should meet end of line
|
||||
if (!(attrs && index === indexEnd)) return false;
|
||||
// push
|
||||
if (silent) return true;
|
||||
state.line = l0+1;
|
||||
const token = state.push('block_attr', '', 0);
|
||||
token.attrs = attrs;
|
||||
token.hidden = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, {alt: ['paragraph']});
|
||||
|
||||
md.inline.ruler.push('inline_attrs', (state, silent) => {
|
||||
const {src, pos} = state;
|
||||
if (src.startsWith(deliminator, pos)) {
|
||||
re.lastIndex = pos+deliminatorLength;
|
||||
const {attrs, index} = parseAttrs(src, re) || {};
|
||||
if (!attrs) return false;
|
||||
// set attr
|
||||
if (silent) return true;
|
||||
const token = state.push('inline_attr', '', 0);
|
||||
token.attrs = attrs;
|
||||
token.content = src.substring(pos, index);
|
||||
token.hidden = true;
|
||||
state.pos = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
md.core.ruler.after('block', 'apply_block_attrs', state => {
|
||||
state.tokens.forEach((token, i, tokens) => {
|
||||
if (token.type === 'block_attr' && token.attrs) {
|
||||
const tokenN = tokens[i+1];
|
||||
if (tokenN) {
|
||||
attrConcat(tokenN, token);
|
||||
tokens.splice(i, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
md.core.ruler.after('inline', 'apply_inline_attrs', state => {
|
||||
state.tokens.forEach((blockToken, iBlock, blockTokens) => {
|
||||
const {children} = blockToken;
|
||||
if (!children) return;
|
||||
children.forEach((token, i, tokens) => {
|
||||
if (token.type !== 'inline_attr') return;
|
||||
const matched = inlineAttrsApplyRules.some(({handler, disabled}) =>
|
||||
disabled ? false : handler(token, i, tokens, iBlock, blockTokens));
|
||||
// not seen as attrs
|
||||
if (!matched) {
|
||||
token.type = 'text';
|
||||
token.hidden = false;
|
||||
}
|
||||
});
|
||||
// remove inline_attr here to prevent index changed
|
||||
blockToken.children = children.filter(t => t.type !== 'inline_attr');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Object.assign(pluginAttr, {
|
||||
inlineAttrsApplyRules,
|
||||
});
|
78
lib/rules.js
Normal file
78
lib/rules.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const {
|
||||
findOpenToken, find, rfind, rfindIndex, attrConcat,
|
||||
} = require('./utils.js');
|
||||
|
||||
const inlineAttrsApplyRules = [
|
||||
{
|
||||
name: 'first_child',
|
||||
handler(token, i, tokens, iBlock, blockTokens) {
|
||||
if (i !== 0) return;
|
||||
const tokenO = rfind(
|
||||
blockTokens, iBlock-1, t=>!t.hidden, t=>t.nesting===1);
|
||||
if (!tokenO) return;
|
||||
// push attrs
|
||||
attrConcat(tokenO, token);
|
||||
const tokenNext = tokens[1];
|
||||
if (tokenNext && tokenNext.type==='text') {
|
||||
// trim start of next token
|
||||
tokenNext.content = tokenNext.content.replace(/^\s+/, '');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'after_inline_close',
|
||||
handler(token, i, tokens, iBlock, blockTokens) {
|
||||
const iC = rfindIndex(tokens, i-1, t=>t.type!=='text'||t.content);
|
||||
const tokenC = iC>=0 && tokens[iC];
|
||||
if (!(tokenC && tokenC.nesting === -1)) return;
|
||||
// find open
|
||||
const tokenO = findOpenToken(tokens, iC);
|
||||
if (!tokenO) return false;
|
||||
attrConcat(tokenO, token);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'last_child',
|
||||
handler(token, i, tokens, iBlock, blockTokens) {
|
||||
if (i !== tokens.length-1) return;
|
||||
const tokenC = find(
|
||||
blockTokens, iBlock+1, t=>!t.hidden, t=>t.nesting===-1);
|
||||
if (!tokenC) return;
|
||||
const {level} = tokenC;
|
||||
const tokenO = rfind(
|
||||
blockTokens, iBlock-1, t=>t.level===level, t=>t.nesting===1);
|
||||
if (!tokenO) return;
|
||||
token.attrs.forEach(attr => tokenO.attrPush(attr));
|
||||
const tokenLast = tokens[i-1];
|
||||
if (tokenLast && tokenLast.type==='text') {
|
||||
// trim end of last token
|
||||
tokenLast.content = tokenLast.content.replace(/\s+$/, '');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = {inlineAttrsApplyRules};
|
||||
|
||||
// TODO
|
||||
inlineAttrsApplyRules.unshift({
|
||||
name: 'raw_table_tr',
|
||||
disabled: true,
|
||||
handler(token, i, tokens, iB, tokenBs) {
|
||||
if (tokens.length !== 1) return;
|
||||
if (tokenBs.length <= i+2) return;
|
||||
if (!(
|
||||
tokenBs[iB+1].type === 'td_close' &&
|
||||
tokenBs[iB+2].type === 'tr_close'
|
||||
)) return;
|
||||
const tokenO = findOpenToken(tokenBs, iB+2);
|
||||
if (!tokenO) return;
|
||||
attrConcat(tokenO, token);
|
||||
tokenBs.splice(iB+1, 1); // td_close
|
||||
tokenBs.splice(iB-1, 1); // td_open
|
||||
return true;
|
||||
},
|
||||
});
|
107
lib/utils.js
Normal file
107
lib/utils.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* @param {string} s - input string
|
||||
* @param {RegExp} re - regular expression for attributes
|
||||
* @param {number} [limit=65536] - limit of attribute count
|
||||
* @return {{attr: [string, string][]}|null}
|
||||
*/
|
||||
function parseAttrs(s, re, limit=65536) {
|
||||
let count = 0;
|
||||
/** @type {string[]} */
|
||||
const classes = [];
|
||||
/** @type {[string, string][]} */
|
||||
const attrs = [];
|
||||
|
||||
let m;
|
||||
while ((m = re.exec(s)) && count<limit) {
|
||||
const g = m.groups;
|
||||
if (g.id) attrs.push(['id', g.id]);
|
||||
else if (g.class) classes.push(g.class);
|
||||
else if (g.attr) {
|
||||
const ql = g.q && g.q.length;
|
||||
const val = ql ? g.val.slice(ql, -ql) : g.val || '';
|
||||
attrs.push([g.attr, val]);
|
||||
} else {
|
||||
if (classes.length) attrs.push(['class', classes.join(' ')]);
|
||||
return {attrs, index: m.index+m[0].length};
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Token} tokens - tokens
|
||||
* @param {number} i - index of close token
|
||||
* @return {Token|null} - open token
|
||||
*/
|
||||
function findOpenToken(tokens, i) {
|
||||
const tokenC = tokens[i];
|
||||
if (!tokenC) return null;
|
||||
const {level} = tokenC;
|
||||
for (i--; i>=0; i--) {
|
||||
const token = tokens[i];
|
||||
if (token.level === level) return token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} arr
|
||||
* @param {number} startIndex
|
||||
* @param {function(any, number): boolean} test
|
||||
* @param {function(any, number): boolean} [constraint]
|
||||
* @return {any|undefined} element;
|
||||
*/
|
||||
function find(arr, startIndex, test, constraint=()=>true) {
|
||||
for (let i=startIndex, len=arr.length; i<len; i++) {
|
||||
const elm = arr[i];
|
||||
if (!constraint(elm)) return;
|
||||
if (test(elm)) return elm;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} arr
|
||||
* @param {number} startIndex
|
||||
* @param {function(any, number): boolean} test
|
||||
* @param {function(any, number): boolean} [constraint]
|
||||
* @return {any|undefined} element;
|
||||
*/
|
||||
function rfind(arr, startIndex, test, constraint=()=>true) {
|
||||
for (let i=startIndex; i>=0; i--) {
|
||||
const elm = arr[i];
|
||||
if (!constraint(elm)) return;
|
||||
if (test(elm)) return elm;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} arr
|
||||
* @param {number} startIndex
|
||||
* @param {function(any, number): boolean} test
|
||||
* @param {function(any, number): boolean} [constraint]
|
||||
* @return {number} index ?? -1;
|
||||
*/
|
||||
function rfindIndex(arr, startIndex, test, constraint=()=>true) {
|
||||
for (let i=startIndex; i>=0; i--) {
|
||||
const elm = arr[i];
|
||||
if (!constraint(elm)) return -1;
|
||||
if (test(elm)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Token} dst - Destination Token
|
||||
* @param {Token} src - Source Token
|
||||
*/
|
||||
function attrConcat(dst, src) {
|
||||
const {attrs} = src;
|
||||
if (attrs) attrs.forEach(attr => dst.attrPush(attr));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseAttrs, findOpenToken, find, rfind, rfindIndex, attrConcat,
|
||||
};
|
25
package.json
Normal file
25
package.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "@sup39/markdown-it-attr",
|
||||
"version": "1.0.0",
|
||||
"description": "A markdown-it plugin to write id, classes, and attributes",
|
||||
"keywords": [
|
||||
"markdown",
|
||||
"markdown-it",
|
||||
"markdown-it-plugin",
|
||||
"attribute"
|
||||
],
|
||||
"repository": "sup39/markdown-it-attr",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sup39/markdown-it-raw-table": "^1.0.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"markdown-it": "^12.0.4",
|
||||
"mocha": "^8.2.1"
|
||||
}
|
||||
}
|
46
test/cases/attrs.txt
Normal file
46
test/cases/attrs.txt
Normal file
|
@ -0,0 +1,46 @@
|
|||
id
|
||||
.
|
||||
# text {#iid}
|
||||
.
|
||||
<h1 id="iid">text</h1>
|
||||
.
|
||||
|
||||
|
||||
class
|
||||
.
|
||||
# text {.aa.b.c.dd .e .fff .gg}
|
||||
.
|
||||
<h1 class="aa b c dd e fff gg">text</h1>
|
||||
.
|
||||
|
||||
|
||||
non-escaped attributes
|
||||
.
|
||||
# text {a=be style=color:red;font-size:24px c=4+5}
|
||||
.
|
||||
<h1 a="be" style="color:red;font-size:24px" c="4+5">text</h1>
|
||||
.
|
||||
|
||||
|
||||
escaped attributes
|
||||
.
|
||||
# text {onload="alert('Hello World!')" onclick='alert("{#another.=.alert}")'}
|
||||
.
|
||||
<h1 onload="alert('Hello World!')" onclick="alert("{#another.=.alert}")">text</h1>
|
||||
.
|
||||
|
||||
|
||||
no need to escape backslash
|
||||
.
|
||||
# text {no-need-to-escape=\back\slash}
|
||||
.
|
||||
<h1 no-need-to-escape="\back\slash">text</h1>
|
||||
.
|
||||
|
||||
|
||||
mixed
|
||||
.
|
||||
# text {.a#b.c d="e.f".g-h i=j-k .l}
|
||||
.
|
||||
<h1 id="b" d="e.f" i="j-k" class="a c g-h l">text</h1>
|
||||
.
|
91
test/cases/common.txt
Normal file
91
test/cases/common.txt
Normal file
|
@ -0,0 +1,91 @@
|
|||
inline
|
||||
.
|
||||
1 *2*{#a} **3** **4**{#b} {#c} 6
|
||||
.
|
||||
<p>1 <em id="a">2</em> <strong>3</strong> <strong id="b">4</strong> {#c} 6</p>
|
||||
.
|
||||
|
||||
header[block]
|
||||
.
|
||||
{#a.b}
|
||||
# c d
|
||||
{e=f}
|
||||
## g h
|
||||
.
|
||||
<h1 id="a" class="b">c d</h1>
|
||||
<h2 e="f">g h</h2>
|
||||
.
|
||||
|
||||
|
||||
header[inline]
|
||||
.
|
||||
# c d {#a.b}
|
||||
## g h {e=f}
|
||||
.
|
||||
<h1 id="a" class="b">c d</h1>
|
||||
<h2 e="f">g h</h2>
|
||||
.
|
||||
|
||||
|
||||
simple list
|
||||
.
|
||||
{.a}
|
||||
- {#d} ab
|
||||
- cd
|
||||
- ef {.e}
|
||||
{.b}
|
||||
### header
|
||||
.
|
||||
<ul class="a">
|
||||
<li id="d">ab</li>
|
||||
<li>cd</li>
|
||||
<li class="e">ef</li>
|
||||
</ul>
|
||||
<h3 class="b">header</h3>
|
||||
.
|
||||
|
||||
|
||||
nested list
|
||||
.
|
||||
{#a}
|
||||
- {#b} x1
|
||||
+ y1
|
||||
+ {#c} y2
|
||||
- x2
|
||||
- x3
|
||||
{#d}
|
||||
+ {#e} z1
|
||||
+ z2
|
||||
# end
|
||||
.
|
||||
<ul id="a">
|
||||
<li id="b">x1
|
||||
<ul>
|
||||
<li>y1</li>
|
||||
<li id="c">y2</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>x2</li>
|
||||
<li>x3
|
||||
<ul id="d">
|
||||
<li id="e">z1</li>
|
||||
<li>z2</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h1>end</h1>
|
||||
.
|
||||
|
||||
|
||||
code[block]
|
||||
.
|
||||
{a.b#c.d}
|
||||
```
|
||||
code
|
||||
line2
|
||||
```
|
||||
.
|
||||
<pre><code a="" id="c" class="b d">code
|
||||
line2
|
||||
</code></pre>
|
||||
.
|
21
test/cases/escape.txt
Normal file
21
test/cases/escape.txt
Normal file
|
@ -0,0 +1,21 @@
|
|||
block
|
||||
.
|
||||
\{#a.b}
|
||||
# c d
|
||||
\{e=f}
|
||||
## g h
|
||||
.
|
||||
<p>{#a.b}</p>
|
||||
<h1>c d</h1>
|
||||
<p>{e=f}</p>
|
||||
<h2>g h</h2>
|
||||
.
|
||||
|
||||
inline
|
||||
.
|
||||
# c d \{#a.b}
|
||||
## g h \{e=f}
|
||||
.
|
||||
<h1>c d {#a.b}</h1>
|
||||
<h2>g h {e=f}</h2>
|
||||
.
|
121
test/cases/table.txt
Normal file
121
test/cases/table.txt
Normal file
|
@ -0,0 +1,121 @@
|
|||
td
|
||||
.
|
||||
| h1 | h2 | h3 |
|
||||
| -- | -- | -- |
|
||||
| c1 | c2 {.k1}| c3 |
|
||||
| d1 {#m1} | d2 | d3 {.m2 m3=m4} |
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>h1</th>
|
||||
<th>h2</th>
|
||||
<th>h3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>c1</td>
|
||||
<td class="k1">c2</td>
|
||||
<td>c3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="m1">d1</td>
|
||||
<td>d2</td>
|
||||
<td m3="m4" class="m2">d3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
.
|
||||
|
||||
|
||||
tr
|
||||
.
|
||||
| h1 | h2 | h3 |
|
||||
| -- | -- | -- |
|
||||
| c1 | c2 | c3 | {.r.s}
|
||||
| d1 | d2 | d3 | { #t u=v }
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>h1</th>
|
||||
<th>h2</th>
|
||||
<th>h3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="r s">
|
||||
<td>c1</td>
|
||||
<td>c2</td>
|
||||
<td>c3</td>
|
||||
</tr>
|
||||
<tr id="t" u="v">
|
||||
<td>d1</td>
|
||||
<td>d2</td>
|
||||
<td>d3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
.
|
||||
|
||||
|
||||
th+td+tr+table
|
||||
.
|
||||
{.bd}
|
||||
| h1{.a.b#c.d} | h2 | h3 |
|
||||
| -- | -- | -- |
|
||||
| c1 | c2 {.k1}| c3 | {#r.s}
|
||||
| d1 {#m1} | d2 | d3 {.m2 m3=m4} |
|
||||
.
|
||||
<table class="bd">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="c" class="a b d">h1</th>
|
||||
<th>h2</th>
|
||||
<th>h3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr id="r" class="s">
|
||||
<td>c1</td>
|
||||
<td class="k1">c2</td>
|
||||
<td>c3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="m1">d1</td>
|
||||
<td>d2</td>
|
||||
<td m3="m4" class="m2">d3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
.
|
||||
|
||||
|
||||
colspan & rowspan
|
||||
.
|
||||
| h1 | h2 | h3 {.ch} |
|
||||
| -- | -- | -- |
|
||||
| x1 | x2 {.c2} | x3 {rowspan=2 .c3} | {.r1}
|
||||
| x4 {colspan=2 .c4} | {.r2}
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>h1</th>
|
||||
<th>h2</th>
|
||||
<th class="ch">h3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="r1">
|
||||
<td>x1</td>
|
||||
<td class="c2">x2</td>
|
||||
<td rowspan="2" class="c3">x3</td>
|
||||
</tr>
|
||||
<tr class="r2">
|
||||
<td colspan="2" class="c4">x4</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
.
|
23
test/index.js
Normal file
23
test/index.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
const mdi = require('markdown-it');
|
||||
const mrt = require('@sup39/markdown-it-raw-table');
|
||||
const mia = require('..');
|
||||
const test = require('./test');
|
||||
|
||||
describe('Attributes', () => {
|
||||
const md = mdi().use(mia);
|
||||
test(md, 'attrs.txt');
|
||||
});
|
||||
describe('Common', () => {
|
||||
const md = mdi().use(mia);
|
||||
test(md, 'common.txt');
|
||||
});
|
||||
describe('Escape', () => {
|
||||
const md = mdi().use(mia);
|
||||
test(md, 'escape.txt');
|
||||
});
|
||||
describe('Table (with @sup39/markdown-it-raw-table)', () => {
|
||||
const md = mdi().use(mrt).use(mia);
|
||||
// enable raw_table_tr
|
||||
mia.inlineAttrsApplyRules.find(e=>e.name==='raw_table_tr').disabled = false;
|
||||
test(md, 'table.txt');
|
||||
});
|
19
test/test.js
Normal file
19
test/test.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const testCaseDir = path.join(__dirname, 'cases');
|
||||
/**
|
||||
* @param {MarkdownIt} md
|
||||
* @param {string} fileName
|
||||
*/
|
||||
module.exports = function test(md, fileName) {
|
||||
const raw =
|
||||
fs.readFileSync(path.join(testCaseDir, fileName)).toString();
|
||||
const elms = raw.split(/\n\.\n/);
|
||||
for (let i=2; i<elms.length; i+=3) {
|
||||
const title = elms[i-2].trim();
|
||||
it(title, () => {
|
||||
assert.equal(md.render(elms[i-1]), elms[i]+'\n');
|
||||
});
|
||||
}
|
||||
};
|
Reference in a new issue