Skip to content

Commit

Permalink
chore: formatting & linting
Browse files Browse the repository at this point in the history
  • Loading branch information
webketje committed Feb 7, 2024
1 parent bbc79f9 commit 1dadc34
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 82 deletions.
94 changes: 53 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @metalsmith/slots

A Metalsmith plugin to divide file contents into slots, associate metadata with them and process them separately
A Metalsmith plugin to divide file contents into slots, associate metadata with them and process them separately

[![metalsmith: core plugin][metalsmith-badge]][metalsmith-url]
[![npm: version][npm-badge]][npm-url]
Expand Down Expand Up @@ -30,34 +30,38 @@ Pass `@metalsmith/slots` to `metalsmith.use`:
import slots from '@metalsmith/slots'

metalsmith.use(slots()) // defaults
metalsmith.use(slots({ // explicit defaults
pattern: '**/*.{md,html}'
}))
metalsmith.use(
slots({
// explicit defaults
pattern: '**/*.{md,html}'
})
)
```

Now you can divide your file in parameterized logical content sections or *slots*, with their own front-matter blocks.
Now you can divide your file in parameterized logical content sections or _slots_, with their own front-matter blocks.
Just define the `slot` field for each. You can associate any metadata with the slots just like file front-matter.

```yaml
---
layout: default.njk
title: This becomes file.title
---
This becomes file.contents

--- # first slot (becomes file.slots.author)
slot: author
name: John Doe
topics: [sports,finance]
topics: [sports, finance]
---
This becomes file.slots.author.contents

--- # second slot (becomes file.slots.ads)
slot: ads
url: https://someadprovider.com/?id=abcde1234
---
<!-- end of file -->
```

@metalsmith/slots then parses the file, removing the slots content from the main `file.contents` field and adding them to `file.slots`:

```js
const file = {
layout: 'default.njk',
Expand All @@ -68,7 +72,7 @@ const file = {
slot: 'author',
name: 'John Doe',
contents: 'This becomes file.slots.author.contents',
topics: ['sports','finance']
topics: ['sports', 'finance']
},
ads: {
slot: 'ads',
Expand All @@ -78,14 +82,15 @@ const file = {
}
}
```

If the file already has an existing `slots` property holding an object, the slots will be shallowly merged in with `Object.assign`.
If the file already has an existing property with another type, it will be overwritten and log a debug warning.

There is one limitation: you cannot \*interrupt\* the main content with a slot and then continue it. Because front-matter is parsed without an explicit "end" boundary, slots must always be defined at the end of the file.

### Defining default slots

You can define a *slots* property in [metalsmith.metadata()](https://metalsmith.io/api/#Metalsmith+metadata):
You can define a _slots_ property in [metalsmith.metadata()](https://metalsmith.io/api/#Metalsmith+metadata):

```js
metalsmith.metadata({
Expand All @@ -98,36 +103,38 @@ metalsmith.metadata({
}
})
```

This property can then be used by plugins like [@metalsmith/layouts](https://github.com/metalsmith/layouts) that merge file metadata into global metadata as rendering context.

If you rather really *set* the defaults to the files so other plugins can access it, you can use [@metalsmith/default-values](https://github.com/metalsmith/default-values)
If you rather really _set_ the defaults to the files so other plugins can access it, you can use [@metalsmith/default-values](https://github.com/metalsmith/default-values)

### Rendering slots in a layout

With the previous examples, [@metalsmith/layouts](https://github.com/metalsmith/layouts) can render slots in a layout, using slots defined inline in a file, or fall back to metalsmith.metadata:

```html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<article>
<aside>
<iframe src="{{ slots.ads.url }}"></iframe>
</aside>
{{ contents | safe }}
<footer>
<h3>By {{ slots.author.name }}</h3>
Writes about {{ slots.author.topics | join(', ') }}
<p> {{ slots.author.contents }}</p>
</footer>
</article>
</body>
<head>
<meta charset="UTF-8" />
<title>{{ title }}</title>
</head>
<body>
<article>
<aside>
<iframe src="{{ slots.ads.url }}"></iframe>
</aside>
{{ contents | safe }}
<footer>
<h3>By {{ slots.author.name }}</h3>
Writes about {{ slots.author.topics | join(', ') }}
<p>{{ slots.author.contents }}</p>
</footer>
</article>
</body>
</html>
```

Note that you can also use `{{ slots.slotname }}` as an alias for `{{ slots.slotname.contents }}` in templating languages that `toString()` the values they output.
Note that you can also use `{{ slots.slotname }}` as an alias for `{{ slots.slotname.contents }}` in templating languages that `toString()` the values they output.

It is not (yet) possible to render slots into their own layouts by defining a slot `layout` field.

Expand All @@ -136,37 +143,39 @@ It is not (yet) possible to render slots into their own layouts by defining a sl
It is easy to render markdown in slots with [@metalsmith/markdown](https://github.com/metalsmith/markdown)'s `keys` and `wildcard` options to target slot contents of all files:

```js
metalsmith.use(markdown({
wildcard: true,
keys: ['slots.*.contents']
}))
metalsmith.use(
markdown({
wildcard: true,
keys: ['slots.*.contents']
})
)
```

### Rendering slots in file.contents

[@metalsmith/in-place](https://github.com/metalsmith/in-place) can be used to render slots inside the file.contents.

`index.md`

```yaml
---
layout: default.njk
title: This becomes file.title
---

<h1>{{ title }}</h1>
This becomes file.contents

By {{ slots.author.name }}.
Writes mostly about {{ slots.author.topics | join(', ') }}
<hr>{{ slots.author.contents | safe }}

---
slot: author
name: John Doe
topics: [sports,finance]
topics: [sports, finance]
---
This becomes file.slots.author.contents.
```

### Combining plugins

An example of using all of @metalsmith layouts, in-place, markdown, default-values and slots in a common order in a metalsmith build:
Expand All @@ -184,10 +193,14 @@ metalsmith
}
})
// default slots by file pattern, eg no author for homepage
.use(defaultValues([{
pattern: 'home.md',
defaults: { slots: (file) => ({ ...(file.slots || {}), author: false }) }
}]))
.use(
defaultValues([
{
pattern: 'home.md',
defaults: { slots: (file) => ({ ...(file.slots || {}), author: false }) }
}
])
)
.use(slots({ pattern: '**/*.md' }))
// render markdown inside slots
.use(markdown({ wildcard: true, keys: ['slots.*.contents'] }))
Expand All @@ -197,7 +210,6 @@ metalsmith
.use(layouts({ pattern: '**/*.html' }))
```


### Debug

To enable debug logs, set the `DEBUG` environment variable to `@metalsmith/slots*`:
Expand Down
36 changes: 18 additions & 18 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ function normalizeOptions(options = {}) {
return Object.assign({}, defaults, options)
}

/**
* @param {string} contents
* @param {import('metalsmith').Metalsmith} matter
*/
/**
* @param {string} contents
* @param {import('metalsmith').Metalsmith} matter
*/
function parseSlots(contents, startDelimiter, matter) {
function recurse(raw, slots = []) {
raw = raw.toString().trim()
Expand All @@ -47,8 +47,6 @@ function parseSlots(contents, startDelimiter, matter) {
slots.push(slot)
slots = recurse(slot.contents, slots)
}



return slots
}
Expand Down Expand Up @@ -79,33 +77,35 @@ function slots(options = {}) {
debug('Processing %s file(s)', matches.length)
debug.info('Matched files: %O', matches)

matches.forEach(filepath => {
matches.forEach((filepath) => {
const file = files[filepath]
let slots = parseSlots(file.contents, startDelimiter, metalsmith.matter)

slots = slots
.reduce((remapped, slot, index) => {
if (index === 0 && slot.name === 'contents') {
file.contents = Buffer.from(slot.contents)
return remapped
}
remapped[slot.name] = slot
Object.defineProperty(slot, 'toString', { value: slotToString, enumerable: false, writable: false })
slots = slots.reduce((remapped, slot, index) => {
if (index === 0 && slot.name === 'contents') {
file.contents = Buffer.from(slot.contents)
return remapped
}, {})
}
remapped[slot.name] = slot
Object.defineProperty(slot, 'toString', { value: slotToString, enumerable: false, writable: false })
return remapped
}, {})

// don't assign to arrays, it could cause confusion
const isArray = Array.isArray(file.slots)
if (typeof file.slots === 'object' && !isArray && file.slots !== null) {
Object.assign(file.slots, slots)
} else {
if (file.slots) {
debug.warn('Overwriting incompatible slots property of type "%s" in file "%s"', isArray ? 'array' : typeof file.slots, metalsmith.path(filepath))
debug.warn(
'Overwriting incompatible slots property of type "%s" in file "%s"',
isArray ? 'array' : typeof file.slots,
metalsmith.path(filepath)
)
}
file.slots = slots
}
})

}
}
export default slots
Loading

0 comments on commit 1dadc34

Please sign in to comment.