Skip to content

Commit a091cc2

Browse files
author
Damir
committed
Shorthands slot names and other enhancements
1 parent 5ebaee4 commit a091cc2

File tree

3 files changed

+104
-8
lines changed

3 files changed

+104
-8
lines changed

src/index.js

+66-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ const {match} = require('posthtml/lib/api');
1010
const merge = require('deepmerge');
1111
const findPathFromTagName = require('./find-path');
1212

13+
// Used for slot without name
14+
const defaultSlotName = '___default__slot__name___';
15+
16+
const defaultSlotType = 'replace';
17+
18+
const slotTypes = {
19+
append: 'append',
20+
prepend: 'prepend'
21+
};
22+
23+
/**
24+
* Process tree's nodes
25+
* @param {Object} tree - posthtml tree
26+
* @param {Object} options - plugin options
27+
* @param {Object} messages - posthtml tree messages
28+
*/
1329
function processNodes(tree, options, messages) {
1430
tree = applyPluginsToTree(tree, options.plugins);
1531

@@ -71,6 +87,13 @@ function processNodes(tree, options, messages) {
7187
return tree;
7288
}
7389

90+
/**
91+
* Parse locals from attributes, globals and via script
92+
* @param {Object} tree - posthtml tree
93+
* @param {Object} options - plugin options
94+
* @param {Object} html - posthtml tree
95+
* @return {Object} merged locals and default
96+
*/
7497
function parseLocals(options, {attrs}, html) {
7598
let attributes = {...attrs};
7699

@@ -96,6 +119,14 @@ function parseLocals(options, {attrs}, html) {
96119
return {attributes, defaultLocals};
97120
}
98121

122+
/**
123+
* Merge slots content
124+
* @param {Object} tree
125+
* @param {Object} component
126+
* @param {Boolean} strict
127+
* @param {String} slotTagName
128+
* @return {Object} tree
129+
*/
99130
function mergeSlots(tree, component, strict, slotTagName) {
100131
const slots = getSlots(slotTagName, tree); // Slot in component.html
101132
const fillSlots = getSlots(slotTagName, component.content); // Slot in page.html
@@ -114,9 +145,8 @@ function mergeSlots(tree, component, strict, slotTagName) {
114145
continue;
115146
}
116147

117-
const slotType = ['replace', 'prepend', 'append'].includes(((slotNode.attrs && slotNode.attrs.type) || '').toLowerCase()) ?
118-
((slotNode.attrs && slotNode.attrs.type) || '').toLowerCase() :
119-
'replace';
148+
let slotType = slotTypes[(slotNode.attrs.type || defaultSlotType).toLowerCase()] || defaultSlotType;
149+
slotType = typeof slotNode.attrs.append === 'undefined' ? (typeof slotNode.attrs.prepend === 'undefined' ? slotType : slotTypes.prepend) : slotTypes.append;
120150

121151
const layoutBlockNodeList = slots[slotName];
122152
for (const layoutBlockNode of layoutBlockNodeList) {
@@ -145,25 +175,48 @@ function mergeSlots(tree, component, strict, slotTagName) {
145175
return tree;
146176
}
147177

178+
/**
179+
* Prepend, append or replace slot content
180+
* @param {Object} slotContent
181+
* @param {Object} defaultContent
182+
* @param {String} slotType
183+
* @return {Object}
184+
*/
148185
function mergeContent(slotContent, defaultContent, slotType) {
149186
slotContent = slotContent || [];
150187
defaultContent = defaultContent || [];
151188

152-
if (!['append', 'prepend'].includes(slotType)) {
189+
// Replace
190+
if (!Object.keys(slotTypes).includes(slotType)) {
153191
return slotContent;
154192
}
155193

156-
return slotType === 'prepend' ?
194+
// Prepend or append
195+
return slotType === slotTypes.prepend ?
157196
slotContent.concat(defaultContent) :
158197
defaultContent.concat(slotContent);
159198
}
160199

200+
/**
201+
* Get all slots from content
202+
* @param {String} tag
203+
* @param {Object} content
204+
* @return {Object}
205+
*/
161206
function getSlots(tag, content = []) {
162207
const slots = {};
163208

164209
match.call(content, {tag}, node => {
165-
if (!node.attrs || !node.attrs.name) {
166-
throw new Error('[components] Missing slot name');
210+
if (!node.attrs) {
211+
node.attrs = {};
212+
}
213+
214+
if (!node.attrs.name) {
215+
node.attrs.name = Object.keys({...node.attrs}).find(name => !Object.keys(slotTypes).includes(name) && name !== 'type');
216+
}
217+
218+
if (!node.attrs.name) {
219+
node.attrs.name = defaultSlotName;
167220
}
168221

169222
const {name} = node.attrs;
@@ -180,6 +233,12 @@ function getSlots(tag, content = []) {
180233
return slots;
181234
}
182235

236+
/**
237+
* Apply plugins to tree
238+
* @param {Object} tree
239+
* @param {Array} plugins
240+
* @return {Object}
241+
*/
183242
function applyPluginsToTree(tree, plugins) {
184243
return plugins.reduce((tree, plugin) => {
185244
tree = plugin(tree);
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<html><head><title>Default Slot Layout</title></head><body><main><slot></slot></main><footer><slot name="footer">footer content</slot></footer></body></html>

test/test-slots.js

+37-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const plugin = require('../src');
55
const posthtml = require('posthtml');
66
const clean = html => html.replace(/(\n|\t)/g, '').trim();
77

8-
test('Must process layout with slots', async t => {
8+
test('Must process with slots', async t => {
99
const actual = `<component src="layouts/base.html"><slot name="content">Content</slot><slot name="footer">Footer</slot></component>`;
1010
const expected = `<html><head><title>Base Layout</title></head><body><main>Content</main><footer>Footer</footer></body></html>`;
1111

@@ -40,3 +40,39 @@ test('Must process append and prepend content to slot', async t => {
4040

4141
t.is(html, expected);
4242
});
43+
44+
test('Must process with slots using shorthands names syntax', async t => {
45+
const actual = `<component src="layouts/base.html"><slot content>Content</slot><slot footer>Footer</slot></component>`;
46+
const expected = `<html><head><title>Base Layout</title></head><body><main>Content</main><footer>Footer</footer></body></html>`;
47+
48+
const html = await posthtml([plugin({root: './test/templates'})]).process(actual).then(result => clean(result.html));
49+
50+
t.is(html, expected);
51+
});
52+
53+
test('Must process append and prepend using shorthands types syntax', async t => {
54+
const actual = `<component src="components/component-append-prepend.html"><slot name="title" append> Append</slot><slot name="body" prepend>Prepend </slot></component>`;
55+
const expected = `<div>Title Append</div><div>Prepend Body</div>`;
56+
57+
const html = await posthtml([plugin({root: './test/templates'})]).process(actual).then(result => clean(result.html));
58+
59+
t.is(html, expected);
60+
});
61+
62+
test('Must process with slots using shorthands names and types syntax together', async t => {
63+
const actual = `<component src="components/component-append-prepend.html"><slot title append> Append</slot><slot body prepend>Prepend </slot></component>`;
64+
const expected = `<div>Title Append</div><div>Prepend Body</div>`;
65+
66+
const html = await posthtml([plugin({root: './test/templates'})]).process(actual).then(result => clean(result.html));
67+
68+
t.is(html, expected);
69+
});
70+
71+
test('Must process with default slot', async t => {
72+
const actual = `<component src="layouts/default-slot.html"><slot>Default Slot Content</slot><slot footer>Footer</slot></component>`;
73+
const expected = `<html><head><title>Default Slot Layout</title></head><body><main>Default Slot Content</main><footer>Footer</footer></body></html>`;
74+
75+
const html = await posthtml([plugin({root: './test/templates'})]).process(actual).then(result => clean(result.html));
76+
77+
t.is(html, expected);
78+
});

0 commit comments

Comments
 (0)