poops 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -8
- package/lib/markup/engines/liquid.js +11 -3
- package/lib/markup/engines/nunjucks.js +13 -4
- package/lib/markup/helpers.js +39 -14
- package/lib/markup/highlight.js +2 -2
- package/lib/markup/renderer.js +8 -5
- package/lib/markups.js +4 -0
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -797,11 +797,25 @@ Output:
|
|
|
797
797
|
- Defaults: `sizes="100vw"`, `loading="lazy"`
|
|
798
798
|
- Falls back to a plain `<img src="...">` if no variants are found
|
|
799
799
|
|
|
800
|
+
**Named crops with `size`** — pass a `size` kwarg to build the `<img>` from a named crop/resize group instead of the default responsive widths. The whole group becomes its own srcset (each crop has its own aspect ratio), so a square thumbnail set, a wide banner set, etc. each get correct `srcset`/`width`/`height`:
|
|
801
|
+
|
|
802
|
+
```nunjucks
|
|
803
|
+
{% image 'static/photo.jpg', size='thumb', alt='', sizes='240px' %}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
```html
|
|
807
|
+
<img src="static/photo-thumb-480w.webp"
|
|
808
|
+
srcset="static/photo-thumb-480w.webp 480w, static/photo-thumb.webp 960w"
|
|
809
|
+
width="480" height="480" sizes="240px" alt="" loading="lazy">
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
This requires the [poops-images](https://github.com/stamat/poops-images) compile cache (named-size widths are read from it). The `size` name matches a named entry in your `images.sizes` config. The largest member of the group is written without a width suffix (`photo-thumb.webp`) — poops still srcsets it at its real width from the cache.
|
|
813
|
+
|
|
800
814
|
**[poops-images](https://github.com/stamat/poops-images) integration:** if a `.poops-images-cache.json` compile cache is found in the output directory (poops-images writes one next to the images it generates), Poops reads variants from it instead of scanning the directory. On top of the scan behavior above, the cache gives you:
|
|
801
815
|
|
|
802
816
|
- `width` and `height` attributes on the `<img>` element (exact dimensions from the cache — prevents layout shift). Pass your own `width`/`height` kwargs to override.
|
|
803
817
|
- Correct `src` when the source format was converted (e.g. `photo.heic` → `photo.jpg`), even when there are no size variants.
|
|
804
|
-
- Named sizes (`photo-thumb-
|
|
818
|
+
- By default the srcset is built only from the plain `{name}-{width}w.{ext}` width variants. Named sizes (`photo-thumb-480w.webp`) and preprocessed outputs (`photo-blurred-640w.jpg`) are kept out of it — they are crops and effects with their own aspect ratios. Reach a named crop group on purpose with the `size` kwarg above (or the `srcset` filter's second argument).
|
|
805
819
|
- EXIF metadata via the `exif` filter (see below).
|
|
806
820
|
|
|
807
821
|
##### googleFonts
|
|
@@ -896,7 +910,7 @@ All filters are available in both engines. The only syntax difference is how arg
|
|
|
896
910
|
|
|
897
911
|
- `jsonify` — serializes a value to JSON. Usage: `{{ myObject | jsonify }}`
|
|
898
912
|
|
|
899
|
-
- `markdown` — renders a markdown string to HTML with GitHub
|
|
913
|
+
- `markdown` — renders a markdown string to HTML with GitHub Flavored Markdown extras: emoji shortcodes (e.g. `:rocket:` → 🚀), alert callouts (`> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, `[!CAUTION]`, `[!INFO]`) and footnotes (`[^1]`). Code fences are syntax-highlighted and headings get slug `id`s plus permalink anchors. Usage: `{{ "**bold** :rocket:" | markdown }}`
|
|
900
914
|
|
|
901
915
|
- `date` — formats a date string. Uses [dayjs](https://day.js.org/) format tokens. A default format can be set via the `timeDateFormat` config option.
|
|
902
916
|
- Nunjucks: `{{ "2024-01-15" | date("MMMM D, YYYY") }}`
|
|
@@ -945,6 +959,8 @@ All filters are available in both engines. The only syntax difference is how arg
|
|
|
945
959
|
|
|
946
960
|
Returns: `static/photo-320w.webp 320w, static/photo-640w.webp 640w, static/photo-960w.webp 960w`
|
|
947
961
|
|
|
962
|
+
Pass a named crop/resize group as the second argument to get that group's srcset instead of the default widths: `{{ 'static/photo.jpg' | srcset: 'thumb' }}` → `static/photo-thumb-480w.webp 480w, static/photo-thumb.webp 960w`.
|
|
963
|
+
|
|
948
964
|
- `exif` — returns the EXIF metadata object for an image from the [poops-images](https://github.com/stamat/poops-images) compile cache (`.poops-images-cache.json` in the output directory), or `null` if there is no cache or no EXIF data. The object includes camera (`make`, `model`, `lensModel`), exposure (`fNumber`, `exposure.formatted`, `iso`, `focalLength35mm`), `dateTime`, and `gps` (`latitude.formatted`, `longitude.formatted`, `altitude`, and a ready-made `googleMapsUrl`).
|
|
949
965
|
|
|
950
966
|
Example — a photo with date and location caption:
|
|
@@ -969,19 +985,21 @@ Returns: `static/photo-320w.webp 320w, static/photo-640w.webp 640w, static/photo
|
|
|
969
985
|
- `path` — site-relative source path, feeds straight into the `{% image %}` tag
|
|
970
986
|
- `date` — `exif.dateTime` when the photo has EXIF, file modification time otherwise — so sorting and grouping work for every image
|
|
971
987
|
- `outputs` — every generated file for the image (site-relative), useful for picking LQIP or preprocessed variants
|
|
972
|
-
- Pass a subdirectory (`'images/2025'`) to scope the list
|
|
988
|
+
- Pass a subdirectory (`'static/images/2025'`) to scope the list
|
|
989
|
+
|
|
990
|
+
**The path is relative to your markup `out` dir, not to `images.in`.** It mirrors where the generated images land — i.e. `images.out` made relative to markup `out`. So if `images.out` is `_site/static/images` and markup `out` is `_site`, the images live at `static/images` on the site and you call `'static/images' | images` (**not** `'images'`, which would look in `_site/images` and return `[]`). This is the same path you already pass to the `{% image %}` tag.
|
|
973
991
|
|
|
974
|
-
Combined with `groupby`, engine-native sorting and the `{% image %}` tag, a photo gallery is a pure template concern:
|
|
992
|
+
Combined with `groupby`, engine-native sorting and the `{% image %}` tag, a photo gallery is a pure template concern. This is the Instagram-style square grid — `size='thumb'` pulls the named crop group and its auto-generated srcset (define a `thumb` crop in `images.sizes`):
|
|
975
993
|
|
|
976
994
|
Nunjucks:
|
|
977
995
|
|
|
978
996
|
```nunjucks
|
|
979
|
-
{% for group in 'images' | images | sort(reverse=true, attribute='date') | groupby("date", "year") %}
|
|
997
|
+
{% for group in 'static/images' | images | sort(reverse=true, attribute='date') | groupby("date", "year") %}
|
|
980
998
|
<h2>{{ group.key }}</h2>
|
|
981
999
|
<div class="grid">
|
|
982
1000
|
{% for img in group.items %}
|
|
983
1001
|
<figure>
|
|
984
|
-
{% image img.path, alt='', sizes='(max-width: 640px) 50vw,
|
|
1002
|
+
{% image img.path, size='thumb', alt='', sizes='(max-width: 640px) 50vw, 240px' %}
|
|
985
1003
|
{% if img.exif and img.exif.gps %}
|
|
986
1004
|
<figcaption>
|
|
987
1005
|
<a href="{{ img.exif.gps.googleMapsUrl }}">📍</a> {{ img.date | date("MMM D, YYYY") }}
|
|
@@ -996,13 +1014,13 @@ Returns: `static/photo-320w.webp 320w, static/photo-640w.webp 640w, static/photo
|
|
|
996
1014
|
Liquid:
|
|
997
1015
|
|
|
998
1016
|
```liquid
|
|
999
|
-
{% assign imgs = 'images' | images | sort: 'date' | reverse %}
|
|
1017
|
+
{% assign imgs = 'static/images' | images | sort: 'date' | reverse %}
|
|
1000
1018
|
{% assign groups = imgs | groupby: "date", "year" %}
|
|
1001
1019
|
{% for group in groups %}
|
|
1002
1020
|
<h2>{{ group.key }}</h2>
|
|
1003
1021
|
<div class="grid">
|
|
1004
1022
|
{% for img in group.items %}
|
|
1005
|
-
<figure>{% image img.path, alt: '' %}</figure>
|
|
1023
|
+
<figure>{% image img.path, size: 'thumb', alt: '' %}</figure>
|
|
1006
1024
|
{% endfor %}
|
|
1007
1025
|
</div>
|
|
1008
1026
|
{% endfor %}
|
|
@@ -27,7 +27,11 @@ export default class LiquidEngine {
|
|
|
27
27
|
this.engine = new Liquid({
|
|
28
28
|
root: roots,
|
|
29
29
|
extname: '.liquid',
|
|
30
|
-
cache:
|
|
30
|
+
// Parsed-template cache: without it every {% render %}/{% include %}
|
|
31
|
+
// re-resolves (realpath per root) and re-parses the partial on every
|
|
32
|
+
// page — dominates build time on partial-heavy sites. Cleared per
|
|
33
|
+
// compile (clearCache) so watch-mode edits are picked up.
|
|
34
|
+
cache: true,
|
|
31
35
|
dynamicPartials: true,
|
|
32
36
|
strictFilters: false,
|
|
33
37
|
strictVariables: false,
|
|
@@ -69,9 +73,9 @@ export default class LiquidEngine {
|
|
|
69
73
|
if (!/^(<\?xml[^?]*\?>\s*)?<svg[\s>]/i.test(content)) return ''
|
|
70
74
|
return content
|
|
71
75
|
})
|
|
72
|
-
engine.registerFilter('srcset', (imagePath) => {
|
|
76
|
+
engine.registerFilter('srcset', (imagePath, size) => {
|
|
73
77
|
const outputDir = path.resolve(process.cwd(), markupOut)
|
|
74
|
-
const { variants } = discoverImageVariants(imagePath, outputDir)
|
|
78
|
+
const { variants } = discoverImageVariants(imagePath, outputDir, size)
|
|
75
79
|
if (variants.length === 0) return ''
|
|
76
80
|
return variants.map(v => `${v.path} ${v.width}w`).join(', ')
|
|
77
81
|
})
|
|
@@ -106,6 +110,10 @@ export default class LiquidEngine {
|
|
|
106
110
|
delete this.globals[key]
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
clearCache() {
|
|
114
|
+
if (this.engine.options.cache) this.engine.options.cache.clear()
|
|
115
|
+
}
|
|
116
|
+
|
|
109
117
|
async render(templateName, context) {
|
|
110
118
|
let source
|
|
111
119
|
const frontMatterResult = parseFrontMatter(templateName)
|
|
@@ -30,6 +30,7 @@ class RelativeLoader extends nunjucks.Loader {
|
|
|
30
30
|
}
|
|
31
31
|
if (!fs.existsSync(fullPath)) {
|
|
32
32
|
log({ tag: 'markup', error: true, text: 'Template not found:', link: name })
|
|
33
|
+
// noCache so a template created mid-watch is found on the next compile
|
|
33
34
|
return { src: '', path: fullPath, noCache: true }
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -53,7 +54,10 @@ class RelativeLoader extends nunjucks.Loader {
|
|
|
53
54
|
source = `{% extends '${frontMatter.layout}.html' %}\n{% block content %}\n${source}\n{% endblock %}`
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
// Cached: getSource does glob resolution + front matter + markdown per
|
|
58
|
+
// call, so re-running it per include per page dominates builds. Cleared
|
|
59
|
+
// per compile (clearCache) so watch-mode edits are picked up.
|
|
60
|
+
return { src: source, path: fullPath, noCache: false }
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
resolve(from, to) {
|
|
@@ -67,10 +71,15 @@ export default class NunjucksEngine {
|
|
|
67
71
|
|
|
68
72
|
this.env = new nunjucks.Environment(
|
|
69
73
|
new RelativeLoader(templatesDir, includePaths),
|
|
70
|
-
{ autoescape, watch: false
|
|
74
|
+
{ autoescape, watch: false }
|
|
71
75
|
)
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
clearCache() {
|
|
79
|
+
// nunjucks keeps compiled templates on each loader's cache object
|
|
80
|
+
for (const loader of this.env.loaders) loader.cache = {}
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
get fileExtension() { return '.njk' }
|
|
75
84
|
get indexableExtensions() { return new Set(['.html', '.md', '.njk']) }
|
|
76
85
|
get markupExtensions() { return 'html|xml|rss|atom|json|njk|md' }
|
|
@@ -108,9 +117,9 @@ export default class NunjucksEngine {
|
|
|
108
117
|
const date = !str || str.trim() === '' ? new Date() : new Date(str)
|
|
109
118
|
return dayjs(date).format(fmt)
|
|
110
119
|
})
|
|
111
|
-
env.addFilter('srcset', (imagePath) => {
|
|
120
|
+
env.addFilter('srcset', (imagePath, size) => {
|
|
112
121
|
const outputDir = path.resolve(process.cwd(), markupOut)
|
|
113
|
-
const { variants } = discoverImageVariants(imagePath, outputDir)
|
|
122
|
+
const { variants } = discoverImageVariants(imagePath, outputDir, size)
|
|
114
123
|
if (variants.length === 0) return ''
|
|
115
124
|
return variants.map(v => `${v.path} ${v.width}w`).join(', ')
|
|
116
125
|
})
|
package/lib/markup/helpers.js
CHANGED
|
@@ -88,15 +88,20 @@ export function decodeTemplateEntities(html) {
|
|
|
88
88
|
// H3s carry a `toc-h3` class for indentation — no nested <ul>, so leading H3s
|
|
89
89
|
// (no parent H2) stay valid. Heading text is already entity-encoded by marked,
|
|
90
90
|
// so it's spliced in as-is (re-escaping would double-encode &).
|
|
91
|
+
// `.sr-only` headings are visually hidden, so they're skipped — a visible TOC
|
|
92
|
+
// linking to invisible content is confusing.
|
|
91
93
|
export function renderToc(html) {
|
|
92
94
|
if (!html) return ''
|
|
93
|
-
const re = /<h([23])\b[^>]
|
|
95
|
+
const re = /<h([23])\b([^>]*)>([\s\S]*?)<\/h\1>/gi
|
|
94
96
|
let items = ''
|
|
95
97
|
let match
|
|
96
98
|
while ((match = re.exec(html)) !== null) {
|
|
99
|
+
const attrs = match[2]
|
|
100
|
+
if (/\bclass="[^"]*\bsr-only\b/i.test(attrs)) continue
|
|
101
|
+
const id = (attrs.match(/\sid="([^"]*)"/i) || [])[1]
|
|
97
102
|
const text = match[3].replace(/<[^>]*>/g, '').trim()
|
|
98
|
-
if (!
|
|
99
|
-
items += `<li class="toc-h${match[1]}"><a href="#${
|
|
103
|
+
if (!id || !text) continue
|
|
104
|
+
items += `<li class="toc-h${match[1]}"><a href="#${id}">${text}</a></li>`
|
|
100
105
|
}
|
|
101
106
|
if (!items) return ''
|
|
102
107
|
return `<nav class="toc" aria-label="On this page"><ul>${items}</ul></nav>`
|
|
@@ -172,16 +177,32 @@ function pickSrcsetAndSrc(variants, originalExt) {
|
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
// Variant discovery from the poops-images compile cache: exact output paths
|
|
175
|
-
// and dimensions, no directory scan.
|
|
176
|
-
// srcset material
|
|
177
|
-
// (
|
|
178
|
-
|
|
180
|
+
// and dimensions, no directory scan. By default only `{name}-{width}w.{ext}`
|
|
181
|
+
// outputs are srcset material. Pass `size` to instead build the srcset from a
|
|
182
|
+
// named crop/resize group (`thumb`) — poops-images tags each named variant with
|
|
183
|
+
// its size name in the cache, and the largest of the group drops the width
|
|
184
|
+
// suffix (`{name}-thumb.{ext}`), so widths come from the cache, not the filename.
|
|
185
|
+
function discoverImageVariantsFromCache(imagePath, outputDir, size) {
|
|
179
186
|
const found = getImageEntry(imagePath, outputDir)
|
|
180
187
|
if (!found) return null
|
|
181
188
|
|
|
182
189
|
const { entry, prefixDir } = found
|
|
183
190
|
const parsed = path.parse(imagePath)
|
|
184
191
|
const originalExt = parsed.ext.replace('.', '')
|
|
192
|
+
const sitePath = (p) => prefixDir ? toPosix(path.join(prefixDir, p)) : toPosix(p)
|
|
193
|
+
const extOf = (p) => path.posix.extname(toPosix(p)).replace('.', '')
|
|
194
|
+
|
|
195
|
+
if (size) {
|
|
196
|
+
// Named group — every output tagged with this size name; widths from the cache
|
|
197
|
+
const variants = (entry.outputs || [])
|
|
198
|
+
.filter(o => o.name === size && o.width)
|
|
199
|
+
.map(o => ({ path: sitePath(o.path), width: o.width, height: o.height, format: extOf(o.path) }))
|
|
200
|
+
if (variants.length === 0) return null
|
|
201
|
+
const { srcVariant, srcsetVariants } = pickSrcsetAndSrc(variants, originalExt)
|
|
202
|
+
const src = srcVariant || srcsetVariants[srcsetVariants.length - 1] || variants[variants.length - 1]
|
|
203
|
+
return { src: src.path, variants: srcsetVariants, width: src.width, height: src.height }
|
|
204
|
+
}
|
|
205
|
+
|
|
185
206
|
const variantPattern = new RegExp(`^${escapeRegExp(parsed.name)}-(\\d+)w\\.([a-z0-9]+)$`)
|
|
186
207
|
const basePattern = new RegExp(`^${escapeRegExp(parsed.name)}\\.([a-z0-9]+)$`)
|
|
187
208
|
|
|
@@ -189,15 +210,17 @@ function discoverImageVariantsFromCache(imagePath, outputDir) {
|
|
|
189
210
|
let base = null
|
|
190
211
|
for (const out of entry.outputs || []) {
|
|
191
212
|
const file = path.posix.basename(toPosix(out.path))
|
|
192
|
-
|
|
213
|
+
// Named / preprocessed variants (they carry a `name`) are crops or effects
|
|
214
|
+
// with their own aspect ratio — never mix them into the default width srcset.
|
|
215
|
+
if (out.name) continue
|
|
193
216
|
let match = file.match(variantPattern)
|
|
194
217
|
if (match) {
|
|
195
|
-
variants.push({ path: sitePath, width: parseInt(match[1], 10), height: out.height, format: match[2] })
|
|
218
|
+
variants.push({ path: sitePath(out.path), width: parseInt(match[1], 10), height: out.height, format: match[2] })
|
|
196
219
|
continue
|
|
197
220
|
}
|
|
198
221
|
match = file.match(basePattern)
|
|
199
222
|
if (match && !base) {
|
|
200
|
-
base = { path: sitePath, width: out.width, height: out.height }
|
|
223
|
+
base = { path: sitePath(out.path), width: out.width, height: out.height }
|
|
201
224
|
}
|
|
202
225
|
}
|
|
203
226
|
|
|
@@ -208,9 +231,11 @@ function discoverImageVariantsFromCache(imagePath, outputDir) {
|
|
|
208
231
|
return { src: src.path, variants: srcsetVariants, width: src.width, height: src.height }
|
|
209
232
|
}
|
|
210
233
|
|
|
211
|
-
export function discoverImageVariants(imagePath, outputDir) {
|
|
212
|
-
const fromCache = discoverImageVariantsFromCache(imagePath, outputDir)
|
|
234
|
+
export function discoverImageVariants(imagePath, outputDir, size) {
|
|
235
|
+
const fromCache = discoverImageVariantsFromCache(imagePath, outputDir, size)
|
|
213
236
|
if (fromCache) return fromCache
|
|
237
|
+
// A named size only exists in the compile cache — no directory-scan fallback.
|
|
238
|
+
if (size) return { src: imagePath, variants: [] }
|
|
214
239
|
|
|
215
240
|
const parsed = path.parse(imagePath)
|
|
216
241
|
const dir = path.join(outputDir, parsed.dir)
|
|
@@ -322,7 +347,7 @@ export function buildImageTag(imagePath, prefix, kwargs, getOutputDir) {
|
|
|
322
347
|
if (isSvg) {
|
|
323
348
|
attrs.unshift(`src="${escapeAttr(prefix + imagePath)}"`)
|
|
324
349
|
} else {
|
|
325
|
-
const { src, variants, width, height } = discoverImageVariants(imagePath, getOutputDir())
|
|
350
|
+
const { src, variants, width, height } = discoverImageVariants(imagePath, getOutputDir(), kwargs && kwargs.size)
|
|
326
351
|
const sizes = (kwargs && kwargs.sizes) || '100vw'
|
|
327
352
|
attrs.unshift(`src="${escapeAttr(prefix + src)}"`)
|
|
328
353
|
if (width && height && !(kwargs && (kwargs.width || kwargs.height))) {
|
|
@@ -337,7 +362,7 @@ export function buildImageTag(imagePath, prefix, kwargs, getOutputDir) {
|
|
|
337
362
|
|
|
338
363
|
attrs.push(`loading="${escapeAttr(loading)}"`)
|
|
339
364
|
if (kwargs) {
|
|
340
|
-
const skip = new Set(['alt', 'sizes', 'loading'])
|
|
365
|
+
const skip = new Set(['alt', 'sizes', 'loading', 'size'])
|
|
341
366
|
for (const [key, val] of Object.entries(kwargs)) {
|
|
342
367
|
// `__keywords` is nunjucks' kwargs marker, never a real attribute
|
|
343
368
|
if (key.startsWith('__') || skip.has(key)) continue
|
package/lib/markup/highlight.js
CHANGED
|
@@ -69,8 +69,8 @@ export function highlightCode(code, language) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export const highlightRenderer = {
|
|
72
|
-
code(
|
|
73
|
-
const highlighted = highlightCode(
|
|
72
|
+
code({ text, lang }) {
|
|
73
|
+
const highlighted = highlightCode(text, lang)
|
|
74
74
|
const langClass = lang ? ` language-${escapeHtml(lang)}` : ''
|
|
75
75
|
return `<pre><code class="hljs${langClass}">${highlighted}</code></pre>\n`
|
|
76
76
|
}
|
package/lib/markup/renderer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Marked } from 'marked'
|
|
2
2
|
import { markedGithubEmoji } from 'marked-github-emoji'
|
|
3
3
|
import { markedGithubAlerts } from 'marked-github-alerts'
|
|
4
|
+
import { markedGithubFootnote } from 'marked-github-footnote'
|
|
4
5
|
import { slugify } from 'book-of-spells'
|
|
5
6
|
import { highlightRenderer, highlightCode } from './highlight.js'
|
|
6
7
|
import { decodeTemplateEntities } from './helpers.js'
|
|
@@ -33,8 +34,8 @@ export const markdownRenderer = {
|
|
|
33
34
|
// as empty). Highlight around/inside each raw block instead, keeping the
|
|
34
35
|
// fence's language, and pass the delimiters through intact. Inline code
|
|
35
36
|
// never splits the delimiters, so it needs no special casing.
|
|
36
|
-
code(
|
|
37
|
-
const highlighted = mapRawSegments(
|
|
37
|
+
code({ text, lang }) {
|
|
38
|
+
const highlighted = mapRawSegments(text, (s) => s && highlightCode(s, lang), (s) => highlightCode(s, lang))
|
|
38
39
|
const langClass = lang ? ` language-${lang.replace(/[^\w-]/g, '')}` : ''
|
|
39
40
|
return `<pre><code class="hljs${langClass}">${highlighted}</code></pre>\n`
|
|
40
41
|
},
|
|
@@ -43,10 +44,11 @@ export const markdownRenderer = {
|
|
|
43
44
|
// no such CSS renders an invisible anchor instead of a stray "#".
|
|
44
45
|
// ponytail: no slug dedup — two identical headings on one page share an id;
|
|
45
46
|
// add a per-parse counter if that ever bites.
|
|
46
|
-
heading(
|
|
47
|
+
heading({ tokens, depth, text: raw }) {
|
|
48
|
+
const text = this.parser.parseInline(tokens)
|
|
47
49
|
const id = slugify(raw || '')
|
|
48
|
-
if (!id) return `<h${
|
|
49
|
-
return `<h${
|
|
50
|
+
if (!id) return `<h${depth}>${text}</h${depth}>\n`
|
|
51
|
+
return `<h${depth} id="${id}">${text}<a class="heading-anchor" href="#${id}" aria-label="Permalink" aria-hidden="true"></a></h${depth}>\n`
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -58,6 +60,7 @@ marked.use(markedGithubAlerts({
|
|
|
58
60
|
info: { title: 'Info', icon: 'info' }
|
|
59
61
|
}
|
|
60
62
|
}))
|
|
63
|
+
marked.use(markedGithubFootnote())
|
|
61
64
|
|
|
62
65
|
// Renders a markdown page source for the template engines. Entity decoding
|
|
63
66
|
// (which un-escapes inside {{ }} / {% %} so template args parse) must skip
|
package/lib/markups.js
CHANGED
|
@@ -375,6 +375,9 @@ export default class Markups {
|
|
|
375
375
|
throw err
|
|
376
376
|
} finally {
|
|
377
377
|
clearFrontMatterCache()
|
|
378
|
+
// Engine template caches live for one compile: fast within a build,
|
|
379
|
+
// watch-mode edits always picked up on the next one
|
|
380
|
+
if (typeof this.engine.clearCache === 'function') this.engine.clearCache()
|
|
378
381
|
}
|
|
379
382
|
}
|
|
380
383
|
|
|
@@ -426,6 +429,7 @@ export default class Markups {
|
|
|
426
429
|
throw err
|
|
427
430
|
} finally {
|
|
428
431
|
clearFrontMatterCache()
|
|
432
|
+
if (typeof this.engine.clearCache === 'function') this.engine.clearCache()
|
|
429
433
|
}
|
|
430
434
|
}
|
|
431
435
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poops",
|
|
3
3
|
"description": "Straightforward, no-bullshit bundler for the web.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "poops.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"static-site-generator"
|
|
35
35
|
],
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
37
|
+
"poops-images": ">=1.2.1",
|
|
38
|
+
"postcss": "^8.0.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"postcss": {
|
|
@@ -61,9 +61,10 @@
|
|
|
61
61
|
"highlight.js": "^11.11.1",
|
|
62
62
|
"liquidjs": "^10.24.0",
|
|
63
63
|
"livereload": "^0.9.3",
|
|
64
|
-
"marked": "^
|
|
64
|
+
"marked": "^18.0.6",
|
|
65
65
|
"marked-github-alerts": "^1.0.1",
|
|
66
66
|
"marked-github-emoji": "^1.0.2",
|
|
67
|
+
"marked-github-footnote": "^1.0.0",
|
|
67
68
|
"nunjucks": "^3.2.4",
|
|
68
69
|
"portscanner": "^2.2.0",
|
|
69
70
|
"printstyle": "^1.0.0",
|
|
@@ -88,4 +89,4 @@
|
|
|
88
89
|
"overrides": {
|
|
89
90
|
"minimatch": ">=10.2.1"
|
|
90
91
|
}
|
|
91
|
-
}
|
|
92
|
+
}
|