@redpanda-data/docs-extensions-and-macros 5.22.1 → 5.22.2

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 CHANGED
@@ -901,7 +901,7 @@ automation
901
901
  * @description
902
902
  * One-time migration that rewires published connector reference pages onto the
903
903
  * regenerated description partial: the header `:description:` becomes
904
- * `include::...[tag=attrs]` and the frozen intro prose becomes
904
+ * `include::...[tag=meta]` and the frozen intro prose becomes
905
905
  * `include::...[tag=body]`. Afterwards `generate rpcn-connector-docs` keeps
906
906
  * both fresh on every run.
907
907
  *
@@ -64,7 +64,6 @@ IMPORTANT: Extensions must be registered under the `antora.extensions` key in yo
64
64
 
65
65
  === Navigation
66
66
  * **unlisted-pages** - Manage pages that appear in navigation but aren't listed
67
- * **produce-redirects** - Generate redirect configurations
68
67
  * **unpublish-pages** - Mark pages as unpublished
69
68
 
70
69
  === Integrations
@@ -125,13 +124,16 @@ antora:
125
124
  algolia_index_name: YOUR_INDEX
126
125
  ----
127
126
 
128
- === Generate redirects
127
+ === Redirects
128
+
129
+ No extension required: core Antora (>= 3.1.8) generates redirects for https://docs.antora.org/antora/latest/page/page-aliases/[page aliases] automatically on every build. Define aliases in your `antora.yml`:
129
130
 
130
131
  [,yaml]
131
132
  ----
132
- antora:
133
- extensions:
134
- - '@redpanda-data/docs-extensions-and-macros/extensions/produce-redirects.js'
133
+ name: redpanda
134
+ asciidoc:
135
+ attributes:
136
+ page-aliases: old-page.adoc,another-old-page.adoc
135
137
  ----
136
138
 
137
139
  == Support
