@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
package/lib/cta.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { chain, chainAll, precomputeSubtree } = require('./async-compat')
|
|
4
|
+
const { escapeHtml, attr } = require('./html')
|
|
5
|
+
const warn = require('./warn')
|
|
6
|
+
|
|
7
|
+
// Call to action — a full-width band closing out a landing section, styled
|
|
8
|
+
// after Fumadocs' "Free & Open Source" block on the Weave.js docs home
|
|
9
|
+
// (`app/(home)/page.tsx`'s `OpenSource()`) rather than after any Figma frame:
|
|
10
|
+
// GH-23 notes this block has none. Composed from primitives already vendored
|
|
11
|
+
// for the landing (button, text) — no new DS component, no new CSS import
|
|
12
|
+
// beyond this file.
|
|
13
|
+
//
|
|
14
|
+
// WHY IT DEVIATES FROM THE ISSUE THAT PROPOSED IT
|
|
15
|
+
//
|
|
16
|
+
// GH-23 sketched `cta::[primary-url="…"]`, a block MACRO with the target as a
|
|
17
|
+
// raw attribute string. That can never resolve an `xref:` to a page in this
|
|
18
|
+
// site — only Asciidoctor itself does that, during its own inline
|
|
19
|
+
// substitution — which is the exact reason card-grid.js's card link and
|
|
20
|
+
// feature-tabs.js's slide CTA are both roled PARAGRAPHS instead of attributes.
|
|
21
|
+
// This block makes the same call, for the same reason: `[.primary]`/
|
|
22
|
+
// `[.secondary]` paragraphs carrying a real `xref:`/`link:`, resolved by the
|
|
23
|
+
// time this file ever sees them.
|
|
24
|
+
//
|
|
25
|
+
// It also drops the issue's inverse surface (`--ids-color-bg-inverse` +
|
|
26
|
+
// `--ids-color-content-inverse`) and its eyebrow chip, neither of which the
|
|
27
|
+
// Fumadocs reference has. An inverse band would need a real fix for its
|
|
28
|
+
// buttons too — the DS ships no inverse button variant, so a plain
|
|
29
|
+
// `.ids-button--primary` resolves BLACK text on a BLACK band in light theme
|
|
30
|
+
// (`--ids-comp-button-primary-default`). Fixing that means widening
|
|
31
|
+
// `tools/ids/sync.mjs`'s THEME_SCOPES the way the dark code-block surface
|
|
32
|
+
// already does (see that file's own comment) — deferred; this block instead
|
|
33
|
+
// sits on `--ids-color-bg-low`, which needs none of that: DS buttons already
|
|
34
|
+
// read correctly against it in both themes, no scope-inversion, no new
|
|
35
|
+
// token, no rule at all separating the band from the page.
|
|
36
|
+
//
|
|
37
|
+
// SYNTAX
|
|
38
|
+
//
|
|
39
|
+
// A `[cta]` example block, authored as its own `==` SECTION — like every
|
|
40
|
+
// other landing block (home.css's own header: "the content column is a
|
|
41
|
+
// stack of blocks... each a section of the AsciiDoc document"), it needs a
|
|
42
|
+
// real section heading to get the landing's uniform dashed-rule band, and
|
|
43
|
+
// that heading also names it in the document outline. `title=` is an
|
|
44
|
+
// optional block-style attribute (raw, so escaped) for a SECOND, larger line
|
|
45
|
+
// inside the band itself — the reference draws only one, so most callers
|
|
46
|
+
// need neither this nor a `.Title` line (which would attach to the image
|
|
47
|
+
// anyway, not to this wrapper — the same "an image consumes the marker
|
|
48
|
+
// immediately touching it" trap card-grid.js and feature-tabs.js hit for a
|
|
49
|
+
// block's STYLE, here for a title instead).
|
|
50
|
+
//
|
|
51
|
+
// Everything else is prose paragraphs, an optional pair of `image::` marks
|
|
52
|
+
// (light and `role=dark`, same split as feature-tabs.js's media still), and
|
|
53
|
+
// one or two role-marked action paragraphs:
|
|
54
|
+
//
|
|
55
|
+
// == Free & open source
|
|
56
|
+
//
|
|
57
|
+
// [cta]
|
|
58
|
+
// ====
|
|
59
|
+
// image::product-logo.svg[Product mark]
|
|
60
|
+
// image::product-logo-dark.svg[role=dark]
|
|
61
|
+
//
|
|
62
|
+
// Actively maintained and open for contributions. Comes with
|
|
63
|
+
// best-in-class documentation and developer experience.
|
|
64
|
+
//
|
|
65
|
+
// [.primary]
|
|
66
|
+
// https://github.com/InditexTech/weavejs-frontend/fork[Create a fork]
|
|
67
|
+
// ====
|
|
68
|
+
//
|
|
69
|
+
// Parts are emitted in a fixed order — title, prose, mark, actions —
|
|
70
|
+
// regardless of the order they were authored in, the same discipline
|
|
71
|
+
// feature-tabs.js applies to a slide: this is a fixed composition, not a
|
|
72
|
+
// flow of blocks, and an author reordering them by accident should not
|
|
73
|
+
// produce a layout nothing drew.
|
|
74
|
+
//
|
|
75
|
+
// `align=` on the block itself switches the inner column between
|
|
76
|
+
// `center` (default, the reference) and `start`.
|
|
77
|
+
|
|
78
|
+
/** The role marking the block's primary call-to-action paragraph. */
|
|
79
|
+
const PRIMARY_ROLE = 'primary'
|
|
80
|
+
/** The role marking the block's secondary call-to-action paragraph. */
|
|
81
|
+
const SECONDARY_ROLE = 'secondary'
|
|
82
|
+
/** The role marking a mark image meant for the dark theme. */
|
|
83
|
+
const DARK_ROLE = 'dark'
|
|
84
|
+
|
|
85
|
+
const ALIGNMENTS = ['center', 'start']
|
|
86
|
+
const DEFAULT_ALIGN = 'center'
|
|
87
|
+
|
|
88
|
+
/** The converter's own `<img>`, lifted whole — see card-grid.js/feature-tabs.js's identical note on why. */
|
|
89
|
+
const IMG_RX = /<img\b[^>]*>/i
|
|
90
|
+
|
|
91
|
+
/** The first anchor of a converted action paragraph: its href and its label. */
|
|
92
|
+
const CTA_RX = /<a\b[^>]*\bhref="([^"]*)"[^>]*>([\s\S]*?)<\/a>/i
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A target on github.com — derived from the href, never authored. GitHub has
|
|
96
|
+
* no place in `icon-masks.css`: that file is generated exclusively from the
|
|
97
|
+
* design system's own icon mirror (its own header, `Do not edit by hand`),
|
|
98
|
+
* and this is a brand mark, not a DS icon — so it is the one hand-authored
|
|
99
|
+
* mask this package carries, in cta.css, and lives only there. No generic
|
|
100
|
+
* external-link icon accompanies it (there was one; removed) — a second,
|
|
101
|
+
* unbranded arrow beside the mark read as redundant rather than
|
|
102
|
+
* informative.
|
|
103
|
+
*/
|
|
104
|
+
const GITHUB_RX = /^https?:\/\/(?:www\.)?github\.com\//i
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* An Asciidoctor block, in whichever major is running.
|
|
108
|
+
*
|
|
109
|
+
* @typedef {import('@asciidoctor/core').AbstractBlock} Block
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The block's children, sorted into the four roles it has.
|
|
114
|
+
*
|
|
115
|
+
* @param {Block} wrapper - the parsed `[cta]` content.
|
|
116
|
+
* @returns {{ images: Block[], primaries: Block[], secondaries: Block[], bodies: Block[] }}
|
|
117
|
+
*/
|
|
118
|
+
function ctaParts(wrapper) {
|
|
119
|
+
/** @type {Block[]} */
|
|
120
|
+
const images = []
|
|
121
|
+
/** @type {Block[]} */
|
|
122
|
+
const primaries = []
|
|
123
|
+
/** @type {Block[]} */
|
|
124
|
+
const secondaries = []
|
|
125
|
+
/** @type {Block[]} */
|
|
126
|
+
const bodies = []
|
|
127
|
+
for (const child of wrapper.getBlocks()) {
|
|
128
|
+
if (child.getContext() === 'image') images.push(child)
|
|
129
|
+
else if (child.hasRole(PRIMARY_ROLE)) primaries.push(child)
|
|
130
|
+
else if (child.hasRole(SECONDARY_ROLE)) secondaries.push(child)
|
|
131
|
+
else bodies.push(child)
|
|
132
|
+
}
|
|
133
|
+
return { images, primaries, secondaries, bodies }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Splits the mark images into the light still and its optional dark
|
|
138
|
+
* counterpart — the same split, and the same reason, as feature-tabs.js's
|
|
139
|
+
* `splitImages`.
|
|
140
|
+
*
|
|
141
|
+
* @param {Block[]} images
|
|
142
|
+
* @param {Block} parent - for warnings.
|
|
143
|
+
* @returns {{ light: Block | undefined, dark: Block | undefined }}
|
|
144
|
+
*/
|
|
145
|
+
function splitImages(images, parent) {
|
|
146
|
+
const dark = images.filter((image) => image.hasRole(DARK_ROLE))
|
|
147
|
+
const light = images.filter((image) => !image.hasRole(DARK_ROLE))
|
|
148
|
+
if (light.length > 1) {
|
|
149
|
+
warn(parent, '[cta]', 'a cta has ' + light.length + ' mark images; only the first is rendered')
|
|
150
|
+
}
|
|
151
|
+
if (dark.length > 1) {
|
|
152
|
+
warn(parent, '[cta]', 'a cta has ' + dark.length + ' `role=dark` mark images; only the first is rendered')
|
|
153
|
+
}
|
|
154
|
+
if (dark.length && !light.length) {
|
|
155
|
+
warn(parent, '[cta]', 'a cta has a `role=dark` mark image but no mark for the light theme')
|
|
156
|
+
}
|
|
157
|
+
return { light: light[0], dark: dark[0] }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The mark: the light still, and the dark one when there is one — laid out
|
|
162
|
+
* exactly like feature-tabs.js's `renderMedia`.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} lightHtml
|
|
165
|
+
* @param {string} darkHtml
|
|
166
|
+
* @returns {string}
|
|
167
|
+
*/
|
|
168
|
+
function renderMark(lightHtml, darkHtml) {
|
|
169
|
+
const light = lightHtml ? (IMG_RX.exec(lightHtml) || [])[0] : ''
|
|
170
|
+
const dark = darkHtml ? (IMG_RX.exec(darkHtml) || [])[0] : ''
|
|
171
|
+
if (!light && !dark) return ''
|
|
172
|
+
const themed = light && dark
|
|
173
|
+
return (
|
|
174
|
+
'<div class="docouture-cta__mark">' +
|
|
175
|
+
(light
|
|
176
|
+
? light.replace(
|
|
177
|
+
'<img',
|
|
178
|
+
'<img class="docouture-cta__mark-image' + (themed ? ' docouture-cta__mark-image--light' : '') + '"'
|
|
179
|
+
)
|
|
180
|
+
: '') +
|
|
181
|
+
(dark ? dark.replace('<img', '<img class="docouture-cta__mark-image docouture-cta__mark-image--dark"') : '') +
|
|
182
|
+
'</div>'
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* One action as a real DS Button, built around the converted paragraph's
|
|
188
|
+
* own href and label — the same construction as feature-tabs.js's
|
|
189
|
+
* `renderCta`, applied to either role.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} converted - the converted action paragraph, or `''`.
|
|
192
|
+
* @param {'primary' | 'secondary'} variant
|
|
193
|
+
* @param {string} role - `[.primary]` or `[.secondary]`, for warnings.
|
|
194
|
+
* @param {Block} parent
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
function renderAction(converted, variant, role, parent) {
|
|
198
|
+
if (!converted) return ''
|
|
199
|
+
const match = CTA_RX.exec(converted)
|
|
200
|
+
if (!match) {
|
|
201
|
+
warn(parent, '[.' + role + ']', 'a cta action carries no link; make it an `xref:` or a `link:`')
|
|
202
|
+
return ''
|
|
203
|
+
}
|
|
204
|
+
const href = match[1] || ''
|
|
205
|
+
const label = match[2] || ''
|
|
206
|
+
const github = GITHUB_RX.test(href)
|
|
207
|
+
return (
|
|
208
|
+
'<a class="ids-button ids-button--' +
|
|
209
|
+
variant +
|
|
210
|
+
' docouture-cta__action' +
|
|
211
|
+
(github ? ' ids-button--icon-and-label ids-button--icon-icon' : '') +
|
|
212
|
+
'"' +
|
|
213
|
+
attr('href', href) +
|
|
214
|
+
'>' +
|
|
215
|
+
'<span class="ids-button__content">' +
|
|
216
|
+
(github
|
|
217
|
+
? '<span class="ids-button__icon ids-button__icon-icon docouture-cta__action-icon ' +
|
|
218
|
+
'docouture-cta__action-icon--github" aria-hidden="true"></span>'
|
|
219
|
+
: '') +
|
|
220
|
+
label +
|
|
221
|
+
'</span>' +
|
|
222
|
+
'</a>'
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Assembles the block once every part has been rendered.
|
|
228
|
+
*
|
|
229
|
+
* @param {Block} parent - the block this replaces.
|
|
230
|
+
* @param {Block} wrapper - the parsed `[cta]` content.
|
|
231
|
+
* @param {object} attrs - the block's own attributes.
|
|
232
|
+
* @param {{ createBlock: Function }} self
|
|
233
|
+
* @returns {object | Promise<object>}
|
|
234
|
+
*/
|
|
235
|
+
function finish(parent, wrapper, attrs, self) {
|
|
236
|
+
const align = attrs.align || DEFAULT_ALIGN
|
|
237
|
+
if (!ALIGNMENTS.includes(align)) {
|
|
238
|
+
warn(parent, '[cta,align=' + attrs.align + ']', 'unknown alignment "' + attrs.align + '"', ALIGNMENTS)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// A block-style attribute, so raw, so escaped — see the header comment on
|
|
242
|
+
// why this is optional: the real heading is the `==` section's own, not
|
|
243
|
+
// this.
|
|
244
|
+
const title = escapeHtml(attrs.title)
|
|
245
|
+
|
|
246
|
+
const { images, primaries, secondaries, bodies } = ctaParts(wrapper)
|
|
247
|
+
if (!primaries.length) {
|
|
248
|
+
warn(parent, '[cta]', 'a cta has no `[.primary]` action')
|
|
249
|
+
}
|
|
250
|
+
if (primaries.length > 1) {
|
|
251
|
+
warn(parent, '[cta]', 'a cta has ' + primaries.length + ' `[.primary]` paragraphs; only the first is rendered')
|
|
252
|
+
}
|
|
253
|
+
if (secondaries.length > 1) {
|
|
254
|
+
warn(parent, '[cta]', 'a cta has ' + secondaries.length + ' `[.secondary]` paragraphs; only the first is rendered')
|
|
255
|
+
}
|
|
256
|
+
if (secondaries.length && !primaries.length) {
|
|
257
|
+
warn(parent, '[cta]', 'a cta has a `[.secondary]` action but no `[.primary]` one')
|
|
258
|
+
}
|
|
259
|
+
for (const body of bodies) {
|
|
260
|
+
if (body.getContext() !== 'paragraph') {
|
|
261
|
+
warn(parent, '[cta]', 'a cta body holds a ' + body.getContext() + ' block; only paragraphs are supported')
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const { light, dark } = splitImages(images, parent)
|
|
265
|
+
|
|
266
|
+
const primary = primaries[0]
|
|
267
|
+
const secondary = secondaries[0]
|
|
268
|
+
const parts = [
|
|
269
|
+
light ? light.convert() : '',
|
|
270
|
+
dark ? dark.convert() : '',
|
|
271
|
+
primary ? primary.getContent() : '',
|
|
272
|
+
secondary ? secondary.getContent() : '',
|
|
273
|
+
]
|
|
274
|
+
bodies.forEach((body) => parts.push(body.getContent()))
|
|
275
|
+
|
|
276
|
+
return chainAll(parts, ([lightHtml, darkHtml, primaryHtml, secondaryHtml, ...bodyHtmls]) => {
|
|
277
|
+
const titleHtml = title ? '<h2 class="docouture-cta__title discrete ids-text--title-l">' + title + '</h2>' : ''
|
|
278
|
+
const leadHtml = bodyHtmls
|
|
279
|
+
.filter(Boolean)
|
|
280
|
+
.map((text) => '<p class="docouture-cta__lead ids-text--body-l">' + text + '</p>')
|
|
281
|
+
.join('')
|
|
282
|
+
const markHtml = renderMark(lightHtml, darkHtml)
|
|
283
|
+
const actionsHtml =
|
|
284
|
+
primaryHtml || secondaryHtml
|
|
285
|
+
? '<div class="docouture-cta__actions">' +
|
|
286
|
+
renderAction(primaryHtml, 'primary', 'primary', parent) +
|
|
287
|
+
renderAction(secondaryHtml, 'secondary', 'secondary', parent) +
|
|
288
|
+
'</div>'
|
|
289
|
+
: ''
|
|
290
|
+
|
|
291
|
+
const html =
|
|
292
|
+
'<div class="docouture-cta docouture-cta--' +
|
|
293
|
+
(ALIGNMENTS.includes(align) ? align : DEFAULT_ALIGN) +
|
|
294
|
+
'">' +
|
|
295
|
+
'<div class="docouture-cta__body">' +
|
|
296
|
+
titleHtml +
|
|
297
|
+
leadHtml +
|
|
298
|
+
'</div>' +
|
|
299
|
+
markHtml +
|
|
300
|
+
actionsHtml +
|
|
301
|
+
'</div>'
|
|
302
|
+
return self.createBlock(parent, 'pass', html, attrs)
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function ctaBlock() {
|
|
307
|
+
this.named('cta')
|
|
308
|
+
this.onContext('example')
|
|
309
|
+
this.process((parent, reader, attrs) => {
|
|
310
|
+
// See steps.js's own comment: Opal (2.2) can hand this a bare JS `null`
|
|
311
|
+
// for a block with no attributes beyond its style, and `createBlock`
|
|
312
|
+
// crashes on that.
|
|
313
|
+
attrs = attrs || {}
|
|
314
|
+
// See steps.js's own comment: a literal JS `null` "source" crashes Opal
|
|
315
|
+
// (2.2) inside `Block#initialize`'s `.nil_or_empty?()` check.
|
|
316
|
+
const wrapper = this.createBlock(parent, 'open', '', attrs)
|
|
317
|
+
// See label-macro.js's own comment on this same pattern.
|
|
318
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
319
|
+
const self = this
|
|
320
|
+
// See async-compat.js's own header comment: parseContent is sync under
|
|
321
|
+
// 2.2 (Opal, real Antora builds) and Promise-returning under 4.0 (the
|
|
322
|
+
// ui-bundle preview harness) — chain() handles either without making
|
|
323
|
+
// this function `async` unconditionally. precomputeSubtree is what makes
|
|
324
|
+
// a `.Title` carrying inline markup arrive converted rather than raw.
|
|
325
|
+
return chain(this.parseContent(wrapper, reader.getLines()), () =>
|
|
326
|
+
chain(precomputeSubtree(wrapper), () => finish(parent, wrapper, attrs, self))
|
|
327
|
+
)
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
module.exports = function registerCta(registry) {
|
|
332
|
+
registry.block(ctaBlock)
|
|
333
|
+
}
|
|
334
|
+
module.exports.ctaBlock = ctaBlock
|