This commit is contained in:
Stuart Colville 2020-07-24 13:50:12 +01:00
Коммит 7afdbfe061
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 91839D842F658BCF
6 изменённых файлов: 4014 добавлений и 0 удалений

4
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
node_modules
.DS_Store
coverage

22
README.md Normal file
Просмотреть файл

@ -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>',
},
});
```

96
index.js Normal file
Просмотреть файл

@ -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;

310
index.test.js Normal file
Просмотреть файл

@ -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);
});
});

17
package.json Normal file
Просмотреть файл

@ -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"
}
}

3565
yarn.lock Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу