Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: write out reusable content as tags #827

Merged
merged 2 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions __tests__/flavored-compilers/reusable-content.test.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
import { mdast, md } from '../../index';

describe('reusable content compiler', () => {
it('writes an undefined reusable content block back to markdown', () => {
it('writes an undefined reusable content block as a tag', () => {
const doc = '<Undefined />';
const tree = mdast(doc);

expect(md(tree)).toMatch(doc);
});

it('writes a defined reusable content block back to markdown', () => {
const reusableContent = {
it('writes a defined reusable content block as a tag', () => {
const tags = {
Defined: '# Whoa',
};
const doc = '<Defined />';
const tree = mdast(doc, { reusableContent });
const tree = mdast(doc, { reusableContent: { tags } });

expect(tree.children[0].children[0].type).toBe('heading');
expect(md(tree)).toMatch(doc);
});

it('writes a defined reusable content block with multiple words back to markdown', () => {
const reusableContent = {
it('writes a defined reusable content block with multiple words as a tag', () => {
const tags = {
MyCustomComponent: '# Whoa',
};
const doc = '<MyCustomComponent />';
const tree = mdast(doc, { reusableContent });
const tree = mdast(doc, { reusableContent: { tags } });

expect(tree.children[0].children[0].type).toBe('heading');
expect(md(tree)).toMatch(doc);
});

describe('writeTags = false', () => {
it('writes a reusable content block as content', () => {
const tags = {
Defined: '# Whoa',
};
const doc = '<Defined />';
const string = md(doc, { reusableContent: { tags, writeTags: false } });

expect(string).toBe('# Whoa\n');
});
});
});
7 changes: 5 additions & 2 deletions __tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ test('it should have the proper utils exports', () => {
compatibilityMode: false,
copyButtons: true,
correctnewlines: false,
disableReusableContent: false,
lazyImages: true,
markdownOptions: {
fences: true,
Expand All @@ -28,7 +27,11 @@ test('it should have the proper utils exports', () => {
paddedTable: true,
},
normalize: true,
reusableContent: {},
reusableContent: {
disabled: false,
tags: {},
writeTags: true,
},
safeMode: false,
settings: { position: false },
theme: 'light',
Expand Down
16 changes: 8 additions & 8 deletions __tests__/transformers/reusable-content.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { mdast } from '../../index';

describe('reusable content transfomer', () => {
it('should replace a reusable content block if the block is provided', () => {
const reusableContent = {
const tags = {
Test: `
# Test

Expand All @@ -17,15 +17,15 @@ Before
After
`;

const tree = mdast(md, { reusableContent });
const tree = mdast(md, { reusableContent: { tags } });

expect(tree.children[0].children[0].value).toBe('Before');
expect(tree.children[1]).toMatchSnapshot();
expect(tree.children[2].children[0].value).toBe('After');
});

it('should replace a reusable content block with multiple words if the block is provided', () => {
const reusableContent = {
const tags = {
MyCustomComponent: `
# Test

Expand All @@ -34,7 +34,7 @@ After
};
const md = '<MyCustomComponent />';

const tree = mdast(md, { reusableContent });
const tree = mdast(md, { reusableContent: { tags } });

expect(tree.children[0]).toMatchSnapshot();
});
Expand All @@ -48,22 +48,22 @@ After
});

it('does not expand reusable content recursively', () => {
const reusableContent = {
const tags = {
Test: '<Test />',
};
const md = '<Test />';
const tree = mdast(md, { reusableContent });
const tree = mdast(md, { reusableContent: { tags } });

expect(tree.children[0].children[0].type).toBe('reusable-content');
expect(tree.children[0].children[0].children).toStrictEqual([]);
});

it('does not replace reusable content if it is disabled', () => {
const reusableContent = {
const tags = {
Test: '<Test />',
};
const md = '<Test />';
const tree = mdast(md, { disableReusableContent: true, reusableContent });
const tree = mdast(md, { reusableContent: { tags, disabled: true } });

expect(tree.children[0].type).toBe('html');
expect(tree.children[0].value).toBe('<Test />');
Expand Down
12 changes: 7 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ export const utils = {
* blocks recursively.
*/
const parseReusableContent = ({ reusableContent, ...opts }) => {
if (opts.disableReusableContent) return [null, opts];
if (reusableContent.disabled) return [{ disabled: true, writeTags: true }, opts];

const parsedReusableContent = Object.entries(reusableContent).reduce((memo, [name, content]) => {
const tags = Object.entries(reusableContent.tags).reduce((memo, [name, content]) => {
// eslint-disable-next-line no-use-before-define
memo[name] = mdast(content, opts).children;
return memo;
}, {});

return [parsedReusableContent, opts];
return [{ ...reusableContent, tags }, opts];
};

/**
Expand Down Expand Up @@ -290,10 +290,12 @@ export function astToPlainText(node, opts = {}) {
/**
* compile mdast to ReadMe-flavored markdown
*/
export function md(tree, opts = {}) {
if (!tree) return null;
export function md(treeOrString, opts = {}) {
if (!treeOrString) return null;
[, opts] = setup('', opts);

const tree = typeof treeOrString === 'string' ? mdast(treeOrString, opts) : treeOrString;

return processor(opts).use(remarkStringify, opts.markdownOptions).use(customCompilers).stringify(tree);
}

Expand Down
20 changes: 16 additions & 4 deletions options.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const options = {
compatibilityMode: false,
copyButtons: true,
correctnewlines: false,
disableReusableContent: false,
markdownOptions: {
fences: true,
commonmark: true,
Expand All @@ -15,7 +14,11 @@ const options = {
},
normalize: true,
lazyImages: true,
reusableContent: {},
reusableContent: {
tags: {},
disabled: false,
writeTags: true,
},
safeMode: false,
settings: {
position: false,
Expand Down Expand Up @@ -77,8 +80,17 @@ const disableTokenizers = {
},
};

const parseOptions = (userOpts = {}) => {
let opts = { ...options, ...userOpts };
const parseOptions = ({ markdownOptions = {}, reusableContent = {}, settings = {}, ...userOpts } = {}) => {
let opts = {
...options,
markdownOptions: { ...options.markdownOptions, ...markdownOptions },
reusableContent: {
...options.reusableContent,
...reusableContent,
},
settings: { ...options.settings, ...settings },
...userOpts,
};

if (opts.disableTokenizers in disableTokenizers) {
opts = { ...opts, ...disableTokenizers[opts.disableTokenizers] };
Expand Down
5 changes: 4 additions & 1 deletion processor/compile/reusable-content.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { type } from '../transform/reusable-content';

export default function ReusableContentCompiler() {
const { writeTags = true } = this.data('reusableContent') || {};
const { Compiler } = this;
const { visitors } = Compiler.prototype;

visitors[type] = node => `<${node.tag} />`;
visitors[type] = function (node) {
return writeTags ? `<${node.tag} />` : this.block(node);
};
}
6 changes: 3 additions & 3 deletions processor/transform/reusable-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export const type = 'reusable-content';
const regexp = /^\s*<(?<tag>[A-Z]\S+)\s*\/>\s*$/;

const reusableContentTransformer = function () {
const reusableContent = this.data('reusableContent');
if (!reusableContent) return () => undefined;
const { tags, disabled } = this.data('reusableContent');
if (disabled) return () => undefined;

return tree => {
visit(tree, 'html', (node, index, parent) => {
Expand All @@ -18,7 +18,7 @@ const reusableContentTransformer = function () {
const block = {
type,
tag,
children: tag in reusableContent ? reusableContent[tag] : [],
children: tag in tags ? tags[tag] : [],
};

parent.children[index] = block;
Expand Down
Loading