@redpanda-data/docs-extensions-and-macros 5.8.0 → 5.9.0
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/bin/doc-tools.js +168 -0
- package/extensions/convert-to-markdown.js +115 -0
- package/extensions/property-page-index.js +44 -0
- package/extensions/validate-attributes.js +1 -1
- package/macros/PROPERTY_AND_ENTERPRISE_REFERENCES.adoc +90 -0
- package/macros/README.adoc +2 -0
- package/macros/REFERENCE.adoc +167 -0
- package/macros/badge.js +35 -13
- package/macros/enterprise.js +390 -0
- package/macros/prop.js +491 -0
- package/package.json +6 -2
- package/tools/enterprise-features/parsers.js +134 -0
- package/tools/enterprise-features/verify.js +244 -0
- package/tools/generate-cli-docs.js +19 -1
- package/tools/migrate-property-refs.js +206 -0
- package/tools/property-extractor/generate-handlebars-docs.js +28 -1
- package/tools/property-extractor/property_extractor.py +34 -0
package/macros/badge.js
CHANGED
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Build the HTML for a badge.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from the macro body so other macros can render an identical badge
|
|
7
|
+
* without depending on the badge macro being registered in the consuming
|
|
8
|
+
* playbook. The enterprise macro uses it to render the beta badge for registry
|
|
9
|
+
* entries marked `beta: true`, which keeps one definition of the markup and
|
|
10
|
+
* the CSS class names.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} opts
|
|
13
|
+
* @param {string} [opts.label] - Badge text.
|
|
14
|
+
* @param {string} [opts.size] - `large` renders without surrounding brackets.
|
|
15
|
+
* @param {string} [opts.tooltip] - Tooltip text, rendered as `data-tooltip`.
|
|
16
|
+
* @returns {string} Badge HTML.
|
|
17
|
+
*/
|
|
18
|
+
function buildBadgeHtml ({ label, size, tooltip } = {}) {
|
|
19
|
+
const text = label || 'label';
|
|
20
|
+
const className = `badge--${text.toLowerCase().replace(/\s+/g, '-')}`;
|
|
21
|
+
const isLarge = size === 'large';
|
|
22
|
+
const sizeClass = isLarge ? 'badge--large' : '';
|
|
23
|
+
const tooltipAttr = tooltip ? ` data-tooltip="${tooltip}"` : '';
|
|
24
|
+
|
|
25
|
+
// Add brackets if not large. The brackets matter beyond styling: they are
|
|
26
|
+
// real text content, so the label survives an HTML-to-Markdown conversion as
|
|
27
|
+
// "(beta)" rather than vanishing with the CSS class.
|
|
28
|
+
const renderedLabel = isLarge ? text : `(${text})`;
|
|
29
|
+
|
|
30
|
+
return `<span class="badge ${className} ${sizeClass}"${tooltipAttr}>${renderedLabel}</span>`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports.buildBadgeHtml = buildBadgeHtml;
|
|
34
|
+
|
|
3
35
|
module.exports.register = function (registry) {
|
|
4
36
|
registry.inlineMacro(function () {
|
|
5
37
|
const self = this;
|
|
6
38
|
self.named('badge');
|
|
7
|
-
self.process((parent, target, attrs) =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const isLarge = attrs.size === 'large';
|
|
11
|
-
const sizeClass = isLarge ? 'badge--large' : '';
|
|
12
|
-
const tooltip = attrs.tooltip;
|
|
13
|
-
const tooltipAttr = tooltip ? ` data-tooltip="${tooltip}"` : '';
|
|
14
|
-
|
|
15
|
-
// Add brackets if not large
|
|
16
|
-
const renderedLabel = isLarge ? label : `(${label})`;
|
|
17
|
-
|
|
18
|
-
return `<span class="badge ${className} ${sizeClass}"${tooltipAttr}>${renderedLabel}</span>`;
|
|
19
|
-
});
|
|
39
|
+
self.process((parent, target, attrs) =>
|
|
40
|
+
buildBadgeHtml({ label: attrs.label, size: attrs.size, tooltip: attrs.tooltip })
|
|
41
|
+
);
|
|
20
42
|
});
|
|
21
43
|
};
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* Macros for marking and listing enterprise features.
|
|
4
|
+
*
|
|
5
|
+
* Inline macro — mark a feature in prose:
|
|
6
|
+
*
|
|
7
|
+
* enterprise:Continuous Data Balancing[]
|
|
8
|
+
* enterprise:Tiered Storage[xref=manage:tiered-storage.adoc]
|
|
9
|
+
* enterprise:Audit Logging[text=audit logging]
|
|
10
|
+
* enterprise:Iceberg Topics[tooltip=Iceberg Topics requires an Enterprise Edition license and object storage.]
|
|
11
|
+
*
|
|
12
|
+
* The feature name renders as a uniquely styled term with a tooltip that
|
|
13
|
+
* explains the feature requires an Enterprise Edition license. The term
|
|
14
|
+
* links to the feature's documentation when an xref attribute is given,
|
|
15
|
+
* and otherwise to the licensing page, so readers can always reach an
|
|
16
|
+
* explanation of what having (or not having) a license means.
|
|
17
|
+
*
|
|
18
|
+
* Block macro — render the licensing feature table for one scope:
|
|
19
|
+
*
|
|
20
|
+
* enterprise_features::redpanda[]
|
|
21
|
+
* enterprise_features::connect[title=Enterprise features in Redpanda Connect]
|
|
22
|
+
*
|
|
23
|
+
* Registry validation:
|
|
24
|
+
*
|
|
25
|
+
* Both macros read the canonical enterprise features registry from the
|
|
26
|
+
* 'shared' component (modules/ROOT/partials/enterprise-features.yml in the
|
|
27
|
+
* shared folder of the docs repo). Inline targets are resolved against the
|
|
28
|
+
* registry case-insensitively, including aliases, and the canonical name,
|
|
29
|
+
* feature xref, and tooltip come from the matching entry. Unknown targets
|
|
30
|
+
* are reported according to the enterprise-validate attribute. When no
|
|
31
|
+
* registry is available (for example, outside an Antora build), the macros
|
|
32
|
+
* fall back to unvalidated rendering with a single warning.
|
|
33
|
+
*
|
|
34
|
+
* Document or site attributes:
|
|
35
|
+
*
|
|
36
|
+
* enterprise-validate 'warn' (default) to log unknown feature names,
|
|
37
|
+
* 'error' to fail the build, 'off' to disable
|
|
38
|
+
* validation.
|
|
39
|
+
* enterprise-licensing-page Resource ID of the licensing page used when
|
|
40
|
+
* no feature page is known
|
|
41
|
+
* (default: get-started:licensing/overview.adoc).
|
|
42
|
+
* enterprise-feature-role CSS class applied to the wrapping span
|
|
43
|
+
* (default: enterprise-feature).
|
|
44
|
+
* enterprise-tooltip 'title' (default), 'true' (renders
|
|
45
|
+
* data-enterprise-tooltip), any attribute name
|
|
46
|
+
* starting with 'data-', or 'false' to disable.
|
|
47
|
+
* enterprise-links 'true' (default) to render the term as a link.
|
|
48
|
+
*
|
|
49
|
+
* Example use in a playbook:
|
|
50
|
+
*
|
|
51
|
+
* asciidoc:
|
|
52
|
+
* extensions:
|
|
53
|
+
* - '@redpanda-data/docs-extensions-and-macros/macros/enterprise'
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
const yaml = require('js-yaml')
|
|
57
|
+
const chalk = require('chalk')
|
|
58
|
+
const { buildBadgeHtml } = require('./badge')
|
|
59
|
+
|
|
60
|
+
const $enterpriseRegistry = Symbol('$enterpriseRegistry')
|
|
61
|
+
|
|
62
|
+
const DEFAULT_LICENSING_PAGE = 'get-started:licensing/overview.adoc'
|
|
63
|
+
const DEFAULT_ROLE = 'enterprise-feature'
|
|
64
|
+
const BETA_LABEL = 'beta'
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Render the beta badge for a registry entry marked `beta: true`.
|
|
68
|
+
*
|
|
69
|
+
* The badge HTML is built directly rather than emitted as `badge:[...]`
|
|
70
|
+
* AsciiDoc, so it renders whether or not the consuming playbook registers the
|
|
71
|
+
* badge macro. Writing the macro call into a registry field used to be the only
|
|
72
|
+
* way to flag a beta feature, and it silently produced the literal text
|
|
73
|
+
* `badge::[label=beta]` in any build without that macro registered.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} entry - Registry entry.
|
|
76
|
+
* @returns {string} Badge HTML, or an empty string when the entry is not beta.
|
|
77
|
+
*/
|
|
78
|
+
function buildBetaBadge (entry) {
|
|
79
|
+
if (!entry || entry.beta !== true) return ''
|
|
80
|
+
return buildBadgeHtml({
|
|
81
|
+
label: BETA_LABEL,
|
|
82
|
+
tooltip: entry['beta-tooltip'] || undefined,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The badge for a registry entry, wrapped for use in generated AsciiDoc.
|
|
88
|
+
*
|
|
89
|
+
* The block macro returns AsciiDoc source that Asciidoctor then parses, so raw
|
|
90
|
+
* HTML placed in a table cell is escaped and the reader sees the markup as
|
|
91
|
+
* text. An inline passthrough emits it verbatim instead, and unlike a
|
|
92
|
+
* `badge:[...]` macro call it does not require the badge macro to be registered
|
|
93
|
+
* in the consuming playbook.
|
|
94
|
+
*
|
|
95
|
+
* @param {object} entry - Registry entry.
|
|
96
|
+
* @returns {string} Passthrough-wrapped badge, or an empty string.
|
|
97
|
+
*/
|
|
98
|
+
function buildBetaBadgeAsciiDoc (entry) {
|
|
99
|
+
const html = buildBetaBadge(entry)
|
|
100
|
+
return html ? `pass:[${html}]` : ''
|
|
101
|
+
}
|
|
102
|
+
const REGISTRY_FILENAME = 'enterprise-features.yml'
|
|
103
|
+
const VALID_SCOPES = ['redpanda', 'console', 'connect', 'operator', 'cloud']
|
|
104
|
+
|
|
105
|
+
const TABLE_TITLES = {
|
|
106
|
+
redpanda: 'Enterprise features in Redpanda',
|
|
107
|
+
console: 'Enterprise features in Redpanda Console',
|
|
108
|
+
connect: 'Enterprise features in Redpanda Connect',
|
|
109
|
+
operator: 'Enterprise features in the Redpanda Operator',
|
|
110
|
+
cloud: 'Enterprise features in Redpanda Cloud',
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const THIRD_COLUMN_HEADINGS = {
|
|
114
|
+
redpanda: 'Behavior Upon Expiration',
|
|
115
|
+
default: 'Restrictions Without Valid License',
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let warnedNoRegistry = false
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Parse the registry YAML into a lookup structure. Exported for unit testing.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} source - Raw YAML from enterprise-features.yml.
|
|
124
|
+
* @param {string} origin - Human-readable location for error messages.
|
|
125
|
+
* @returns {{features: object[], lookup: Map<string, object>}}
|
|
126
|
+
*/
|
|
127
|
+
function parseRegistry (source, origin = REGISTRY_FILENAME) {
|
|
128
|
+
const data = yaml.load(source)
|
|
129
|
+
if (!data || !Array.isArray(data.features)) {
|
|
130
|
+
throw new Error(`Enterprise features registry ${origin} has no 'features' list.`)
|
|
131
|
+
}
|
|
132
|
+
const lookup = new Map()
|
|
133
|
+
const claim = (rawKey, feature, kind) => {
|
|
134
|
+
const key = rawKey.trim().toLowerCase()
|
|
135
|
+
if (!key) return
|
|
136
|
+
const existing = lookup.get(key)
|
|
137
|
+
if (existing && existing !== feature) {
|
|
138
|
+
throw new Error(`Duplicate enterprise feature ${kind} '${rawKey}' in ${origin}: used by both '${existing.name}' and '${feature.name}'.`)
|
|
139
|
+
}
|
|
140
|
+
lookup.set(key, feature)
|
|
141
|
+
}
|
|
142
|
+
for (const feature of data.features) {
|
|
143
|
+
if (!feature || !feature.name) {
|
|
144
|
+
throw new Error(`Enterprise features registry ${origin} has an entry without a name.`)
|
|
145
|
+
}
|
|
146
|
+
if (feature.scope && !VALID_SCOPES.includes(feature.scope)) {
|
|
147
|
+
throw new Error(`Enterprise feature '${feature.name}' in ${origin} has unknown scope '${feature.scope}'.`)
|
|
148
|
+
}
|
|
149
|
+
claim(feature.name, feature, 'name')
|
|
150
|
+
for (const alias of feature.aliases || []) claim(String(alias), feature, 'alias')
|
|
151
|
+
}
|
|
152
|
+
return { features: data.features, lookup }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Load and cache the registry from the shared component in the Antora
|
|
157
|
+
* content catalog. Returns undefined when no registry is available.
|
|
158
|
+
*/
|
|
159
|
+
function loadRegistry (config) {
|
|
160
|
+
const contentCatalog = config && config.contentCatalog
|
|
161
|
+
if (!contentCatalog) return undefined
|
|
162
|
+
if (contentCatalog[$enterpriseRegistry] !== undefined) return contentCatalog[$enterpriseRegistry] || undefined
|
|
163
|
+
let registry = null
|
|
164
|
+
const partials = contentCatalog.findBy({ component: 'shared', module: 'ROOT', family: 'partial' }) || []
|
|
165
|
+
const registryFile = partials.find((file) => file.path && file.path.endsWith(REGISTRY_FILENAME))
|
|
166
|
+
if (registryFile) {
|
|
167
|
+
registry = parseRegistry(registryFile.contents.toString(), registryFile.path)
|
|
168
|
+
}
|
|
169
|
+
// Cache null too, so a missing registry is only searched for once per build.
|
|
170
|
+
contentCatalog[$enterpriseRegistry] = registry
|
|
171
|
+
return registry || undefined
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function warnNoRegistry () {
|
|
175
|
+
if (warnedNoRegistry) return
|
|
176
|
+
warnedNoRegistry = true
|
|
177
|
+
console.warn(chalk.yellow(`Enterprise features registry (${REGISTRY_FILENAME} in the shared component) not found; enterprise: targets are not validated.`))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Report an unknown feature name according to the enterprise-validate mode.
|
|
182
|
+
*/
|
|
183
|
+
function reportUnknownFeature ({ feature, mode, registry, filePath }) {
|
|
184
|
+
if (mode === 'off') return
|
|
185
|
+
const candidates = registry.features
|
|
186
|
+
.map((entry) => entry.name)
|
|
187
|
+
.filter((name) => name.toLowerCase().includes(feature.trim().toLowerCase().split(/\s+/)[0] || ''))
|
|
188
|
+
.slice(0, 3)
|
|
189
|
+
const hint = candidates.length ? ` Did you mean: ${candidates.join(', ')}?` : ''
|
|
190
|
+
const where = filePath ? ` in ${filePath}` : ''
|
|
191
|
+
const message = `enterprise:${feature}[] does not match any feature in the enterprise features registry${where}.${hint} Add the feature to ${REGISTRY_FILENAME} in the shared component first.`
|
|
192
|
+
if (mode === 'error') throw new Error(message)
|
|
193
|
+
console.warn(chalk.yellow(message))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Pick the environment-appropriate feature page from a registry entry.
|
|
198
|
+
* Some features have separate documentation for Kubernetes, Linux
|
|
199
|
+
* (self-managed), and Redpanda Cloud. Mirrors the config_ref macro's
|
|
200
|
+
* environment-awareness: the env-cloud and env-kubernetes page attributes
|
|
201
|
+
* select the xref-cloud and xref-kubernetes registry fields, falling back
|
|
202
|
+
* to the default xref.
|
|
203
|
+
*
|
|
204
|
+
* @param {object} entry - Registry entry.
|
|
205
|
+
* @param {object} document - Asciidoctor document (for env attributes).
|
|
206
|
+
* @returns {string|undefined}
|
|
207
|
+
*/
|
|
208
|
+
function resolveEntryXref (entry, document) {
|
|
209
|
+
if (!entry) return undefined
|
|
210
|
+
if (document.getAttribute('env-cloud') !== undefined && entry['xref-cloud']) return entry['xref-cloud']
|
|
211
|
+
if (document.getAttribute('env-kubernetes') !== undefined && entry['xref-kubernetes']) return entry['xref-kubernetes']
|
|
212
|
+
return entry.xref
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolve the tooltip attribute name from the enterprise-tooltip document
|
|
217
|
+
* attribute. Mirrors the glossary macro's contract.
|
|
218
|
+
*
|
|
219
|
+
* @param {string|undefined} raw - Raw attribute value.
|
|
220
|
+
* @returns {string|undefined} Attribute name to emit, or undefined when disabled.
|
|
221
|
+
*/
|
|
222
|
+
function resolveTooltipAttribute (raw) {
|
|
223
|
+
if (raw === 'false') return undefined
|
|
224
|
+
if (raw === undefined || raw === 'title') return 'title'
|
|
225
|
+
if (raw === 'true') return 'data-enterprise-tooltip'
|
|
226
|
+
if (raw.startsWith('data-')) return raw
|
|
227
|
+
console.warn(`enterprise-tooltip attribute '${raw}' must be 'title', 'true', 'false', or start with 'data-'. Falling back to 'title'.`)
|
|
228
|
+
return 'title'
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build the AsciiDoc content emitted for one inline macro instance. Exported
|
|
233
|
+
* for unit testing.
|
|
234
|
+
*
|
|
235
|
+
* @param {object} opts
|
|
236
|
+
* @param {string} opts.feature - Canonical feature name.
|
|
237
|
+
* @param {string} [opts.text] - Display text override.
|
|
238
|
+
* @param {string} [opts.xref] - Resource ID of the feature documentation.
|
|
239
|
+
* @param {string} [opts.url] - Absolute link used when no xref exists.
|
|
240
|
+
* @param {string} [opts.tooltip] - Tooltip text override.
|
|
241
|
+
* @param {string} opts.licensingPage - Resource ID of the licensing page.
|
|
242
|
+
* @param {string} opts.role - CSS class for the wrapping span.
|
|
243
|
+
* @param {string|undefined} opts.tooltipAttr - Tooltip attribute name, or undefined to omit.
|
|
244
|
+
* @param {boolean} opts.links - Whether to render a link.
|
|
245
|
+
* @returns {string}
|
|
246
|
+
*/
|
|
247
|
+
function buildEnterpriseContent ({ feature, text, xref, url, tooltip, licensingPage, role, tooltipAttr, links }) {
|
|
248
|
+
const display = text || feature
|
|
249
|
+
const tooltipText = tooltip || `${feature} requires an Enterprise Edition license.`
|
|
250
|
+
const escapedTooltip = tooltipText.replace(/"/g, '"')
|
|
251
|
+
const tooltipHtml = tooltipAttr ? ` ${tooltipAttr}="${escapedTooltip}"` : ''
|
|
252
|
+
let inner = display
|
|
253
|
+
if (links) {
|
|
254
|
+
inner = xref ? `xref:${xref}[${display}]` : (url ? `link:${url}[${display}]` : `xref:${licensingPage}[${display}]`)
|
|
255
|
+
}
|
|
256
|
+
return `<span class="${role}"${tooltipHtml}>${inner}</span>`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Build the AsciiDoc source of the licensing feature table for one scope.
|
|
261
|
+
* Exported for unit testing.
|
|
262
|
+
*
|
|
263
|
+
* @param {object[]} features - All registry entries.
|
|
264
|
+
* @param {string} scope - Scope to render.
|
|
265
|
+
* @param {object} [opts]
|
|
266
|
+
* @param {string} [opts.title] - Table title override.
|
|
267
|
+
* @param {string} [opts.heading] - Third column heading override.
|
|
268
|
+
* @returns {string}
|
|
269
|
+
*/
|
|
270
|
+
function buildFeatureTable (features, scope, opts = {}) {
|
|
271
|
+
const rows = features
|
|
272
|
+
.filter((feature) => feature.scope === scope)
|
|
273
|
+
.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }))
|
|
274
|
+
const title = opts.title || TABLE_TITLES[scope]
|
|
275
|
+
const heading = opts.heading || THIRD_COLUMN_HEADINGS[scope] || THIRD_COLUMN_HEADINGS.default
|
|
276
|
+
const lines = [`.${title}`, '[cols="1a,2a,2a"]', '|===', `| Feature | Description | ${heading}`, '']
|
|
277
|
+
for (const feature of rows) {
|
|
278
|
+
let cell = feature.xref
|
|
279
|
+
? `xref:${feature.xref}[${feature.name}]`
|
|
280
|
+
: (feature.url ? `link:${feature.url}[${feature.name}]` : feature.name)
|
|
281
|
+
if (feature['feature-suffix']) cell += ` ${feature['feature-suffix']}`
|
|
282
|
+
const betaBadge = buildBetaBadgeAsciiDoc(feature)
|
|
283
|
+
if (betaBadge) cell += ` ${betaBadge}`
|
|
284
|
+
if (feature['show-gating-property'] && feature['gating-property']) cell += `\n(\`${feature['gating-property']}\`)`
|
|
285
|
+
lines.push(`| ${cell}`)
|
|
286
|
+
lines.push(`| ${(feature.description || '').trim()}`)
|
|
287
|
+
lines.push(`| ${(feature.expiration || '').trim()}`)
|
|
288
|
+
lines.push('')
|
|
289
|
+
}
|
|
290
|
+
lines.push('|===')
|
|
291
|
+
return lines.join('\n')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function enterpriseInlineMacro (config) {
|
|
295
|
+
return function () {
|
|
296
|
+
const self = this
|
|
297
|
+
self.named('enterprise')
|
|
298
|
+
// Specifying the regexp allows spaces in the feature name.
|
|
299
|
+
self.$option('regexp', /enterprise:([^[]+)\[(|.*?[^\\])\]/)
|
|
300
|
+
self.process(function (parent, target, attributes) {
|
|
301
|
+
const document = parent.getDocument()
|
|
302
|
+
let registry
|
|
303
|
+
if (config && config.contentCatalog) {
|
|
304
|
+
registry = loadRegistry(config)
|
|
305
|
+
if (!registry) warnNoRegistry()
|
|
306
|
+
}
|
|
307
|
+
let feature = target
|
|
308
|
+
let entry
|
|
309
|
+
if (registry) {
|
|
310
|
+
entry = registry.lookup.get(target.trim().toLowerCase())
|
|
311
|
+
if (entry) {
|
|
312
|
+
feature = entry.name
|
|
313
|
+
} else {
|
|
314
|
+
reportUnknownFeature({
|
|
315
|
+
feature: target,
|
|
316
|
+
mode: document.getAttribute('enterprise-validate', 'warn'),
|
|
317
|
+
registry,
|
|
318
|
+
filePath: config && config.file && config.file.src && config.file.src.path,
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
let content = buildEnterpriseContent({
|
|
323
|
+
feature,
|
|
324
|
+
text: attributes.text,
|
|
325
|
+
xref: attributes.xref || resolveEntryXref(entry, document),
|
|
326
|
+
url: entry && entry.url,
|
|
327
|
+
tooltip: attributes.tooltip || (entry && entry.tooltip) || undefined,
|
|
328
|
+
licensingPage: document.getAttribute('enterprise-licensing-page', DEFAULT_LICENSING_PAGE),
|
|
329
|
+
role: document.getAttribute('enterprise-feature-role', DEFAULT_ROLE),
|
|
330
|
+
tooltipAttr: resolveTooltipAttribute(document.getAttribute('enterprise-tooltip')),
|
|
331
|
+
links: document.getAttribute('enterprise-links', 'true') === 'true',
|
|
332
|
+
})
|
|
333
|
+
// A feature marked beta in the registry is beta wherever it is
|
|
334
|
+
// referenced, so prose gets the same badge as the generated tables.
|
|
335
|
+
// Set enterprise-beta-badge to false to suppress it in prose only.
|
|
336
|
+
if (document.getAttribute('enterprise-beta-badge', 'true') === 'true') {
|
|
337
|
+
const betaBadge = buildBetaBadge(entry)
|
|
338
|
+
if (betaBadge) content += ` ${betaBadge}`
|
|
339
|
+
}
|
|
340
|
+
// The xref inside the span is resolved by the 'macros' substitution,
|
|
341
|
+
// the same mechanism the config_ref macro relies on.
|
|
342
|
+
return self.createInline(parent, 'quoted', content, { attributes: { subs: 'macros' } })
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function enterpriseFeaturesBlockMacro (config) {
|
|
348
|
+
return function () {
|
|
349
|
+
const self = this
|
|
350
|
+
self.named('enterprise_features')
|
|
351
|
+
self.process(function (parent, target, attributes) {
|
|
352
|
+
const scope = (target || attributes.scope || '').trim()
|
|
353
|
+
if (!VALID_SCOPES.includes(scope)) {
|
|
354
|
+
throw new Error(`enterprise_features::[] needs a scope of ${VALID_SCOPES.join(', ')} as its target, got '${scope}'.`)
|
|
355
|
+
}
|
|
356
|
+
const registry = config && config.contentCatalog ? loadRegistry(config) : undefined
|
|
357
|
+
if (!registry) {
|
|
358
|
+
warnNoRegistry()
|
|
359
|
+
return self.parseContent(parent, `WARNING: The enterprise features registry is unavailable, so the ${scope} feature table cannot be rendered.`)
|
|
360
|
+
}
|
|
361
|
+
const table = buildFeatureTable(registry.features, scope, { title: attributes.title, heading: attributes.heading })
|
|
362
|
+
// A block anchor before the macro call ([[my-id]]) is consumed into the
|
|
363
|
+
// macro's id attribute. Re-emit it so crossrefs to the table keep working.
|
|
364
|
+
const source = attributes.id ? `[[${attributes.id}]]\n${table}` : table
|
|
365
|
+
return self.parseContent(parent, source)
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function register (registry, config = {}) {
|
|
371
|
+
if (typeof registry.register === 'function') {
|
|
372
|
+
registry.register(function () {
|
|
373
|
+
this.inlineMacro(enterpriseInlineMacro(config))
|
|
374
|
+
this.blockMacro(enterpriseFeaturesBlockMacro(config))
|
|
375
|
+
})
|
|
376
|
+
} else if (typeof registry.inlineMacro === 'function') {
|
|
377
|
+
registry.inlineMacro(enterpriseInlineMacro(config))
|
|
378
|
+
if (typeof registry.blockMacro === 'function') registry.blockMacro(enterpriseFeaturesBlockMacro(config))
|
|
379
|
+
} else {
|
|
380
|
+
console.warn("no 'inlineMacro' method on alleged registry")
|
|
381
|
+
}
|
|
382
|
+
return registry
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports.register = register
|
|
386
|
+
module.exports.buildEnterpriseContent = buildEnterpriseContent
|
|
387
|
+
module.exports.buildFeatureTable = buildFeatureTable
|
|
388
|
+
module.exports.parseRegistry = parseRegistry
|
|
389
|
+
module.exports.resolveEntryXref = resolveEntryXref
|
|
390
|
+
module.exports.resolveTooltipAttribute = resolveTooltipAttribute
|