@inditextech/docouture-asciidoc-extensions 0.1.0-SNAPSHOT.40.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 +254 -0
- package/index.js +117 -0
- package/lib/accordion.js +243 -0
- package/lib/async-compat.js +90 -0
- package/lib/card-grid.js +327 -0
- package/lib/cta.js +334 -0
- package/lib/feature-tabs.js +400 -0
- package/lib/first-positional.js +13 -0
- package/lib/html.js +121 -0
- package/lib/kroki-config.js +191 -0
- package/lib/kroki-instance.js +89 -0
- package/lib/kroki-mermaid-theme.js +93 -0
- package/lib/kroki.js +165 -0
- package/lib/label-macro.js +69 -0
- package/lib/mono-macro.js +41 -0
- package/lib/nowrap-cols.js +61 -0
- package/lib/shiki-config.js +94 -0
- package/lib/shiki-instance.js +64 -0
- package/lib/shiki-syntax-highlighter.js +137 -0
- package/lib/table-container.js +143 -0
- package/lib/table-width.js +70 -0
- package/lib/tabs.js +263 -0
- package/lib/unique-id.js +78 -0
- package/lib/video-size.js +89 -0
- package/lib/warn.js +75 -0
- package/package.json +33 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Bridges the two Asciidoctor.js majors this repo runs — see
|
|
4
|
+
// first-positional.js's own comment for the first instance of this split.
|
|
5
|
+
// 2.2 (Opal, real Antora site builds) is fully synchronous throughout its
|
|
6
|
+
// JS bridge. 4.0 (native JS, the ui-bundle preview harness) makes
|
|
7
|
+
// `parseContent`/`convert`/`precomputeText` all return Promises instead.
|
|
8
|
+
// Neither extension using this can be written `async` unconditionally: that
|
|
9
|
+
// would hand 2.2's synchronous Opal caller a Promise where it expects a
|
|
10
|
+
// real value, since Opal's own dispatch loop never awaits a plain JS
|
|
11
|
+
// function's return value — verified empirically (a bare `async` process
|
|
12
|
+
// function renders literally "[object Promise]" under Antora's 2.2, not an
|
|
13
|
+
// error, which is what surfaced this in the first place).
|
|
14
|
+
//
|
|
15
|
+
// `chain`/`chainAll` run `fn` immediately if the input is already a plain
|
|
16
|
+
// value (2.2), or after it resolves if it's a thenable (4.0). The
|
|
17
|
+
// extension's own `process()` callback then returns whatever `fn` returns —
|
|
18
|
+
// a plain value under 2.2, a Promise under 4.0 — matching what each
|
|
19
|
+
// version's own block-processing loop expects (4.0's parser `await`s the
|
|
20
|
+
// return value of `processMethod`; 2.2's Opal loop uses it directly).
|
|
21
|
+
function chain(value, fn) {
|
|
22
|
+
if (value && typeof value.then === 'function') return value.then(fn)
|
|
23
|
+
return fn(value)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function chainAll(values, fn) {
|
|
27
|
+
if (values.some((v) => v && typeof v.then === 'function')) {
|
|
28
|
+
return Promise.all(values).then(fn)
|
|
29
|
+
}
|
|
30
|
+
return fn(values)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// A ListItem's `.getText()` needs `.precomputeText()` called first when the
|
|
34
|
+
// item was parsed outside Document's own top-level parse — exactly what
|
|
35
|
+
// these two extensions do via `parseContent`. The substituted text (inline
|
|
36
|
+
// macros like `xref:` resolved) is normally pre-computed during
|
|
37
|
+
// `Document.parse()`'s own post-processing walk (see @asciidoctor/core
|
|
38
|
+
// 4.0's document.js) — a walk that only ever reaches blocks left attached
|
|
39
|
+
// to the real document tree. Both extensions here discard their
|
|
40
|
+
// `parseContent` wrapper (replacing it with a single `pass` block holding
|
|
41
|
+
// pre-rendered HTML — see steps.js/card-grid.js's own `finish()`), so
|
|
42
|
+
// nothing in it is EVER part of that tree, and nothing in it gets
|
|
43
|
+
// precomputed automatically — verified empirically: without this, any
|
|
44
|
+
// `*bold*`/`xref:`/etc. inside a nested list item renders as literal,
|
|
45
|
+
// unsubstituted source text under 4.0 (2.2 is unaffected — its `getText()`
|
|
46
|
+
// always returns already-substituted text, no separate precompute step
|
|
47
|
+
// exists). `precomputeSubtree` runs the same precompute pass by hand, once,
|
|
48
|
+
// over everything `parseContent` produced.
|
|
49
|
+
//
|
|
50
|
+
// A Block's own `.getTitle()` has exactly the same problem, for exactly the
|
|
51
|
+
// same reason, and needs `.precomputeTitle()` — measured: a `.Title` line
|
|
52
|
+
// carrying an `xref:` or a `link:` comes back as raw, unsubstituted source
|
|
53
|
+
// (`https://x.com[Label]`) from a block parsed through `parseContent`, and
|
|
54
|
+
// as converted HTML (`<a href="https://x.com">Label</a>`) once precomputed.
|
|
55
|
+
// That is load-bearing for card-grid.js, whose card titles ARE links, and it
|
|
56
|
+
// was a latent bug for steps.js's own `.Title` lines before this was added.
|
|
57
|
+
// 2.2 has no `precomputeTitle` at all and needs none — its `getTitle()` is
|
|
58
|
+
// always already substituted — hence the `typeof` guard, the same one
|
|
59
|
+
// `precomputeText` gets.
|
|
60
|
+
//
|
|
61
|
+
// Recurses through both list shapes the Asciidoctor object model uses:
|
|
62
|
+
// `getBlocks()` for any ordinary block INCLUDING ulist/olist (where each
|
|
63
|
+
// child is a ListItem directly), and a dlist's own `[terms[], description]`
|
|
64
|
+
// tuple shape specifically (its `blocks` array holds pairs, not ListItems,
|
|
65
|
+
// so a plain `getBlocks()` recursion alone would silently skip every term
|
|
66
|
+
// and description inside it).
|
|
67
|
+
function precomputeSubtree(node) {
|
|
68
|
+
const jobs = []
|
|
69
|
+
function visitItem(item) {
|
|
70
|
+
if (!item) return
|
|
71
|
+
if (typeof item.precomputeText === 'function') jobs.push(item.precomputeText())
|
|
72
|
+
if (typeof item.precomputeTitle === 'function') jobs.push(item.precomputeTitle())
|
|
73
|
+
if (typeof item.getBlocks === 'function') visitChildren(item.getBlocks())
|
|
74
|
+
}
|
|
75
|
+
function visitChildren(blocks) {
|
|
76
|
+
blocks.forEach((block) => {
|
|
77
|
+
if (Array.isArray(block)) {
|
|
78
|
+
const [terms, description] = block
|
|
79
|
+
terms.forEach(visitItem)
|
|
80
|
+
visitItem(description)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
visitItem(block)
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
visitItem(node)
|
|
87
|
+
return chainAll(jobs, () => node)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { chain, chainAll, precomputeSubtree }
|
package/lib/card-grid.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { chain, chainAll, precomputeSubtree } = require('./async-compat')
|
|
4
|
+
const { escapeHtml } = require('./html')
|
|
5
|
+
const warn = require('./warn')
|
|
6
|
+
|
|
7
|
+
// Card grid — the ATD Card (Figma component set 2669:33855, states and types
|
|
8
|
+
// at 2610:29000 for light and 2669:42541 for dark), rendered as IDS Card
|
|
9
|
+
// (card/card.css) in a CSS grid. It is the block behind the landing's
|
|
10
|
+
// Quicklinks row (GH-20) AND the section-landing card grids the Weave.js
|
|
11
|
+
// migration brought over (Fumadocs' `<Cards>`/`<Card>`, 210 uses across 11
|
|
12
|
+
// files) — one component, one look, doc pages and landings alike.
|
|
13
|
+
//
|
|
14
|
+
// The design's card is a slot of three optional groups over an optional
|
|
15
|
+
// image:
|
|
16
|
+
//
|
|
17
|
+
// Header info a 20x20 icon and a `label/m` subheader
|
|
18
|
+
// Main info the title (`title/s`) and description (`body/l`)
|
|
19
|
+
// Meta `.ids-label--grey` chips
|
|
20
|
+
//
|
|
21
|
+
// The frames' date field is deliberately not modelled, and neither is
|
|
22
|
+
// `Selected`: a documentation card has nothing to select. Nor is there a
|
|
23
|
+
// call to action — the ATD Card catalogue has no button in any of its four
|
|
24
|
+
// types, and the whole card is already the link, so a "Know more" would be a
|
|
25
|
+
// second affordance for the one thing clicking anywhere on the card does.
|
|
26
|
+
// (An earlier Quicklinks frame, 2696:54698, drew a ghost button; the
|
|
27
|
+
// catalogue supersedes it.)
|
|
28
|
+
//
|
|
29
|
+
// Every colour in both theme frames resolves to a semantic IDS token that
|
|
30
|
+
// ids-tokens.css already swaps under `.ids-theme-dark`, so dark theme costs
|
|
31
|
+
// this extension and card-grid.css nothing — see that file's own header.
|
|
32
|
+
//
|
|
33
|
+
// SYNTAX
|
|
34
|
+
//
|
|
35
|
+
// A `[cards]` example block holding one `[card]` per card. A card is either
|
|
36
|
+
// a paragraph — title plus description, which is what the migrated corpus
|
|
37
|
+
// needs — or an open block, when it also carries an image:
|
|
38
|
+
//
|
|
39
|
+
// [cards,type=image-portrait,columns="1 s:2 m:3",width=container]
|
|
40
|
+
// ====
|
|
41
|
+
// [card,icon="business/file-outlined",subheader="Getting started",labels="java, spring"]
|
|
42
|
+
// .xref:quickstart.adoc[Quickstart]
|
|
43
|
+
// --
|
|
44
|
+
// image::quickstart.svg[Woven cloth, folded]
|
|
45
|
+
//
|
|
46
|
+
// Create your first application with AMIGA Framework Java.
|
|
47
|
+
// --
|
|
48
|
+
//
|
|
49
|
+
// [card]
|
|
50
|
+
// .xref:sdk:index.adoc[SDK]
|
|
51
|
+
// The headless library the canvas is built on.
|
|
52
|
+
// ====
|
|
53
|
+
//
|
|
54
|
+
// The block title carries the link, exactly as the dlist term did in this
|
|
55
|
+
// block's first form: Asciidoctor runs inline substitutions over a title, so
|
|
56
|
+
// an `xref:` arrives here already converted to an `<a>`, and Antora's own
|
|
57
|
+
// page resolution is used rather than reimplemented. The image is a real
|
|
58
|
+
// `image::` macro for the same reason — the resource ID resolves through
|
|
59
|
+
// Antora, and alt text is native AsciiDoc rather than a second attribute.
|
|
60
|
+
//
|
|
61
|
+
// Why the open block is required for the image form: `[card]` on an
|
|
62
|
+
// `image::` line is consumed as the BLOCK MACRO's own style and never
|
|
63
|
+
// reaches `getStyle()` (measured: the block reports `style: null` and its
|
|
64
|
+
// first positional attribute is the image's alt text), so an image can never
|
|
65
|
+
// itself be the card marker. An open block keeps the style, keeps the title,
|
|
66
|
+
// and delimits the card explicitly instead of relying on a "blocks until the
|
|
67
|
+
// next marker" heuristic.
|
|
68
|
+
|
|
69
|
+
const TYPES = ['no-image', 'image-landscape', 'image-square', 'image-portrait']
|
|
70
|
+
const DEFAULT_TYPE = 'no-image'
|
|
71
|
+
|
|
72
|
+
const WIDTHS = ['content', 'container']
|
|
73
|
+
const DEFAULT_WIDTH = 'content'
|
|
74
|
+
|
|
75
|
+
// `xs` is the base, unprefixed, so it has no name here — a bare `3` sets the
|
|
76
|
+
// base count. The rest are the PROJECT's breakpoints (ids-breakpoints.css:
|
|
77
|
+
// s 513, m 1240, l 1680), not the design system package's own, and the
|
|
78
|
+
// classes they map to are mobile-first and non-exclusive, like the DS grid's
|
|
79
|
+
// `--span-*` utilities.
|
|
80
|
+
const BREAKPOINTS = ['s', 'm', 'l']
|
|
81
|
+
const DEFAULT_COLUMNS = '1 s:2 m:3'
|
|
82
|
+
const MAX_COLUMNS = 4
|
|
83
|
+
|
|
84
|
+
const COLUMN_RX = /^(?:([a-z]+):)?([0-9]+)$/
|
|
85
|
+
// `group/name`, both lowercase and hyphen-separated — the shape
|
|
86
|
+
// `icons.yml` uses. Whether that icon is actually MASKED is checked by
|
|
87
|
+
// ui-bundle's own `just icons-build` (scripts/build-sprite.mjs), which owns
|
|
88
|
+
// the manifest; duplicating the list here would be a second source of truth
|
|
89
|
+
// that drifts. An icon that passes the shape check but has no mask falls
|
|
90
|
+
// back to a visible marker in card-grid.css rather than rendering nothing.
|
|
91
|
+
const ICON_RX = /^[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
92
|
+
|
|
93
|
+
// The first `href` of the converted title, which is the card's own link.
|
|
94
|
+
const HREF_RX = /<a\b[^>]*\bhref="([^"]*)"/i
|
|
95
|
+
// Lifted whole from the converted image block rather than rebuilt from the
|
|
96
|
+
// target: the host converter is what resolves an image URL (Antora's
|
|
97
|
+
// `imagesdir` per page, its own resource resolution), and `getImageUri` does
|
|
98
|
+
// not apply `imagesdir` unless handed the asset key explicitly. Taking the
|
|
99
|
+
// converter's own `<img>` also keeps the alt, width and height it derived.
|
|
100
|
+
const IMG_RX = /<img\b[^>]*>/i
|
|
101
|
+
|
|
102
|
+
/** `image-portrait` -> `portrait`; `no-image` has no modifier of its own. */
|
|
103
|
+
const typeModifier = (type) => (type === 'no-image' ? '' : ' docouture-card--' + type.slice('image-'.length))
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Parse the `columns` spec into grid classes.
|
|
107
|
+
*
|
|
108
|
+
* `"1 s:2 m:3"` -> base 1, from `s` 2, from `m` 3. Anything malformed, out of
|
|
109
|
+
* range, or naming a breakpoint that does not exist is an authoring error and
|
|
110
|
+
* fails the build.
|
|
111
|
+
*/
|
|
112
|
+
function parseColumns(spec, parent) {
|
|
113
|
+
const classes = []
|
|
114
|
+
for (const token of String(spec).trim().split(/\s+/)) {
|
|
115
|
+
if (!token) continue
|
|
116
|
+
const match = COLUMN_RX.exec(token)
|
|
117
|
+
if (!match) {
|
|
118
|
+
warn(parent, '[cards,columns="' + spec + '"]', 'cannot read the column "' + token + '"; expected `3` or `m:3`')
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
const [, breakpoint, count] = match
|
|
122
|
+
if (breakpoint && !BREAKPOINTS.includes(breakpoint)) {
|
|
123
|
+
warn(parent, '[cards,columns="' + spec + '"]', 'unknown breakpoint "' + breakpoint + '"', BREAKPOINTS)
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
const columns = Number(count)
|
|
127
|
+
if (columns < 1 || columns > MAX_COLUMNS) {
|
|
128
|
+
warn(
|
|
129
|
+
parent,
|
|
130
|
+
'[cards,columns="' + spec + '"]',
|
|
131
|
+
columns + ' columns is outside the supported range 1-' + MAX_COLUMNS
|
|
132
|
+
)
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
classes.push('docouture-card-grid--cols-' + (breakpoint ? breakpoint + '-' : '') + columns)
|
|
136
|
+
}
|
|
137
|
+
return classes
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The image blocks and the body blocks of one card, whichever form it took. */
|
|
141
|
+
function cardParts(block) {
|
|
142
|
+
if (block.getContext() !== 'open') return { images: [], bodies: [block] }
|
|
143
|
+
const images = []
|
|
144
|
+
const bodies = []
|
|
145
|
+
for (const child of block.getBlocks()) {
|
|
146
|
+
if (child.getContext() === 'image') images.push(child)
|
|
147
|
+
else bodies.push(child)
|
|
148
|
+
}
|
|
149
|
+
return { images, bodies }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function renderHeader(icon, subheader, parent) {
|
|
153
|
+
if (!icon && !subheader) return ''
|
|
154
|
+
let iconHtml = ''
|
|
155
|
+
if (icon) {
|
|
156
|
+
if (ICON_RX.test(icon)) {
|
|
157
|
+
iconHtml =
|
|
158
|
+
'<span class="docouture-card__icon ids-icon-mask--' +
|
|
159
|
+
escapeHtml(icon.replace('/', '-')) +
|
|
160
|
+
'" aria-hidden="true"></span>'
|
|
161
|
+
} else {
|
|
162
|
+
warn(
|
|
163
|
+
parent,
|
|
164
|
+
'[card,icon="' + icon + '"]',
|
|
165
|
+
'not an icon reference; expected `group/name`, e.g. `business/file-outlined`'
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const subheaderHtml = subheader ? '<span class="docouture-card__subheader">' + escapeHtml(subheader) + '</span>' : ''
|
|
170
|
+
return '<div class="docouture-card__header">' + iconHtml + subheaderHtml + '</div>'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function renderMeta(labels) {
|
|
174
|
+
const chips = String(labels || '')
|
|
175
|
+
.split(',')
|
|
176
|
+
.map((label) => label.trim())
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
if (!chips.length) return ''
|
|
179
|
+
return (
|
|
180
|
+
'<div class="docouture-card__meta">' +
|
|
181
|
+
chips
|
|
182
|
+
.map(
|
|
183
|
+
(label) =>
|
|
184
|
+
'<span class="ids-label ids-label--grey"><span class="ids-label__content">' +
|
|
185
|
+
escapeHtml(label) +
|
|
186
|
+
'</span></span>'
|
|
187
|
+
)
|
|
188
|
+
.join('') +
|
|
189
|
+
'</div>'
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function buildCardHtml({ type, imageHtml, header, title, description, meta }) {
|
|
194
|
+
return (
|
|
195
|
+
'<div class="ids-card ids-card--vertical docouture-card' +
|
|
196
|
+
typeModifier(type) +
|
|
197
|
+
'">' +
|
|
198
|
+
imageHtml +
|
|
199
|
+
'<div class="ids-card__content">' +
|
|
200
|
+
header +
|
|
201
|
+
'<div class="docouture-card__main">' +
|
|
202
|
+
'<div class="docouture-card__title">' +
|
|
203
|
+
title +
|
|
204
|
+
'</div>' +
|
|
205
|
+
description +
|
|
206
|
+
'</div>' +
|
|
207
|
+
meta +
|
|
208
|
+
'</div>' +
|
|
209
|
+
'</div>'
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function renderCard(block, type, parent) {
|
|
214
|
+
// NOT escaped, deliberately — `getTitle()` returns CONVERTED HTML, not
|
|
215
|
+
// text. The `xref:` this block is built around arrives as
|
|
216
|
+
// `<a href="...">Label</a>`, so escaping it would render the anchor as
|
|
217
|
+
// visible source and break every card link. See lib/html.js's header for
|
|
218
|
+
// which strings do need escaping (raw block attributes) and which are
|
|
219
|
+
// already safe.
|
|
220
|
+
const title = block.getTitle()
|
|
221
|
+
if (!title) {
|
|
222
|
+
warn(parent, '[card]', 'a card has no title; give it a `.Title` line carrying the link')
|
|
223
|
+
return ''
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const href = (HREF_RX.exec(title) || [])[1]
|
|
227
|
+
if (!href) {
|
|
228
|
+
warn(
|
|
229
|
+
parent,
|
|
230
|
+
'[card] ' + block.getAttribute('title'),
|
|
231
|
+
'a card title carries no link; make it an `xref:` or a `link:`'
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const { images, bodies } = cardParts(block)
|
|
236
|
+
if (type === 'no-image' && images.length) {
|
|
237
|
+
warn(parent, '[card]', 'this card has an image but the block is `type=' + type + '`', TYPES.slice(1))
|
|
238
|
+
}
|
|
239
|
+
if (type !== 'no-image' && !images.length) {
|
|
240
|
+
warn(parent, '[card]', 'a `type=' + type + '` card needs an `image::` of its own')
|
|
241
|
+
}
|
|
242
|
+
if (images.length > 1) {
|
|
243
|
+
warn(parent, '[card]', 'a card has ' + images.length + ' images; only the first is rendered')
|
|
244
|
+
}
|
|
245
|
+
for (const body of bodies) {
|
|
246
|
+
if (body.getContext() !== 'paragraph') {
|
|
247
|
+
warn(parent, '[card]', 'a card body holds a ' + body.getContext() + ' block; only paragraphs are supported')
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const parts = []
|
|
252
|
+
parts.push(images.length ? images[0].convert() : '')
|
|
253
|
+
bodies.forEach((body) => parts.push(body.getContent()))
|
|
254
|
+
|
|
255
|
+
return chainAll(parts, ([converted, ...texts]) => {
|
|
256
|
+
const image = converted ? (IMG_RX.exec(converted) || [])[0] : ''
|
|
257
|
+
const imageHtml = image ? '<div class="ids-card__image docouture-card__image">' + image + '</div>' : ''
|
|
258
|
+
const description = texts
|
|
259
|
+
.filter(Boolean)
|
|
260
|
+
.map((text) => '<p>' + text + '</p>')
|
|
261
|
+
.join('')
|
|
262
|
+
return buildCardHtml({
|
|
263
|
+
type,
|
|
264
|
+
imageHtml,
|
|
265
|
+
header: renderHeader(block.getAttribute('icon'), block.getAttribute('subheader'), parent),
|
|
266
|
+
title,
|
|
267
|
+
description,
|
|
268
|
+
meta: renderMeta(block.getAttribute('labels')),
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function finish(parent, wrapper, attrs, self) {
|
|
274
|
+
const type = attrs.type || DEFAULT_TYPE
|
|
275
|
+
if (!TYPES.includes(type)) {
|
|
276
|
+
warn(parent, '[cards,type=' + attrs.type + ']', 'unknown card type "' + attrs.type + '"', TYPES)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const width = attrs.width || DEFAULT_WIDTH
|
|
280
|
+
if (!WIDTHS.includes(width)) {
|
|
281
|
+
warn(parent, '[cards,width=' + attrs.width + ']', 'unknown width "' + attrs.width + '"', WIDTHS)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const cards = wrapper.getBlocks().filter((block) => block.getStyle() === 'card')
|
|
285
|
+
if (!cards.length) {
|
|
286
|
+
warn(parent, '[cards]', 'a cards block with no `[card]` in it')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const cardHtmls = cards.map((card) => renderCard(card, TYPES.includes(type) ? type : DEFAULT_TYPE, parent))
|
|
290
|
+
|
|
291
|
+
return chainAll(cardHtmls, (rendered) => {
|
|
292
|
+
const classes = ['docouture-card-grid']
|
|
293
|
+
.concat(parseColumns(attrs.columns || DEFAULT_COLUMNS, parent))
|
|
294
|
+
.concat('docouture-card-grid--width-' + (WIDTHS.includes(width) ? width : DEFAULT_WIDTH))
|
|
295
|
+
const html = '<div class="' + classes.join(' ') + '">' + rendered.join('') + '</div>'
|
|
296
|
+
return self.createBlock(parent, 'pass', html, attrs)
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function cardsBlock() {
|
|
301
|
+
this.named('cards')
|
|
302
|
+
this.onContext('example')
|
|
303
|
+
this.process((parent, reader, attrs) => {
|
|
304
|
+
// See steps.js's own comment: Opal (2.2) can hand this a bare JS `null`
|
|
305
|
+
// for a block with no attributes beyond its style, and `createBlock`
|
|
306
|
+
// crashes on that.
|
|
307
|
+
attrs = attrs || {}
|
|
308
|
+
// See steps.js's own comment: a literal JS `null` "source" crashes Opal
|
|
309
|
+
// (2.2) inside `Block#initialize`'s `.nil_or_empty?()` check.
|
|
310
|
+
const wrapper = this.createBlock(parent, 'open', '', attrs)
|
|
311
|
+
// See label-macro.js's own comment on this same pattern.
|
|
312
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
313
|
+
const self = this
|
|
314
|
+
// See async-compat.js's own header comment: parseContent is sync under
|
|
315
|
+
// 2.2 (Opal, real Antora builds) and Promise-returning under 4.0 (the
|
|
316
|
+
// ui-bundle preview harness) — chain() handles either without making
|
|
317
|
+
// this function `async` unconditionally.
|
|
318
|
+
return chain(this.parseContent(wrapper, reader.getLines()), () =>
|
|
319
|
+
chain(precomputeSubtree(wrapper), () => finish(parent, wrapper, attrs, self))
|
|
320
|
+
)
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = function registerCardGrid(registry) {
|
|
325
|
+
registry.block(cardsBlock)
|
|
326
|
+
}
|
|
327
|
+
module.exports.cardsBlock = cardsBlock
|