Initial commit
This commit is contained in:
Коммит
7afdbfe061
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
|
||||
coverage
|
|
@ -0,0 +1,22 @@
|
|||
# markdown-it-heading-wrapper
|
||||
|
||||
A plugin very much inspired by and based on [markdown-it-header-sections](https://github.com/arve0/markdown-it-header-sections),
|
||||
albeit in this case it's designed to allow for arbitrary markup wrappers
|
||||
rather than just a single `section`.
|
||||
|
||||
The idea behind this is to allow a document author to focus on pure
|
||||
documentation and to have the renderer add boilerplate markup to affect a
|
||||
specific layout.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
markdown = md({
|
||||
html: true,
|
||||
}).use(require('markdown-it-heading-wrapper'), {
|
||||
h1: {
|
||||
before: '<section>',
|
||||
after: '</section>',
|
||||
},
|
||||
});
|
||||
```
|
|
@ -0,0 +1,96 @@
|
|||
function headingWrapperPlugin (md, options) {
|
||||
const opts = Object.assign({}, options);
|
||||
|
||||
function lastItem(arr) {
|
||||
return arr[arr.length-1];
|
||||
}
|
||||
|
||||
function headingWrapper (state){
|
||||
const tokens = [];
|
||||
const sections = [];
|
||||
let token;
|
||||
let Token = state.Token;
|
||||
let nestingLevel = 0;
|
||||
|
||||
function getWrapperOpen(headerTag) {
|
||||
const token = new Token('heading_wrapper_open', '', 1);
|
||||
token.heading_wrapper_relation = headerTag;
|
||||
return token
|
||||
}
|
||||
|
||||
function getWrapperClose(headerTag) {
|
||||
const token = new Token('heading_wrapper_close', '', -1);
|
||||
token.heading_wrapper_relation = headerTag;
|
||||
return token
|
||||
}
|
||||
|
||||
function closeOpenWrappersToNesting(nestingLevel) {
|
||||
while (sections.length && nestingLevel < lastItem(sections).nestingLevel) {
|
||||
const poppedSection = sections.pop();
|
||||
if (opts[poppedSection.headerTag]) {
|
||||
tokens.push(getWrapperClose(poppedSection.headerTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeOpenWrappers(section) {
|
||||
while (sections.length && section.headerTag.charAt(1) <= lastItem(sections).headerTag.charAt(1)) {
|
||||
const poppedSection = sections.pop();
|
||||
if (opts[poppedSection.headerTag]) {
|
||||
tokens.push(getWrapperClose(poppedSection.headerTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeAllOpenWrappers(section) {
|
||||
let poppedSection;
|
||||
while (poppedSection = sections.pop()) {
|
||||
if (opts[poppedSection.headerTag]) {
|
||||
tokens.push(getWrapperClose(poppedSection.headerTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, l = state.tokens.length; i < l; i++) {
|
||||
token = state.tokens[i];
|
||||
|
||||
if (token.type.indexOf('heading') !== 0) {
|
||||
nestingLevel += token.nesting;
|
||||
}
|
||||
|
||||
closeOpenWrappersToNesting(nestingLevel);
|
||||
|
||||
if (token.type === 'heading_open'){
|
||||
const section = {
|
||||
headerTag: token.tag,
|
||||
nestingLevel: nestingLevel,
|
||||
};
|
||||
closeOpenWrappers(section);
|
||||
if (opts[token.tag]) {
|
||||
tokens.push(getWrapperOpen(token.tag));
|
||||
sections.push(section);
|
||||
}
|
||||
}
|
||||
tokens.push(token);
|
||||
}
|
||||
|
||||
// Close all open wrappers from h6 -> h1.
|
||||
closeAllOpenWrappers();
|
||||
|
||||
state.tokens = tokens;
|
||||
}
|
||||
|
||||
md.renderer.rules.heading_wrapper_open = function (tokens, idx, options, env, self) {
|
||||
const token = tokens[idx];
|
||||
return `${opts[token.heading_wrapper_relation].before}\n`;
|
||||
};
|
||||
|
||||
md.renderer.rules.heading_wrapper_close = function (tokens, idx, options, env, self) {
|
||||
const token = tokens[idx];
|
||||
return `${opts[token.heading_wrapper_relation].after}\n`;
|
||||
};
|
||||
|
||||
md.core.ruler.push('heading_wrapper', headingWrapper);
|
||||
}
|
||||
|
||||
module.exports = headingWrapperPlugin;
|
|
@ -0,0 +1,310 @@
|
|||
const { stripIndent } = require('common-tags');
|
||||
const md = require("markdown-it");
|
||||
|
||||
let markdown;
|
||||
|
||||
describe('Full config', () => {
|
||||
beforeAll(() => {
|
||||
markdown = md({
|
||||
html: true,
|
||||
}).use(require('./index.js'), {
|
||||
h1: {
|
||||
before: '<section><!-- open h1 section -->',
|
||||
after: '</section><!-- close h1 section -->',
|
||||
},
|
||||
h2: {
|
||||
before: '<section><!-- open h2 section -->',
|
||||
after: '</section><!-- close h2 section -->',
|
||||
},
|
||||
h3: {
|
||||
before: '<section><!-- open h3 section -->',
|
||||
after: '</section><!-- close h3 section -->',
|
||||
},
|
||||
h4: {
|
||||
before: '<section><!-- open h4 section -->',
|
||||
after: '</section><!-- close h4 section -->',
|
||||
},
|
||||
h5: {
|
||||
before: '<section><!-- open h5 section -->',
|
||||
after: '</section><!-- close h5 section -->',
|
||||
},
|
||||
h5: {
|
||||
before: '<section><!-- open h6 section -->',
|
||||
after: '</section><!-- close h6 section -->',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('wraps headings', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
# Heading level 1
|
||||
|
||||
This is some markup
|
||||
|
||||
## Heading level 2
|
||||
|
||||
This is some more markup
|
||||
|
||||
### Heading level 3
|
||||
|
||||
Something else
|
||||
|
||||
## Heading level 2
|
||||
|
||||
Some more content`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<section><!-- open h1 section -->
|
||||
<h1>Heading level 1</h1>
|
||||
<p>This is some markup</p>
|
||||
<section><!-- open h2 section -->
|
||||
<h2>Heading level 2</h2>
|
||||
<p>This is some more markup</p>
|
||||
<section><!-- open h3 section -->
|
||||
<h3>Heading level 3</h3>
|
||||
<p>Something else</p>
|
||||
</section><!-- close h3 section -->
|
||||
</section><!-- close h2 section -->
|
||||
<section><!-- open h2 section -->
|
||||
<h2>Heading level 2</h2>
|
||||
<p>Some more content</p>
|
||||
</section><!-- close h2 section -->
|
||||
</section><!-- close h1 section -->`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
|
||||
it('handles bad heading order', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
## Heading level 2
|
||||
|
||||
This is some markup
|
||||
|
||||
# Heading level 1
|
||||
|
||||
This is some more markup`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<section><!-- open h2 section -->
|
||||
<h2>Heading level 2</h2>
|
||||
<p>This is some markup</p>
|
||||
</section><!-- close h2 section -->
|
||||
<section><!-- open h1 section -->
|
||||
<h1>Heading level 1</h1>
|
||||
<p>This is some more markup</p>
|
||||
</section><!-- close h1 section -->`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
|
||||
it('handles nesting', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
* List item 1
|
||||
## Heading level 2
|
||||
* List item 2
|
||||
### Heading level 3
|
||||
Some text`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<ul>
|
||||
<li>List item 1<section><!-- open h2 section -->
|
||||
<h2>Heading level 2</h2>
|
||||
</section><!-- close h2 section -->
|
||||
</li>
|
||||
<li>List item 2<section><!-- open h3 section -->
|
||||
<h3>Heading level 3</h3>
|
||||
Some text</section><!-- close h3 section -->
|
||||
</li>
|
||||
</ul>`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
|
||||
it('handles headings in block quotes', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
> This is a block quote
|
||||
> #### This is a heading
|
||||
> This is some more text`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<blockquote>
|
||||
<p>This is a block quote</p>
|
||||
<section><!-- open h4 section -->
|
||||
<h4>This is a heading</h4>
|
||||
<p>This is some more text</p>
|
||||
</section><!-- close h4 section -->
|
||||
</blockquote>`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Partial config', () => {
|
||||
beforeAll(() => {
|
||||
markdown = md({
|
||||
html: true,
|
||||
}).use(require('./index.js'), {
|
||||
h2: {
|
||||
before: '<section><!-- open h2 section -->',
|
||||
after: '</section><!-- close h2 section -->',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('wraps headings', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
# Heading level 1
|
||||
|
||||
This is some markup
|
||||
|
||||
## Heading level 2
|
||||
|
||||
This is some more markup
|
||||
|
||||
### Heading level 3
|
||||
|
||||
Something else
|
||||
|
||||
## Heading level 2
|
||||
|
||||
Some more content`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<h1>Heading level 1</h1>
|
||||
<p>This is some markup</p>
|
||||
<section><!-- open h2 section -->
|
||||
<h2>Heading level 2</h2>
|
||||
<p>This is some more markup</p>
|
||||
<h3>Heading level 3</h3>
|
||||
<p>Something else</p>
|
||||
</section><!-- close h2 section -->
|
||||
<section><!-- open h2 section -->
|
||||
<h2>Heading level 2</h2>
|
||||
<p>Some more content</p>
|
||||
</section><!-- close h2 section -->`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
|
||||
it('handles bad heading order only for configured headings', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
## Heading level 2
|
||||
|
||||
This is some markup
|
||||
|
||||
# Heading level 1
|
||||
|
||||
This is some more markup`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<section><!-- open h2 section -->
|
||||
<h2>Heading level 2</h2>
|
||||
<p>This is some markup</p>
|
||||
</section><!-- close h2 section -->
|
||||
<h1>Heading level 1</h1>
|
||||
<p>This is some more markup</p>`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
|
||||
it('handles nesting only for configured headings', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
* List item 1
|
||||
## Heading level 2
|
||||
* List item 2
|
||||
### Heading level 3
|
||||
Some text`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<ul>
|
||||
<li>List item 1<section><!-- open h2 section -->
|
||||
<h2>Heading level 2</h2>
|
||||
</section><!-- close h2 section -->
|
||||
</li>
|
||||
<li>List item 2
|
||||
<h3>Heading level 3</h3>
|
||||
Some text</li>
|
||||
</ul>`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
|
||||
it('handles headings only configured headings in block quotes', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
> This is a block quote
|
||||
> ## This is a heading
|
||||
> This is some more text
|
||||
> ### This is another heading`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<blockquote>
|
||||
<p>This is a block quote</p>
|
||||
<section><!-- open h2 section -->
|
||||
<h2>This is a heading</h2>
|
||||
<p>This is some more text</p>
|
||||
<h3>This is another heading</h3>
|
||||
</section><!-- close h2 section -->
|
||||
</blockquote>`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Other plugins', () => {
|
||||
beforeAll(() => {
|
||||
markdown = md({
|
||||
html: true,
|
||||
})
|
||||
.use(require('markdown-it-anchor'))
|
||||
.use(require('./index.js'), {
|
||||
h2: {
|
||||
before: '<section><!-- open h2 section -->',
|
||||
after: '</section><!-- close h2 section -->',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('wraps headings without causing problems with other plugins', () => {
|
||||
const sampleMarkdown = stripIndent`
|
||||
# Heading level 1
|
||||
|
||||
This is some markup
|
||||
|
||||
## Heading level 2
|
||||
|
||||
This is some more markup
|
||||
|
||||
### Heading level 3
|
||||
|
||||
Something else
|
||||
|
||||
## Heading level 2
|
||||
|
||||
Some more content`;
|
||||
|
||||
const expected = stripIndent`
|
||||
<h1 id="heading-level-1">Heading level 1</h1>
|
||||
<p>This is some markup</p>
|
||||
<section><!-- open h2 section -->
|
||||
<h2 id="heading-level-2">Heading level 2</h2>
|
||||
<p>This is some more markup</p>
|
||||
<h3 id="heading-level-3">Heading level 3</h3>
|
||||
<p>Something else</p>
|
||||
</section><!-- close h2 section -->
|
||||
<section><!-- open h2 section -->
|
||||
<h2 id="heading-level-2-2">Heading level 2</h2>
|
||||
<p>Some more content</p>
|
||||
</section><!-- close h2 section -->`;
|
||||
|
||||
const rendered = markdown.render(sampleMarkdown).trim();
|
||||
expect(rendered).toEqual(expected);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "markdown-it-heading-wrapper",
|
||||
"version": "0.0.1",
|
||||
"description": "A markdown-it plugin for wrapping headings in markdown in arbitrary markup.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"common-tags": "^1.8.0",
|
||||
"jest": "^26.1.0",
|
||||
"markdown-it": "^11.0.0",
|
||||
"markdown-it-anchor": "^5.3.0"
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче