poops 1.9.6 → 1.9.8
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 -0
- package/lib/markup/engines/liquid.js +3 -10
- package/lib/markup/engines/nunjucks.js +3 -12
- package/lib/markup/helpers.js +10 -6
- package/lib/markup/highlight.js +41 -8
- package/lib/markup/renderer.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1023,6 +1023,20 @@ The language argument is optional. If omitted, highlight.js will attempt to auto
|
|
|
1023
1023
|
|
|
1024
1024
|
Registered languages: `javascript`/`js`, `typescript`/`ts`, `css`, `scss`, `html`, `xml`, `json`, `bash`/`sh`, `shell`, `python`/`py`, `ruby`/`rb`, `php`, `java`, `c`, `cpp`, `csharp`/`cs`, `go`, `rust`/`rs`, `yaml`/`yml`, `markdown`/`md`, `sql`, `diff`.
|
|
1025
1025
|
|
|
1026
|
+
**Fence info strings.** Only the first word names the language. Anything after it is carried onto the `<code>` element instead of being dropped: a bare word becomes a class, a `key=value` token becomes a `data-` attribute. This is how a fence marks itself for a later stage — a post-`markup` `exec` script that turns `code.preview` blocks into live demos, for example — without a marker comment in the markdown.
|
|
1027
|
+
|
|
1028
|
+
````md
|
|
1029
|
+
```html preview tab=options widths=375,768
|
|
1030
|
+
<my-element></my-element>
|
|
1031
|
+
```
|
|
1032
|
+
````
|
|
1033
|
+
|
|
1034
|
+
```html
|
|
1035
|
+
<pre><code class="hljs language-html preview" data-tab="options" data-widths="375,768">…</code></pre>
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
Values are single tokens — no quotes, no spaces. A trailing `=` with nothing after it emits a valueless attribute (`expanded=` → `data-expanded=""`), for a flag you want to read with `hasAttribute` rather than as a class. The same applies to the `{% highlight %}` tag and the `highlight` filter.
|
|
1039
|
+
|
|
1026
1040
|
#### Custom Filters
|
|
1027
1041
|
|
|
1028
1042
|
All filters are available in both engines. The only syntax difference is how arguments are passed: Nunjucks uses parentheses `| filter("arg")`, Liquid uses a colon `| filter: "arg"`.
|
|
@@ -1093,6 +1107,18 @@ All filters are available in both engines. The only syntax difference is how arg
|
|
|
1093
1107
|
---
|
|
1094
1108
|
```
|
|
1095
1109
|
|
|
1110
|
+
The same `jsonld` object works in your `site` data, as a site-wide default — useful when every page on the site is one type. A docs site is `TechArticle`, not `WebPage`:
|
|
1111
|
+
|
|
1112
|
+
```json
|
|
1113
|
+
"markup": {
|
|
1114
|
+
"site": {
|
|
1115
|
+
"jsonld": { "@type": "TechArticle" }
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
Precedence is defaults → `site.jsonld` → `page.jsonld`, so a single page can still opt out (a `FAQPage` inside a `TechArticle` site). A site-wide `@type` also overrides the auto-detected `BlogPosting` on dated pages, so on a site mixing docs and a blog set the type per page instead. Both merge into the page's own block only — the auto-emitted `WebSite` and `BreadcrumbList` blocks are untouched.
|
|
1121
|
+
|
|
1096
1122
|
poops auto-picks `BlogPosting` (page has a `date`) or `WebPage`. Override `@type` with the `jsonld` object for any schema.org type — common ones search/generative engines act on: `Article`, `NewsArticle`, `HowTo`, `FAQPage`, `QAPage`, `Product`, `Recipe`, `Event`, `Course`, `VideoObject`, `SoftwareApplication`, `Organization`, `Person`, `BreadcrumbList`, `WebSite`. Full list at [schema.org/docs/full](https://schema.org/docs/full.html); validate with the [Rich Results Test](https://search.google.com/test/rich-results). A per-`@type` table with the notable fields is in the [Templating docs](example/src/markup/docs/quick-start/templating-html.md).
|
|
1097
1123
|
|
|
1098
1124
|
- `breadcrumb` — generates a visible breadcrumb `<nav class="breadcrumb"><ol>…</ol></nav>` trail for the page body (blog posts, nested pages), from the same URL-depth data the `jsonld` `BreadcrumbList` uses: the site root, each ancestor folder (humanized, e.g. `docs/static-site` → _Static Site_), then the current page as `aria-current` text. Pass `relativePathPrefix` so links resolve against the current output location (localhost in dev, your deployed subpath in prod) — not the absolute domain.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { Liquid } from 'liquidjs'
|
|
4
|
-
import { highlightCode } from '../highlight.js'
|
|
4
|
+
import { highlightCode, codeBlock } from '../highlight.js'
|
|
5
5
|
import { marked, renderMarkdownCached } from '../renderer.js'
|
|
6
6
|
import { discoverImageVariants, parseFrontMatter, groupby, buildImageTag, buildPaginationTag, renderToc, buildJsonLd, buildOpenGraph, buildCanonical, buildBreadcrumb } from '../helpers.js'
|
|
7
7
|
import { getImageExif, listImages } from '../image-cache.js'
|
|
@@ -131,11 +131,7 @@ export default class LiquidEngine {
|
|
|
131
131
|
engine.registerFilter('canonical', (page, site) => buildCanonical(page, site))
|
|
132
132
|
engine.registerFilter('breadcrumb', (page, site, prefix) => buildBreadcrumb(page, site, prefix))
|
|
133
133
|
engine.registerFilter('groupby', (arr, key, datePart) => groupby(arr, key, datePart))
|
|
134
|
-
engine.registerFilter('highlight', (code, lang) =>
|
|
135
|
-
const highlighted = highlightCode(code, lang)
|
|
136
|
-
const langClass = lang ? ` language-${lang}` : ''
|
|
137
|
-
return `<pre><code class="hljs${langClass}">${highlighted}</code></pre>`
|
|
138
|
-
})
|
|
134
|
+
engine.registerFilter('highlight', (code, lang) => codeBlock(highlightCode(code, lang), lang))
|
|
139
135
|
}
|
|
140
136
|
|
|
141
137
|
registerTags(getOutputDir) {
|
|
@@ -347,10 +343,7 @@ function registerHighlightTag(engine) {
|
|
|
347
343
|
},
|
|
348
344
|
* render(ctx) {
|
|
349
345
|
const code = yield this.liquid.renderer.renderTemplates(this.templates, ctx)
|
|
350
|
-
|
|
351
|
-
const highlighted = highlightCode(code, lang)
|
|
352
|
-
const langClass = lang ? ` language-${lang}` : ''
|
|
353
|
-
return `<pre><code class="hljs${langClass}">${highlighted}</code></pre>`
|
|
346
|
+
return codeBlock(highlightCode(code, this.lang), this.lang)
|
|
354
347
|
}
|
|
355
348
|
})
|
|
356
349
|
}
|
|
@@ -6,7 +6,7 @@ import path from 'node:path'
|
|
|
6
6
|
import { discoverImageVariants, parseFrontMatter, groupby, buildImageTag, buildPaginationTag, renderToc, buildJsonLd, buildOpenGraph, buildCanonical, buildBreadcrumb } from '../helpers.js'
|
|
7
7
|
import { getImageExif, listImages } from '../image-cache.js'
|
|
8
8
|
import { toPosix } from '../../utils/helpers.js'
|
|
9
|
-
import { highlightCode } from '../highlight.js'
|
|
9
|
+
import { highlightCode, codeBlock } from '../highlight.js'
|
|
10
10
|
import { marked, renderMarkdownCached } from '../renderer.js'
|
|
11
11
|
import { slugify, humanize } from 'book-of-spells'
|
|
12
12
|
import dayjs from 'dayjs'
|
|
@@ -197,11 +197,7 @@ export default class NunjucksEngine {
|
|
|
197
197
|
env.addFilter('canonical', (page, site) => new nunjucks.runtime.SafeString(buildCanonical(page, site)))
|
|
198
198
|
env.addFilter('breadcrumb', (page, site, prefix) => new nunjucks.runtime.SafeString(buildBreadcrumb(page, site, prefix)))
|
|
199
199
|
env.addFilter('groupby', (arr, key, datePart) => groupby(arr, key, datePart))
|
|
200
|
-
env.addFilter('highlight', (code, lang) =>
|
|
201
|
-
const highlighted = highlightCode(code, lang)
|
|
202
|
-
const langClass = lang ? ` language-${lang}` : ''
|
|
203
|
-
return new nunjucks.runtime.SafeString(`<pre><code class="hljs${langClass}">${highlighted}</code></pre>`)
|
|
204
|
-
})
|
|
200
|
+
env.addFilter('highlight', (code, lang) => new nunjucks.runtime.SafeString(codeBlock(highlightCode(code, lang), lang)))
|
|
205
201
|
}
|
|
206
202
|
|
|
207
203
|
registerTags(getOutputDir) {
|
|
@@ -335,12 +331,7 @@ export class HighlightExtension {
|
|
|
335
331
|
}
|
|
336
332
|
|
|
337
333
|
run(context, lang, body) {
|
|
338
|
-
|
|
339
|
-
const highlighted = highlightCode(code, lang)
|
|
340
|
-
const langClass = lang ? ` language-${lang}` : ''
|
|
341
|
-
return new nunjucks.runtime.SafeString(
|
|
342
|
-
`<pre><code class="hljs${langClass}">${highlighted}</code></pre>`
|
|
343
|
-
)
|
|
334
|
+
return new nunjucks.runtime.SafeString(codeBlock(highlightCode(body(), lang), lang))
|
|
344
335
|
}
|
|
345
336
|
}
|
|
346
337
|
|
package/lib/markup/helpers.js
CHANGED
|
@@ -532,10 +532,12 @@ export function buildBreadcrumb(page, site = {}, prefix = '') {
|
|
|
532
532
|
// gains a `logo` ImageObject when `site.logo` is set — Google Article rich
|
|
533
533
|
// results require it. On the homepage (page has no `url`) a second `WebSite`
|
|
534
534
|
// block is emitted, declaring the site name for search results.
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
//
|
|
535
|
+
// `site.jsonld` and `page.jsonld` (objects) are merged over the generated
|
|
536
|
+
// defaults — site-wide first, then the page — so a site can set a default
|
|
537
|
+
// @type (a docs site is TechArticle, not WebPage) and a page can still add or
|
|
538
|
+
// replace any field as a full escape hatch. Values come from front matter, so
|
|
539
|
+
// the JSON is escaped for a <script> context to prevent a `</script>`
|
|
540
|
+
// break-out (XSS).
|
|
539
541
|
export function buildJsonLd(page, site = {}) {
|
|
540
542
|
page = page || {}
|
|
541
543
|
site = site || {}
|
|
@@ -577,8 +579,10 @@ export function buildJsonLd(page, site = {}) {
|
|
|
577
579
|
if (page.wordcount) data.wordCount = page.wordcount
|
|
578
580
|
}
|
|
579
581
|
|
|
580
|
-
// page.jsonld
|
|
581
|
-
// the escape hatch is meant for whole-key
|
|
582
|
+
// site.jsonld then page.jsonld extend/override — the page wins. Shallow
|
|
583
|
+
// merge — nested schema is rare and the escape hatch is meant for whole-key
|
|
584
|
+
// replacement.
|
|
585
|
+
if (site.jsonld && typeof site.jsonld === 'object') Object.assign(data, site.jsonld)
|
|
582
586
|
if (page.jsonld && typeof page.jsonld === 'object') Object.assign(data, page.jsonld)
|
|
583
587
|
|
|
584
588
|
const blocks = [jsonLdScript(data)]
|
package/lib/markup/highlight.js
CHANGED
|
@@ -58,20 +58,53 @@ function escapeHtml(str) {
|
|
|
58
58
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
// A fence's info string is everything after the backticks — the language plus
|
|
62
|
+
// any meta words (```html preview). Only the first word names the language, so
|
|
63
|
+
// the rest has to come off before a hljs lookup or a `language-` class: with it
|
|
64
|
+
// attached, getLanguage misses and the block silently falls to auto-detection.
|
|
65
|
+
export function fenceLang(info) {
|
|
66
|
+
return String(info || '').trim().split(/\s+/)[0].toLowerCase()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Everything after the language is the fence's own markup for a later stage:
|
|
70
|
+
// a bare word becomes a class (```html preview → code.preview), `key=value`
|
|
71
|
+
// becomes a data attribute (tab=options → data-tab="options"). Values are
|
|
72
|
+
// single tokens — no quotes, no spaces — which keeps the parser a split and is
|
|
73
|
+
// enough for settings; anything longer belongs in the markdown around the fence.
|
|
74
|
+
export function fenceAttrs(info) {
|
|
75
|
+
const [lang, ...rest] = String(info || '').trim().split(/\s+/).filter(Boolean)
|
|
76
|
+
const classes = ['hljs']
|
|
77
|
+
if (lang) classes.push(`language-${lang.toLowerCase().replace(/[^\w-]/g, '')}`)
|
|
78
|
+
let data = ''
|
|
79
|
+
for (const token of rest) {
|
|
80
|
+
const eq = token.indexOf('=')
|
|
81
|
+
if (eq < 0) {
|
|
82
|
+
const name = token.replace(/[^\w-]/g, '')
|
|
83
|
+
if (name) classes.push(name)
|
|
84
|
+
continue
|
|
66
85
|
}
|
|
86
|
+
const key = token.slice(0, eq).replace(/[^\w-]/g, '')
|
|
87
|
+
if (key) data += ` data-${key}="${escapeHtml(token.slice(eq + 1))}"`
|
|
88
|
+
}
|
|
89
|
+
return ` class="${classes.join(' ')}"${data}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The one place a highlighted fence becomes html. Every renderer and engine
|
|
93
|
+
// goes through it, so they cannot drift on what a fence produces again.
|
|
94
|
+
export function codeBlock(highlighted, info) {
|
|
95
|
+
return `<pre><code${fenceAttrs(info)}>${highlighted}</code></pre>`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function highlightCode(code, language) {
|
|
99
|
+
const lang = fenceLang(language)
|
|
100
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
101
|
+
return hljs.highlight(code, { language: lang }).value
|
|
67
102
|
}
|
|
68
103
|
return hljs.highlightAuto(code).value
|
|
69
104
|
}
|
|
70
105
|
|
|
71
106
|
export const highlightRenderer = {
|
|
72
107
|
code({ text, lang }) {
|
|
73
|
-
|
|
74
|
-
const langClass = lang ? ` language-${escapeHtml(lang)}` : ''
|
|
75
|
-
return `<pre><code class="hljs${langClass}">${highlighted}</code></pre>\n`
|
|
108
|
+
return `${codeBlock(highlightCode(text, lang), lang)}\n`
|
|
76
109
|
}
|
|
77
110
|
}
|
package/lib/markup/renderer.js
CHANGED
|
@@ -3,7 +3,7 @@ import { markedGithubEmoji } from 'marked-github-emoji'
|
|
|
3
3
|
import { markedGithubAlerts } from 'marked-github-alerts'
|
|
4
4
|
import { markedGithubFootnote } from 'marked-github-footnote'
|
|
5
5
|
import { slugify } from 'book-of-spells'
|
|
6
|
-
import { highlightRenderer, highlightCode } from './highlight.js'
|
|
6
|
+
import { highlightRenderer, highlightCode, codeBlock } from './highlight.js'
|
|
7
7
|
import { decodeTemplateEntities } from './helpers.js'
|
|
8
8
|
|
|
9
9
|
const RAW_BLOCK_RE = /\{%-?\s*raw\s*-?%\}([\s\S]*?)\{%-?\s*endraw\s*-?%\}/g
|
|
@@ -36,8 +36,7 @@ export const markdownRenderer = {
|
|
|
36
36
|
// never splits the delimiters, so it needs no special casing.
|
|
37
37
|
code({ text, lang }) {
|
|
38
38
|
const highlighted = mapRawSegments(text, (s) => s && highlightCode(s, lang), (s) => highlightCode(s, lang))
|
|
39
|
-
|
|
40
|
-
return `<pre><code class="hljs${langClass}">${highlighted}</code></pre>\n`
|
|
39
|
+
return `${codeBlock(highlighted, lang)}\n`
|
|
41
40
|
},
|
|
42
41
|
// Give every heading a slug id + a permalink anchor. The anchor is empty on
|
|
43
42
|
// purpose — themes reveal a "#" via `.heading-anchor::before`, so a site with
|