@@ -706,23 +706,7 @@ In this example, the `attributespath` option points to a local YAML file (`./loc
706
706
 
707
707
  == Produce redirects (customization of core Antora)
708
708
 
709
- This extension replaces the default https://gitlab.com/antora/antora/-/tree/v3.1.x/packages/redirect-producer[`produceRedirects()` function] in Antora to handle redirect loops caused by https://docs.antora.org/antora/latest/page/page-aliases/[page aliases]. Normally, page aliases in Antora are used to resolve outdated links without causing issues. However, with https://docs.antora.org/antora/latest/playbook/urls-html-extension-style/#html-extension-style-key[`indexify`], the same URL may inadvertently be used for both the source and target of a redirect, leading to loops. This problem is https://antora.zulipchat.com/#narrow/stream/282400-users/topic/Redirect.20Loop.20Issue.20with.20Page.20Renaming.20and.20Indexify/near/433691700[recognized as a bug] in core Antora. For example, creating a page alias for `modules/manage/security/authorization.adoc` to point to `modules/manage/security/authorization/index.adoc' can lead to a redirect loop where `manage/security/authorization/` points to `manage/security/authorization/`. Furthermore, omitting the alias would lead to `xref not found` errors because Antora relies on the alias to resolve the old xrefs. This extension is necessary until such behaviors are natively supported or fixed in Antora core.
710
-
711
- === Environment variables
712
-
713
- This extension does not require any environment variables.
714
-
715
- === Configuration options
716
-
717
- There are no configurable options for this extension.
718
-
719
- === Registration
720
-
721
- ```yaml
722
- antora:
723
- extensions:
724
- - '@redpanda-data/docs-extensions-and-macros/extensions/modify-redirects'
725
- ```
709
+ Antora's built-in `@antora/redirect-producer` already produces redirect rules for https://docs.antora.org/antora/latest/page/page-aliases/[page aliases], including the redirect-loop case caused by https://docs.antora.org/antora/latest/playbook/urls-html-extension-style/#html-extension-style-key[`indexify`] that once required a workaround. Antora runs the real module automatically on every build with no extension required. This project previously shipped a hand-copied override of that module to work around the loop, but the override had drifted from the upstream fix and reintroduced a worse bug (a self-referential alias could overwrite the real page it pointed at). The override has been removed; core Antora handles redirects natively.
726
710
 
727
711
  == Replace attributes in attachments
728
712
 
@@ -85,7 +85,6 @@ antora:
85
85
  extensions:
86
86
  - '@redpanda-data/docs-extensions-and-macros/extensions/version-fetcher/set-latest-version'
87
87
  - '@redpanda-data/docs-extensions-and-macros/extensions/generate-index-data.js'
88
- - '@redpanda-data/docs-extensions-and-macros/extensions/produce-redirects.js'
89
88
  ----
90
89
 
91
90
  Extensions run in the order they're listed.
@@ -122,16 +121,7 @@ antora:
122
121
 
123
122
  === Redirect management
124
123
 
125
- Generate redirects from old URLs to new ones:
126
-
127
- [,yaml]
128
- ----
129
- antora:
130
- extensions:
131
- - '@redpanda-data/docs-extensions-and-macros/extensions/produce-redirects.js'
132
- ----
133
-
134
- Define redirects in your `antora.yml`:
124
+ No extension required: core Antora (>= 3.1.8) generates redirects for old URLs automatically. Define redirects in your `antora.yml`:
135
125
 
136
126
  [,yaml]
137
127
  ----
@@ -4,6 +4,61 @@ const $glossaryContexts = Symbol('$glossaryContexts')
4
4
  const { posix: path } = require('path')
5
5
  const chalk = require('chalk')
6
6
 
7
+ // Backtick-monospace in hover-text. The unconstrained (double-backtick) form is
8
+ // matched first, otherwise it leaves a stray delimiter on each side.
9
+ const MONOSPACE_RX = /``([^`]+)``|`([^`]+)`/g
10
+
11
+ // Escape for interpolation into a double-quoted HTML attribute. Same four
12
+ // replacements, in the same order, as badge.js and enterprise.js; `&` has to go
13
+ // first or it re-escapes the entities the later passes introduce.
14
+ function escapeAttr (value) {
15
+ return String(value)
16
+ .replace(/&/g, '&')
17
+ .replace(/"/g, '"')
18
+ .replace(/</g, '&lt;')
19
+ .replace(/>/g, '&gt;')
20
+ }
21
+
22
+ // Only a data-* tooltip is read by a JS tooltip library that renders it as HTML;
23
+ // the native title attribute is always plain text. Shared with the
24
+ // glossary-tooltip validation below so the two cannot drift apart.
25
+ function tooltipRendersHtml (tooltipAttr) {
26
+ return typeof tooltipAttr === 'string' && tooltipAttr.startsWith('data-')
27
+ }
28
+
29
+ // hover-text is raw, unconverted source text, so it has to be escaped before it
30
+ // lands in an HTML attribute. How many passes depends on the consumer:
31
+ //
32
+ // title The browser decodes the attribute once and shows the result as
33
+ // text, so one pass is exactly right.
34
+ // data-* docs-ui initializes tippy on these with allowHTML: true
35
+ // (docs-ui/src/js/12-activate-tooltips.js), which means the value is
36
+ // decoded once by the HTML parser and then parsed AGAIN as HTML by
37
+ // innerHTML. A single pass is spent on that first decode and escaped
38
+ // markup comes back to life -- `<img onerror=...>` in hover-text
39
+ // became a live element that fired. Escape twice so one level
40
+ // survives the decode, and add <code> afterwards so it is the only
41
+ // live markup in the value.
42
+ //
43
+ // Set definitionIsHtml for a definition that Asciidoctor already converted --
44
+ // the inline glossterm:term[definition] form arrives as HTML, unlike a term
45
+ // file's raw :hover-text: value -- so it is escaped once rather than twice and
46
+ // its markup survives to the reader instead of being shown as literal tags.
47
+ function formatTooltipDefinition (text, tooltipAttr, definitionIsHtml) {
48
+ if (!tooltipRendersHtml(tooltipAttr)) return escapeAttr(text)
49
+ if (definitionIsHtml) {
50
+ // Already HTML, so one pass is right: the attribute decode hands the markup
51
+ // back intact for innerHTML to render, and a quote in the text still cannot
52
+ // terminate the attribute early.
53
+ return escapeAttr(text)
54
+ }
55
+ return escapeAttr(escapeAttr(text)).replace(
56
+ MONOSPACE_RX,
57
+ (match, unconstrained, constrained) =>
58
+ `<code>${unconstrained !== undefined ? unconstrained : constrained}</code>`
59
+ )
60
+ }
61
+
7
62
  module.exports.register = function (registry, config = {}) {
8
63
 
9
64
  const vfs = adaptVfs()
@@ -113,19 +168,24 @@ module.exports.register = function (registry, config = {}) {
113
168
  }
114
169
  var tooltip = document.getAttribute('glossary-tooltip')
115
170
  if (tooltip === 'true') tooltip = 'data-glossary-tooltip'
116
- if (tooltip && tooltip !== 'title' && !tooltip.startsWith('data-')) {
171
+ if (tooltip && tooltip !== 'title' && !tooltipRendersHtml(tooltip)) {
117
172
  console.log(`glossary-tooltip attribute '${tooltip}' must be 'true', 'title', or start with 'data-`)
118
173
  tooltip = undefined
119
174
  }
120
175
  const logTerms = document.hasAttribute('glossary-log-terms')
121
176
  var definition;
122
177
  var pageTitle;
178
+ // A term file's :hover-text: is raw source text, but the definition
179
+ // passed inline as glossterm:term[definition] has already been through
180
+ // Asciidoctor's inline substitutions, so the two need different escaping.
181
+ var definitionIsHtml = false;
123
182
  const index = context.gloss.findIndex((candidate) => candidate.term === term)
124
183
  if (index >= 0) {
125
184
  definition = context.gloss[index].def
126
185
  pageTitle = context.gloss[index].pageTitle
127
186
  } else {
128
187
  definition = attributes.definition;
188
+ definitionIsHtml = !!definition;
129
189
  }
130
190
  if (definition) {
131
191
  logTerms && console.log(`${term}:: ${definition}`)
@@ -154,12 +214,13 @@ module.exports.register = function (registry, config = {}) {
154
214
  inline = self.createInline(parent, 'quoted', customText, { attributes: attrs })
155
215
  }
156
216
  if (tooltip) {
217
+ const formattedDefinition = formatTooltipDefinition(definition, tooltip, definitionIsHtml)
157
218
  const a = inline.convert()
158
219
  const matches = a.match(TRX)
159
220
  if (matches) {
160
- return self.createInline(parent, 'quoted', `${matches[1]} ${tooltip}="${definition}"${matches[2]}`)
221
+ return self.createInline(parent, 'quoted', `${matches[1]} ${tooltip}="${formattedDefinition}"${matches[2]}`)
161
222
  } else {
162
- return self.createInline(parent, 'quoted', `<span ${tooltip}="${definition}">${a}</span>`)
223
+ return self.createInline(parent, 'quoted', `<span ${tooltip}="${formattedDefinition}">${a}</span>`)
163
224
  }
164
225
  }
165
226
  return inline
@@ -186,3 +247,5 @@ module.exports.register = function (registry, config = {}) {
186
247
  }
187
248
  return registry
188
249
  }
250
+
251
+ module.exports.formatTooltipDefinition = formatTooltipDefinition
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "5.22.1",
3
+ "version": "5.22.2",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -77,8 +77,6 @@
77
77
  "./extensions/find-related-labs": "./extensions/find-related-labs.js",
78
78
  "./extensions/convert-llms-to-txt": "./extensions/convert-llms-to-txt.js",
79
79
  "./extensions/convert-sitemap-to-markdown": "./extensions/convert-sitemap-to-markdown.js",
80
- "./extensions/modify-redirects": "./extensions/produce-redirects.js",
81
- "./extensions/produce-redirects": "./extensions/produce-redirects.js",
82
80
  "./extensions/algolia-indexer/index": "./extensions/algolia-indexer/index.js",
83
81
  "./extensions/aggregate-terms": "./extensions/aggregate-terms.js",
84
82
  "./extensions/resolve-xrefs-in-attachments": "./extensions/resolve-xrefs-in-attachments.js",
@@ -43,8 +43,8 @@ const STALE_METADATA_PARTIAL =
43
43
  // deleting the file would break any published page that includes it, so blank
44
44
  // it instead and log so a human removes the include line.
45
45
  //
46
- // The empty attrs/body regions are load-bearing. Pages include this file with
47
- // `[tag=attrs]` and `[tag=body]`, and Asciidoctor logs `tag 'attrs' not found
46
+ // The empty meta/body regions are load-bearing. Pages include this file with
47
+ // `[tag=meta]` and `[tag=body]`, and Asciidoctor logs `tag 'meta' not found
48
48
  // in include file` for every tag a target does not define -- so a banner
49
49
  // without the regions turns one blanked partial into two build warnings on
50
50
  // every page that includes it, on every build, which is a build failure
@@ -54,7 +54,7 @@ const STALE_DESCRIPTION_PARTIAL =
54
54
  '// This content is autogenerated. Do not edit manually. ' +
55
55
  'The connector no longer has a description, so this partial is intentionally empty. ' +
56
56
  'You can remove the include directive that points at this file from the connector page.\n' +
57
- '\n// tag::attrs[]\n// end::attrs[]\n' +
57
+ '\n// tag::meta[]\n// end::meta[]\n' +
58
58
  '\n// tag::body[]\n// end::body[]\n';
59
59
 
60
60
  // Component families with their own reference pages, keyed by the canonical
@@ -606,12 +606,12 @@ async function generateRpcnConnectorDocs(options) {
606
606
  // connectors with neither summary nor description, keep the inline
607
607
  // text rather than getting an include that cannot resolve.
608
608
  item.hasDescriptionPartial = true;
609
- // The attrs region only sets :description: when the connector has a
609
+ // The meta region only sets :description: when the connector has a
610
610
  // summary, so a page may only replace its header attribute with the
611
611
  // include when there is one. Otherwise the page would lose its meta
612
612
  // description entirely and keep the inline attribute instead.
613
613
  if (typeof item.summary === 'string' && item.summary.trim()) {
614
- item.hasDescriptionAttrs = true;
614
+ item.hasDescriptionMeta = true;
615
615
  }
616
616
  if (!writeFullDrafts) {
617
617
  partialsWritten++;
@@ -988,7 +988,7 @@ function backfillPageDescriptions (connectorData, { pagesRoot, dryRun = false }
988
988
  const lines = content.split('\n');
989
989
  const headerEnd = lines.findIndex((l) => l.trim() === '');
990
990
  const header = lines.slice(0, headerEnd === -1 ? lines.length : headerEnd);
991
- // A page migrated to the description partial carries the attrs include
991
+ // A page migrated to the description partial carries the meta include
992
992
  // instead of a literal :description:. Without this arm the backfill
993
993
  // splices a competing meta block into every migrated page, so the page
994
994
  // ends up with a dead include plus a hand-inserted attribute that
@@ -997,7 +997,7 @@ function backfillPageDescriptions (connectorData, { pagesRoot, dryRun = false }
997
997
 
998
998
  // Meta descriptions are read as plain text by search results, link
999
999
  // previews, and assistants, so the markup comes out mechanically. Same
1000
- // flattener the description partial's attrs region uses, so a page
1000
+ // flattener the description partial's meta region uses, so a page
1001
1001
  // cannot publish one meta description before migrating to the partial
1002
1002
  // and a different one after.
1003
1003
  const summary = flattenToAttributeValue(item.summary);
@@ -6,7 +6,7 @@ const { descriptionIncludeLine } = require('../metadata-utils.js');
6
6
  * Handlebars helper: emit the include directive for a connector's description
7
7
  * partial, optionally for one of its tag regions.
8
8
  *
9
- * {{{descriptionInclude this 'attrs'}}}
9
+ * {{{descriptionInclude this 'meta'}}}
10
10
  * {{{descriptionInclude this 'body'}}}
11
11
  *
12
12
  * Drafted pages use this instead of freezing the summary and description into
@@ -16,7 +16,7 @@ const { descriptionIncludeLine } = require('../metadata-utils.js');
16
16
  * writes the file.
17
17
  *
18
18
  * @param {object} item connector data with `type`/`typeDir` and `name`
19
- * @param {string} [tag] `attrs` or `body`
19
+ * @param {string} [tag] `meta` or `body`
20
20
  * @returns {string}
21
21
  */
22
22
  module.exports = function descriptionInclude (item, tag) {
@@ -7,7 +7,7 @@ const { flattenToAttributeValue } = require('../metadata-utils.js');
7
7
  * value suitable for the `:description:` page attribute (and therefore for the
8
8
  * page's `<meta name="description">`).
9
9
  *
10
- * Delegates to the shared flattener so the description partial's attrs region
10
+ * Delegates to the shared flattener so the description partial's meta region
11
11
  * and `backfillPageDescriptions` publish the same text for the same summary.
12
12
  * Collapsing whitespace alone is not enough: summaries carry xrefs, link
13
13
  * macros and inline code, and an unflattened value ships raw
@@ -248,10 +248,10 @@ function descriptionWithMetadataInclude (item) {
248
248
  * auto-generated summary and description from a regenerated partial instead of
249
249
  * freezing them into the page body at first draft.
250
250
  *
251
- * The partial defines two tag regions, so pages address it twice: `attrs` in
251
+ * The partial defines two tag regions, so pages address it twice: `meta` in
252
252
  * the page header (sets `:description:`) and `body` where the prose renders.
253
253
  * @param {object} item connector data with `type`/`typeDir` and `name`
254
- * @param {string} [tag] tag region to include (`attrs` or `body`); omit for the whole file
254
+ * @param {string} [tag] tag region to include (`meta` or `body`); omit for the whole file
255
255
  * @returns {string}
256
256
  */
257
257
  function descriptionIncludeLine (item, tag) {
@@ -308,7 +308,7 @@ function lostMetadataSections (oldContent, newContent) {
308
308
  * edited.
309
309
  *
310
310
  * This is the one implementation: `summaryAttribute` (the description
311
- * partial's attrs region) and `backfillPageDescriptions` (the page-header
311
+ * partial's meta region) and `backfillPageDescriptions` (the page-header
312
312
  * self-heal) both call it, so the two paths cannot drift into publishing
313
313
  * different meta descriptions for the same summary.
314
314
  *
@@ -11,7 +11,7 @@
11
11
  * to `modules/components/partials/descriptions/<type>/<name>.adoc` on every
12
12
  * run, with two tag regions. This script rewires existing pages to it:
13
13
  *
14
- * header: include::...[tag=attrs] inside the page's `// tag::meta[]` region
14
+ * header: include::...[tag=meta] inside the page's `// tag::meta[]` region
15
15
  * body: include::...[tag=body] where the frozen prose is today
16
16
  *
17
17
  * The header rewire is mechanical and safe: it swaps one `:description:` line
@@ -36,7 +36,7 @@ const path = require('path');
36
36
  const { descriptionIncludeLine, normalizeTypeDir, flattenToAttributeValue } = require('./metadata-utils.js');
37
37
 
38
38
  // The page's meta tag region: the block single-source stubs inherit
39
- // :description: from, so the attrs include belongs inside it.
39
+ // :description: from, so the meta include belongs inside it.
40
40
  const META_TAG_OPEN = /^\/\/\s*tag::meta\[\]\s*$/;
41
41
  const META_TAG_CLOSE = /^\/\/\s*end::meta\[\]\s*$/;
42
42
 
@@ -123,34 +123,34 @@ function migrateDescriptionsToPartials ({
123
123
  continue;
124
124
  }
125
125
  const partial = fs.readFileSync(partialPath, 'utf8');
126
- const attrs = tagRegion(partial, 'attrs');
126
+ const meta = tagRegion(partial, 'meta');
127
127
  const body = tagRegion(partial, 'body');
128
- if (!attrs || !body || contentLines(body).length === 0) {
128
+ if (!meta || !body || contentLines(body).length === 0) {
129
129
  // A blanked partial (description removed upstream) has both regions
130
130
  // but no content. Wiring a page to it would publish an empty page.
131
131
  results.skipped.push({ page: key, reason: 'description partial is empty' });
132
132
  continue;
133
133
  }
134
134
 
135
- const includeAttrs = descriptionIncludeLine({ typeDir: partialTypeDir, name }, 'attrs');
135
+ const includeMeta = descriptionIncludeLine({ typeDir: partialTypeDir, name }, 'meta');
136
136
  const includeBody = descriptionIncludeLine({ typeDir: partialTypeDir, name }, 'body');
137
137
 
138
138
  let lines = fs.readFileSync(pagePath, 'utf8').split('\n');
139
139
  let changed = false;
140
140
 
141
141
  // --- header ---------------------------------------------------------
142
- if (!lines.some((l) => l.trim() === includeAttrs)) {
142
+ if (!lines.some((l) => l.trim() === includeMeta)) {
143
143
  const open = lines.findIndex((l) => META_TAG_OPEN.test(l));
144
144
  const close = lines.findIndex((l, i) => i > open && META_TAG_CLOSE.test(l));
145
- const attrValue = contentLines(attrs)
145
+ const metaValue = contentLines(meta)
146
146
  .find((l) => l.startsWith(':description:'));
147
147
  const pageAttr = lines.slice(0, introOf(lines)).find((l) => l.startsWith(':description:'));
148
- if (!attrValue) {
149
- // The connector has no summary, so the attrs region sets nothing.
148
+ if (!metaValue) {
149
+ // The connector has no summary, so the meta region sets nothing.
150
150
  // Replacing a hand-written :description: with it would drop the
151
151
  // page's meta description.
152
- results.skipped.push({ page: key, reason: 'attrs region sets no :description:' });
153
- } else if (pageAttr && flattenToAttributeValue(pageAttr) !== flattenToAttributeValue(attrValue)) {
152
+ results.skipped.push({ page: key, reason: 'meta region sets no :description:' });
153
+ } else if (pageAttr && flattenToAttributeValue(pageAttr) !== flattenToAttributeValue(metaValue)) {
154
154
  // The page's meta description is not the connector summary, so it
155
155
  // was authored by hand (often tuned for search) and swapping in the
156
156
  // include would silently discard that editorial work. 13 pages in
@@ -159,7 +159,7 @@ function migrateDescriptionsToPartials ({
159
159
  // header alone.
160
160
  results.skipped.push({ page: key, reason: 'hand-authored :description: differs from the summary (needs a human)' });
161
161
  } else if (open !== -1 && close > open) {
162
- lines.splice(open + 1, close - open - 1, includeAttrs);
162
+ lines.splice(open + 1, close - open - 1, includeMeta);
163
163
  results.headers.push(key);
164
164
  changed = true;
165
165
  } else {
@@ -169,7 +169,7 @@ function migrateDescriptionsToPartials ({
169
169
  // :description: lines in the first place.
170
170
  const attrLine = lines.findIndex((l) => l.startsWith(':description:'));
171
171
  if (attrLine !== -1 && attrLine < introOf(lines)) {
172
- lines.splice(attrLine, 1, '// tag::meta[]', includeAttrs, '// end::meta[]');
172
+ lines.splice(attrLine, 1, '// tag::meta[]', includeMeta, '// end::meta[]');
173
173
  results.headers.push(key);
174
174
  changed = true;
175
175
  } else {
@@ -183,7 +183,7 @@ function migrateDescriptionsToPartials ({
183
183
  // it could not see and the page ended up with two. AsciiDoc takes the
184
184
  // last assignment, so leaving the literal one there would make the
185
185
  // include a no-op and the page would keep publishing frozen text.
186
- if (lines.some((l) => l.trim() === includeAttrs)) {
186
+ if (lines.some((l) => l.trim() === includeMeta)) {
187
187
  for (let i = introOf(lines) - 1; i >= 0; i--) {
188
188
  if (lines[i].startsWith(':description:')) {
189
189
  lines.splice(i, 1);
@@ -3,9 +3,9 @@
3
3
  :type: {{type}}
4
4
  {{#if support}}:support: {{support}}{{/if}}
5
5
  :categories: [{{join categories ", "}}]
6
- {{#if hasDescriptionAttrs}}
6
+ {{#if hasDescriptionMeta}}
7
7
  // tag::meta[]
8
- {{{descriptionInclude this "attrs"}}}
8
+ {{{descriptionInclude this "meta"}}}
9
9
  // end::meta[]
10
10
  {{else if summary}}
11
11
  // tag::meta[]
@@ -16,7 +16,7 @@
16
16
  :description: {{{summaryAttribute description}}}
17
17
  // end::meta[]
18
18
  {{/if}}
19
- {{!-- The attrs include stays inside the meta tag region so single-source
19
+ {{!-- The meta include stays inside the meta tag region so single-source
20
20
  stubs keep inheriting :description: as a header attribute, and the
21
21
  value now refreshes with the connector instead of being frozen at
22
22
  first draft. Component families with no description partial keep the
@@ -2,12 +2,12 @@
2
2
  // This content is autogenerated. Do not edit manually. The summary and description come from the connector's definition in the Connect source. To change them, update the connector upstream, or use the doc-tools CLI with the --overrides option: https://redpandadata.atlassian.net/wiki/spaces/DOC/pages/1247543314/Generate+reference+docs+for+Redpanda+Connect
3
3
  //
4
4
  // Pages consume this partial in two places so both stay fresh:
5
- // header: include::...[tag=attrs] (sets :description: for search snippets and meta tags)
5
+ // header: include::...[tag=meta] (sets :description: for search snippets and meta tags)
6
6
  // body: include::...[tag=body] (renders the summary and description)
7
7
 
8
- // tag::attrs[]
8
+ // tag::meta[]
9
9
  {{#if summary}}:description: {{{summaryAttribute summary}}}
10
- {{/if}}// end::attrs[]
10
+ {{/if}}// end::meta[]
11
11
 
12
12
  // tag::body[]
13
13
  {{#if summary}}{{{summary}}}
@@ -1,166 +0,0 @@
1
- 'use strict';
2
-
3
- const { raiseListenerLimit } = require('./util/raise-listener-limit')
4
-
5
- const File = require('vinyl')
6
- const { posix: path } = require('path')
7
-
8
- const ENCODED_SPACE_RX = /%20/g
9
-
10
- module.exports.register = function () {
11
- raiseListenerLimit(this)
12
- this.once('contextStarted', () => {
13
- const { publishFiles: produceRedirectsDelegate } = this.getFunctions()
14
- this.replaceFunctions({
15
- produceRedirects (playbook, aliases) {
16
- if ('findBy' in aliases) aliases = aliases.findBy({ family: 'alias' }) // @deprecated remove in Antora 4
17
- if (!(aliases = aliases.filter((it) => it.pub.url !== it.rel.pub.url)).length) return []
18
- let siteUrl = playbook.site.url
19
- if (siteUrl) siteUrl = stripTrailingSlash(siteUrl, '')
20
- const directoryRedirects = (playbook.urls.htmlExtensionStyle || 'default') !== 'default'
21
- switch (playbook.urls.redirectFacility) {
22
- case 'gitlab':
23
- return createNetlifyRedirects(aliases, extractUrlPath(siteUrl), !directoryRedirects, false)
24
- case 'httpd':
25
- return createHttpdHtaccess(aliases, extractUrlPath(siteUrl), directoryRedirects)
26
- case 'netlify':
27
- return createNetlifyRedirects(aliases, extractUrlPath(siteUrl), !directoryRedirects)
28
- case 'nginx':
29
- return createNginxRewriteConf(aliases, extractUrlPath(siteUrl))
30
- case 'static':
31
- return populateStaticRedirectFiles(
32
- aliases.filter((it) => it.out),
33
- siteUrl
34
- )
35
- default:
36
- return unpublish(aliases)
37
- }
38
- }
39
- })
40
- })
41
- }
42
-
43
- function createStaticRedirectContents (file, siteUrl) {
44
- const targetUrl = file.rel.pub.url
45
- let linkTag
46
- let to = targetUrl.charAt() === '/' ? computeRelativeUrlPath(file.pub.url, targetUrl) : undefined
47
- let toText = to
48
- if (to) {
49
- if (siteUrl && siteUrl.charAt() !== '/') {
50
- linkTag = `<link rel="canonical" href="${(toText = siteUrl + targetUrl)}">\n`
51
- }
52
- } else {
53
- linkTag = `<link rel="canonical" href="${(toText = to = targetUrl)}">\n`
54
- }
55
- return `<!DOCTYPE html>
56
- <meta charset="utf-8">
57
- ${linkTag || ''}<script>location="${to}"</script>
58
- <meta http-equiv="refresh" content="0; url=${to}">
59
- <meta name="robots" content="noindex">
60
- <title>Redirect Notice</title>
61
- <h1>Redirect Notice</h1>
62
- <p>The page you requested has been relocated to <a href="${to}">${toText}</a>.</p>`
63
- }
64
-
65
- function extractUrlPath (url) {
66
- if (url) {
67
- if (url.charAt() === '/') return url
68
- const urlPath = new URL(url).pathname
69
- return urlPath === '/' ? '' : urlPath
70
- } else {
71
- return ''
72
- }
73
- }
74
-
75
- function createHttpdHtaccess (files, urlPath, directoryRedirects = false) {
76
- const rules = files.reduce((accum, file) => {
77
- delete file.out
78
- let fromUrl = file.pub.url
79
- fromUrl = ~fromUrl.indexOf('%20') ? `'${urlPath}${fromUrl.replace(ENCODED_SPACE_RX, ' ')}'` : urlPath + fromUrl
80
- let toUrl = file.rel.pub.url
81
- toUrl = ~toUrl.indexOf('%20') ? `'${urlPath}${toUrl.replace(ENCODED_SPACE_RX, ' ')}'` : urlPath + toUrl
82
- // see https://httpd.apache.org/docs/current/en/mod/mod_alias.html#redirect
83
- // NOTE: redirect rule for directory prefix does not require trailing slash
84
- if (file.pub.splat) {
85
- accum.push(`Redirect 302 ${fromUrl} ${stripTrailingSlash(toUrl)}`)
86
- } else if (directoryRedirects) {
87
- accum.push(`RedirectMatch 301 ^${regexpEscape(fromUrl)}$ ${stripTrailingSlash(toUrl)}`)
88
- } else {
89
- accum.push(`Redirect 301 ${fromUrl} ${toUrl}`)
90
- }
91
- return accum
92
- }, [])
93
- return [new File({ contents: Buffer.from(rules.join('\n') + '\n'), out: { path: '.htaccess' } })]
94
- }
95
-
96
- // NOTE: a trailing slash on the pathname will be ignored
97
- // see https://docs.netlify.com/routing/redirects/redirect-options/#trailing-slash
98
- // however, we keep it when generating the rules for clarity
99
- function createNetlifyRedirects (files, urlPath, addDirectoryRedirects = false, useForceFlag = true) {
100
- const rules = files.reduce((accum, file) => {
101
- delete file.out
102
- const fromUrl = urlPath + file.pub.url
103
- const toUrl = urlPath + file.rel.pub.url
104
- const forceFlag = useForceFlag ? '!' : ''
105
- if (file.pub.splat) {
106
- accum.push(`${fromUrl}/* ${ensureTrailingSlash(toUrl)}:splat 302${forceFlag}`)
107
- } else {
108
- accum.push(`${fromUrl} ${toUrl} 301${forceFlag}`)
109
- if (addDirectoryRedirects && fromUrl.endsWith('/index.html')) {
110
- accum.push(`${fromUrl.substr(0, fromUrl.length - 10)} ${toUrl} 301${forceFlag}`)
111
- }
112
- }
113
- return accum
114
- }, [])
115
- return [new File({ contents: Buffer.from(rules.join('\n') + '\n'), out: { path: '_redirects' } })]
116
- }
117
-
118
- function createNginxRewriteConf (files, urlPath) {
119
- const rules = files.map((file) => {
120
- delete file.out
121
- let fromUrl = file.pub.url
122
- fromUrl = ~fromUrl.indexOf('%20') ? `'${urlPath}${fromUrl.replace(ENCODED_SPACE_RX, ' ')}'` : urlPath + fromUrl
123
- let toUrl = file.rel.pub.url
124
- toUrl = ~toUrl.indexOf('%20') ? `'${urlPath}${toUrl.replace(ENCODED_SPACE_RX, ' ')}'` : urlPath + toUrl
125
- if (file.pub.splat) {
126
- const toUrlWithTrailingSlash = ensureTrailingSlash(toUrl)
127
- return `location ^~ ${fromUrl}/ { rewrite ^${regexpEscape(fromUrl)}/(.*)$ ${toUrlWithTrailingSlash}$1 redirect; }`
128
- } else {
129
- return `location = ${fromUrl} { return 301 ${toUrl}; }`
130
- }
131
- })
132
- return [new File({ contents: Buffer.from(rules.join('\n').trim() + '\n'), out: { path: '.etc/nginx/rewrite.conf' } })]
133
- }
134
-
135
- function populateStaticRedirectFiles(files, siteUrl) {
136
- for (const file of files) {
137
- const content = createStaticRedirectContents(file, siteUrl) + '\n';
138
- file.contents = Buffer.from(content);
139
- }
140
- return []
141
- }
142
-
143
- function unpublish (files) {
144
- files.forEach((file) => delete file.out)
145
- return []
146
- }
147
-
148
- function computeRelativeUrlPath (from, to) {
149
- if (to === from) return to.charAt(to.length - 1) === '/' ? './' : path.basename(to)
150
- return (path.relative(path.dirname(from + '.'), to) || '.') + (to.charAt(to.length - 1) === '/' ? '/' : '')
151
- }
152
-
153
- function ensureTrailingSlash (str) {
154
- return str.charAt(str.length - 1) === '/' ? str : str + '/'
155
- }
156
-
157
- function stripTrailingSlash (str, root = '/') {
158
- if (str === '/') return root
159
- const lastIdx = str.length - 1
160
- return str.charAt(lastIdx) === '/' ? str.substr(0, lastIdx) : str
161
- }
162
-
163
- function regexpEscape (str) {
164
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // don't escape "-" since it's meaningless in a literal
165
- }
166
-