@redpanda-data/docs-extensions-and-macros 3.2.12 → 3.3.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.adoc
CHANGED
|
@@ -77,6 +77,22 @@ antora:
|
|
|
77
77
|
index-latest-only: true
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
=== Component category aggregator
|
|
81
|
+
|
|
82
|
+
This extension maps Redpanda Connect component data into a structured format:
|
|
83
|
+
|
|
84
|
+
- Maps original component names to common names.
|
|
85
|
+
- Populates `connectCategoriesData` and `flatComponentsData` attributes.
|
|
86
|
+
- Skips deprecated components.
|
|
87
|
+
|
|
88
|
+
==== Registration Example
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
antora:
|
|
92
|
+
extensions:
|
|
93
|
+
- require: '@redpanda-data/docs-extensions-and-macros/extensions/generate-rp-connect-categories'
|
|
94
|
+
```
|
|
95
|
+
|
|
80
96
|
=== Version fetcher
|
|
81
97
|
|
|
82
98
|
This extension fetches the latest release versions from GitHub.
|
|
@@ -420,6 +436,66 @@ asciidoc:
|
|
|
420
436
|
- '@redpanda-data/docs-extensions-and-macros/macros/helm-ref'
|
|
421
437
|
----
|
|
422
438
|
|
|
439
|
+
=== components_by_category
|
|
440
|
+
|
|
441
|
+
This macro generates a tabbed interface to display Redpanda Connect components by category.
|
|
442
|
+
|
|
443
|
+
The categories are fetched from the `connectCategoriesData` that's generated in the << Component category aggregator>> extension.
|
|
444
|
+
|
|
445
|
+
==== Usage
|
|
446
|
+
|
|
447
|
+
```asciidoc
|
|
448
|
+
components_by_category::[<type>]
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
==== Registration example
|
|
452
|
+
|
|
453
|
+
```yaml
|
|
454
|
+
asciidoc:
|
|
455
|
+
extensions:
|
|
456
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/rp-connect-components'
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
=== component_table
|
|
460
|
+
|
|
461
|
+
This macro generates a searchable table of all Redpanda Connect components with filters for support and type.
|
|
462
|
+
|
|
463
|
+
The types are fetched from the `flatComponentsData` that's generated in the << Component category aggregator>> extension.
|
|
464
|
+
|
|
465
|
+
==== Usage
|
|
466
|
+
|
|
467
|
+
```asciidoc
|
|
468
|
+
component_table::[]
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
==== Registration example
|
|
472
|
+
|
|
473
|
+
```yaml
|
|
474
|
+
asciidoc:
|
|
475
|
+
extensions:
|
|
476
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/rp-connect-components'
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
=== component_type_dropdown
|
|
480
|
+
|
|
481
|
+
This macro generates a dropdown of other supported types for a particular component, allowing users to switch between different types.
|
|
482
|
+
|
|
483
|
+
The types are fetched from the `flatComponentsData` that's generated in the << Component category aggregator>> extension.
|
|
484
|
+
|
|
485
|
+
==== Usage
|
|
486
|
+
|
|
487
|
+
```asciidoc
|
|
488
|
+
component_type_dropdown::[]
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
==== Registration example
|
|
492
|
+
|
|
493
|
+
```yaml
|
|
494
|
+
asciidoc:
|
|
495
|
+
extensions:
|
|
496
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/rp-connect-components'
|
|
497
|
+
```
|
|
498
|
+
|
|
423
499
|
== Development quickstart
|
|
424
500
|
|
|
425
501
|
This section provides information on how to develop this project.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports.register = function ({ config }) {
|
|
4
|
+
const logger = this.getLogger('redpanda-connect-category-aggregation-extension');
|
|
5
|
+
|
|
6
|
+
// TODO: Integrate these as attributes like common-name in the autogenerated source pages.
|
|
7
|
+
// This hardcoded map is just for launch.
|
|
8
|
+
const componentNameMap = {
|
|
9
|
+
"aws_kinesis_data_streams": "AWS Kinesis Data Streams",
|
|
10
|
+
"aws_kinesis_firehose": "AWS Kinesis Firehose",
|
|
11
|
+
"aws_kinesis": "AWS Kinesis",
|
|
12
|
+
"aws_sqs": "AWS SQS",
|
|
13
|
+
"aws_sns": "AWS SNS",
|
|
14
|
+
"azure_cosmosdb": "Azure Cosmos DB",
|
|
15
|
+
"azure_table_storage": "Azure Table Storage",
|
|
16
|
+
"gcp_bigquery": "GCP BigQuery",
|
|
17
|
+
"oracle": "Oracle",
|
|
18
|
+
"snowflake_put": "Snowflake",
|
|
19
|
+
"aws_dynamodb": "AWS DynamoDB",
|
|
20
|
+
"azure_blob_storage": "Azure Blob Storage",
|
|
21
|
+
"aws_s3": "AWS S3",
|
|
22
|
+
"cassandra": "Cassandra",
|
|
23
|
+
"gcp_cloud_storage": "GCP Cloud Storage",
|
|
24
|
+
"amqp": "AMQP",
|
|
25
|
+
"avro": "Avro",
|
|
26
|
+
"awk": "AWK",
|
|
27
|
+
"aws_lambda": "AWS Lambda",
|
|
28
|
+
"azure_queue_storage": "Azure Queue Storage",
|
|
29
|
+
"clickhouse": "ClickHouse",
|
|
30
|
+
"cockroachdb_changefeed": "CockroachDB",
|
|
31
|
+
"couchbase": "Couchbase",
|
|
32
|
+
"csv": "CSV",
|
|
33
|
+
"discord": "Discord",
|
|
34
|
+
"elasticsearch": "Elasticsearch",
|
|
35
|
+
"kafka_franz": "Franz-go",
|
|
36
|
+
"gcp_pubsub": "GCP Pub/Sub",
|
|
37
|
+
"grok": "Grok",
|
|
38
|
+
"hdfs": "HDFS",
|
|
39
|
+
"http": "HTTP",
|
|
40
|
+
"javascript": "JavaScript",
|
|
41
|
+
"jmespath": "JMESPath",
|
|
42
|
+
"json_schema": "JSON Schema",
|
|
43
|
+
"kafka": "Kafka",
|
|
44
|
+
"memcached": "Memcached",
|
|
45
|
+
"msgpack": "MessagePack",
|
|
46
|
+
"mongodb": "MongoDB",
|
|
47
|
+
"mqtt": "MQTT",
|
|
48
|
+
"mysql": "MySQL",
|
|
49
|
+
"nanomsg": "nanomsg",
|
|
50
|
+
"nats": "NATS",
|
|
51
|
+
"nsq": "NSQ",
|
|
52
|
+
"opensearch": "OpenSearch",
|
|
53
|
+
"parquet": "Parquet",
|
|
54
|
+
"postgresql": "PostgreSQL",
|
|
55
|
+
"protobuf": "Protobuf",
|
|
56
|
+
"pulsar": "Pulsar",
|
|
57
|
+
"redis": "Redis",
|
|
58
|
+
"ristretto": "Ristretto",
|
|
59
|
+
"schema_registry": "Schema Registry",
|
|
60
|
+
"sentry": "Sentry",
|
|
61
|
+
"socket": "Socket",
|
|
62
|
+
"sql": "SQL",
|
|
63
|
+
"sqlite": "SQLite",
|
|
64
|
+
"trino": "Trino",
|
|
65
|
+
"wasm": "Wasm (WebAssembly)",
|
|
66
|
+
"websocket": "WebSocket",
|
|
67
|
+
"twitter_search": "X (Twitter)",
|
|
68
|
+
"xml": "XML"
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.once('contentClassified', ({ siteCatalog, contentCatalog }) => {
|
|
72
|
+
const redpandaConnect = contentCatalog.getComponents().find(component => component.name === 'redpanda-connect');
|
|
73
|
+
if (!redpandaConnect || !redpandaConnect.latest) {
|
|
74
|
+
logger.info('Could not find the redpanda-connect component');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const descriptions = redpandaConnect.latest.asciidoc.attributes.categories;
|
|
78
|
+
if (!descriptions) {
|
|
79
|
+
logger.info('No categories attribute found in redpanda-connect component');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const connectCategoriesData = {};
|
|
84
|
+
const flatComponentsData = [];
|
|
85
|
+
const types = Object.keys(descriptions);
|
|
86
|
+
|
|
87
|
+
// Initialize connectCategoriesData for each type
|
|
88
|
+
types.forEach(type => {
|
|
89
|
+
connectCategoriesData[type] = [];
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const files = contentCatalog.findBy({ component: 'redpanda-connect', family: 'page' });
|
|
94
|
+
|
|
95
|
+
files.forEach(file => {
|
|
96
|
+
const content = file.contents.toString('utf8');
|
|
97
|
+
const categoryMatch = /:categories: (.*)/.exec(content);
|
|
98
|
+
const typeMatch = /:type: (.*)/.exec(content);
|
|
99
|
+
const statusMatch = /:status: (.*)/.exec(content);
|
|
100
|
+
const pubUrl = file.pub.url;
|
|
101
|
+
const name = file.src.stem;
|
|
102
|
+
|
|
103
|
+
if (categoryMatch && typeMatch) {
|
|
104
|
+
let categories = categoryMatch[1];
|
|
105
|
+
const fileType = typeMatch[1];
|
|
106
|
+
let status = statusMatch ? statusMatch[1] : 'Enterprise';
|
|
107
|
+
if (status === 'beta' || status === 'experimental') status = 'Community';
|
|
108
|
+
if (status === 'stable') status = 'Enterprise';
|
|
109
|
+
|
|
110
|
+
// Skip deprecated components
|
|
111
|
+
if (status === 'deprecated') return;
|
|
112
|
+
|
|
113
|
+
// Map component name to common name
|
|
114
|
+
const commonName = componentNameMap[name] || name;
|
|
115
|
+
|
|
116
|
+
// Populate connectCategoriesData
|
|
117
|
+
if (types.includes(fileType) && categories) {
|
|
118
|
+
if (typeof categories === 'string') {
|
|
119
|
+
categories = categories.replace(/[\[\]"]/g, '').split(',').map(category => category.trim());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
categories.forEach(category => {
|
|
123
|
+
let categoryObj = connectCategoriesData[fileType].find(cat => cat.name === category);
|
|
124
|
+
|
|
125
|
+
if (!categoryObj) {
|
|
126
|
+
categoryObj = descriptions[fileType].find(desc => desc.name === category) || { name: category, description: "" };
|
|
127
|
+
categoryObj.items = [];
|
|
128
|
+
connectCategoriesData[fileType].push(categoryObj);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
categoryObj.items.push({ name: commonName, url: pubUrl, status: status });
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Populate flatComponentsData
|
|
136
|
+
let flatItem = flatComponentsData.find(item => item.name === commonName);
|
|
137
|
+
if (flatItem) {
|
|
138
|
+
if (!flatItem.types.includes(fileType)) {
|
|
139
|
+
flatItem.types.push({ type: fileType, url: pubUrl });
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
flatItem = { name: commonName, originalName: name, support: status, types: [{ type: fileType, url: pubUrl }] };
|
|
143
|
+
flatComponentsData.push(flatItem);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
redpandaConnect.latest.asciidoc.attributes.connectCategoriesData = connectCategoriesData;
|
|
150
|
+
redpandaConnect.latest.asciidoc.attributes.flatComponentsData = flatComponentsData;
|
|
151
|
+
logger.info(`Added Redpanda Connect data to latest Asciidoc object: ${JSON.stringify({ connectCategoriesData, flatComponentsData }, null, 2)}`);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
logger.error(`Error updating latest Asciidoc object for Redpanda Connect: ${error.message}`);
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
logger.error(`Error processing Redpanda Connect files: ${error.message}`);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
};
|
|
@@ -2,7 +2,7 @@ module.exports.register = function ({ config }) {
|
|
|
2
2
|
const { addToNavigation, unlistedPagesHeading = 'Unlisted Pages' } = config
|
|
3
3
|
const logger = this.getLogger('unlisted-pages-extension')
|
|
4
4
|
this
|
|
5
|
-
.on('navigationBuilt', ({ contentCatalog }) => {
|
|
5
|
+
.on('navigationBuilt', ({ navigationCatalog, contentCatalog }) => {
|
|
6
6
|
contentCatalog.getComponents().forEach(({ versions }) => {
|
|
7
7
|
versions.forEach(({ name: component, version, navigation: nav, url: defaultUrl }) => {
|
|
8
8
|
if (component === 'api') return;
|
|
@@ -16,7 +16,10 @@ module.exports.register = function ({ config }) {
|
|
|
16
16
|
logger.warn({ file: page.src, source: page.src.origin }, 'detected unlisted page')
|
|
17
17
|
return collector.concat(page)
|
|
18
18
|
}, [])
|
|
19
|
-
if (unlistedPages.length &&
|
|
19
|
+
if (unlistedPages.length && component === 'redpanda-connect') {
|
|
20
|
+
// Some component pages for Redpanda Connect are autogenerated. This function tries to add unlisted component pages to the nav in case a new one gets created without updating the nav.
|
|
21
|
+
addRedpandaConnectPagesToNav(nav[0].items, unlistedPages)
|
|
22
|
+
} else if (unlistedPages.length && addToNavigation) {
|
|
20
23
|
nav.push({
|
|
21
24
|
content: unlistedPagesHeading,
|
|
22
25
|
items: unlistedPages.map((page) => {
|
|
@@ -36,4 +39,31 @@ function getNavEntriesByUrl (items = [], accum = {}) {
|
|
|
36
39
|
getNavEntriesByUrl(item.items, accum)
|
|
37
40
|
})
|
|
38
41
|
return accum
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function addRedpandaConnectPagesToNav(navItems, pages) {
|
|
45
|
+
// get the Components nav section
|
|
46
|
+
let componentsSection = navItems.find(item => item.content === 'Components');
|
|
47
|
+
if (!componentsSection) return
|
|
48
|
+
|
|
49
|
+
pages.forEach(page => {
|
|
50
|
+
const dirname = page.out.dirname;
|
|
51
|
+
if (!dirname.includes('/components/')) return
|
|
52
|
+
const heading = page.asciidoc.attributes.doctitle;
|
|
53
|
+
const pathParts = dirname.split('/').slice(2); // Get the type
|
|
54
|
+
// get existing nav items inside the Components tree
|
|
55
|
+
let currentLevel = componentsSection.items;
|
|
56
|
+
|
|
57
|
+
pathParts.forEach((part, index) => {
|
|
58
|
+
const capitalizedPart = part.charAt(0).toUpperCase() + part.slice(1);
|
|
59
|
+
let section = currentLevel.find(item => item.content === capitalizedPart);
|
|
60
|
+
if (!section) {
|
|
61
|
+
section = { content: capitalizedPart, items: [], root: index === 0 };
|
|
62
|
+
currentLevel.push(section);
|
|
63
|
+
}
|
|
64
|
+
currentLevel = section.items;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
currentLevel.push({ content: page.asciidoc.navtitle || page.src.stem, url: page.pub.url, urlType: 'internal' });
|
|
68
|
+
});
|
|
39
69
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports.register = function (registry, context) {
|
|
4
|
+
let tabsCounter = 1; // Counter for generating unique IDs
|
|
5
|
+
|
|
6
|
+
// Add the category tabs for components
|
|
7
|
+
registry.blockMacro(function () {
|
|
8
|
+
const self = this;
|
|
9
|
+
self.named('components_by_category');
|
|
10
|
+
self.positionalAttributes(['type']);
|
|
11
|
+
self.process((parent, target, attrs) => {
|
|
12
|
+
const type = attrs.type;
|
|
13
|
+
const categoriesData = context.config?.attributes?.connectCategoriesData || {}
|
|
14
|
+
const categories = categoriesData[type] || {};
|
|
15
|
+
const currentTabsId = `tabs-${tabsCounter++}`; // Unique ID for this set of tabs
|
|
16
|
+
|
|
17
|
+
let tabsHtml = `
|
|
18
|
+
<div id="${currentTabsId}" class="openblock tabs is-sync is-loaded" data-sync-group-id="${type}">
|
|
19
|
+
<div class="content">
|
|
20
|
+
<div class="ulist tablist">
|
|
21
|
+
<ul role="tablist">`;
|
|
22
|
+
|
|
23
|
+
categories.forEach((category, index) => {
|
|
24
|
+
tabsHtml += `
|
|
25
|
+
<li id="${currentTabsId}-${category.name}" class="tab" tabindex="${index === 0 ? '0' : '-1'}" role="tab" data-sync-id="${category.name}" aria-controls="${currentTabsId}-${category.name}--panel" aria-selected="${index === 0}">
|
|
26
|
+
<p>${category.name}</p>
|
|
27
|
+
</li>`;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
tabsHtml += `
|
|
31
|
+
</ul>
|
|
32
|
+
</div>`;
|
|
33
|
+
|
|
34
|
+
categories.forEach((category, index) => {
|
|
35
|
+
tabsHtml += `
|
|
36
|
+
<div id="${currentTabsId}-${category.name}--panel" class="tabpanel${index === 0 ? '' : ' is-hidden'}" aria-labelledby="${currentTabsId}-${category.name}"${index === 0 ? '' : ' hidden'} role="tabpanel">
|
|
37
|
+
<div class="listingblock">
|
|
38
|
+
<div class="content">
|
|
39
|
+
<p>${category.description}</p>
|
|
40
|
+
<div class="two-column-grid">`;
|
|
41
|
+
category.items.forEach(item => {
|
|
42
|
+
tabsHtml += `
|
|
43
|
+
<a href="${item.url}" class="component-card"><strong>${item.name}</strong></a>`;
|
|
44
|
+
});
|
|
45
|
+
tabsHtml += `
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>`;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
tabsHtml += `
|
|
53
|
+
</div>
|
|
54
|
+
</div>`;
|
|
55
|
+
|
|
56
|
+
return self.createBlock(parent, 'pass', tabsHtml);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Add the searchable table of all components (component catalog)
|
|
61
|
+
registry.blockMacro(function () {
|
|
62
|
+
const self = this;
|
|
63
|
+
self.named('component_table');
|
|
64
|
+
self.process((parent, target, attrs) => {
|
|
65
|
+
const flatComponentsData = context.config?.attributes?.flatComponentsData || []
|
|
66
|
+
|
|
67
|
+
// Sort flatComponentsData alphabetically by name
|
|
68
|
+
flatComponentsData.sort((a, b) => a.name.localeCompare(b.name));
|
|
69
|
+
|
|
70
|
+
let tableHtml = `
|
|
71
|
+
<div class="table-filters">
|
|
72
|
+
<input class="table-search" type="text" id="componentTableSearch" onkeyup="filterComponentTable()" placeholder="Search for components...">
|
|
73
|
+
<select class="type-dropdown" id="supportFilter" onchange="filterComponentTable()">
|
|
74
|
+
<option value="">All Support</option>`;
|
|
75
|
+
|
|
76
|
+
// Extract unique support values for the filter
|
|
77
|
+
const uniqueSupportValues = [...new Set(flatComponentsData.map(item => item.support))];
|
|
78
|
+
uniqueSupportValues.forEach(support => {
|
|
79
|
+
tableHtml += `<option value="${support}">${support}</option>`;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
tableHtml += `
|
|
83
|
+
</select>
|
|
84
|
+
<select class="type-dropdown" id="typeFilter" onchange="filterComponentTable()">
|
|
85
|
+
<option value="">All Types</option>`;
|
|
86
|
+
|
|
87
|
+
// Extract unique types for the filter
|
|
88
|
+
const uniqueTypes = [...new Set(flatComponentsData.flatMap(item => item.types.map(typeObj => typeObj.type)))];
|
|
89
|
+
uniqueTypes.forEach(type => {
|
|
90
|
+
tableHtml += `<option value="${type}">${type.charAt(0).toUpperCase() + type.slice(1)}</option>`;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
tableHtml += `
|
|
94
|
+
</select>
|
|
95
|
+
</div>
|
|
96
|
+
<table class="tableblock frame-all grid-all stripes-even no-clip stretch component-table" id="componentTable">
|
|
97
|
+
<colgroup>
|
|
98
|
+
<col style="width: 33.3%;">
|
|
99
|
+
<col style="width: 33.3%;">
|
|
100
|
+
<col style="width: 33.3%;">
|
|
101
|
+
</colgroup>
|
|
102
|
+
<thead>
|
|
103
|
+
<tr>
|
|
104
|
+
<th class="tableblock halign-left valign-top">Connector</th>
|
|
105
|
+
<th class="tableblock halign-left valign-top">Support</th>
|
|
106
|
+
<th class="tableblock halign-left valign-top">Type</th>
|
|
107
|
+
</tr>
|
|
108
|
+
</thead>
|
|
109
|
+
<tbody>`;
|
|
110
|
+
|
|
111
|
+
flatComponentsData.forEach(item => {
|
|
112
|
+
const typeDropdown = item.types.length > 1
|
|
113
|
+
? `<select class="type-dropdown" onchange="updateComponentUrl(this, true)">
|
|
114
|
+
${item.types.map(typeObj => `<option value="${typeObj.url}">${typeObj.type.charAt(0).toUpperCase() + typeObj.type.slice(1)}</option>`).join('')}
|
|
115
|
+
</select>`
|
|
116
|
+
: item.types[0].type;
|
|
117
|
+
|
|
118
|
+
tableHtml += `
|
|
119
|
+
<tr>
|
|
120
|
+
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="${item.types[0].url}">${item.name}</a></p></td>
|
|
121
|
+
<td class="tableblock halign-left valign-top"><p class="tableblock">${item.support}</p></td>
|
|
122
|
+
<td class="tableblock halign-left valign-top"><p class="tableblock">${typeDropdown}</p></td>
|
|
123
|
+
</tr>`;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
tableHtml += `
|
|
127
|
+
</tbody>
|
|
128
|
+
</table>
|
|
129
|
+
<script>
|
|
130
|
+
function filterComponentTable() {
|
|
131
|
+
const nameInput = document.getElementById('componentTableSearch').value.toLowerCase();
|
|
132
|
+
const supportFilter = document.getElementById('supportFilter').value;
|
|
133
|
+
const typeFilter = document.getElementById('typeFilter').value;
|
|
134
|
+
const table = document.getElementById('componentTable');
|
|
135
|
+
const trs = table.getElementsByTagName('tr');
|
|
136
|
+
|
|
137
|
+
for (let i = 1; i < trs.length; i++) {
|
|
138
|
+
const nameTd = trs[i].getElementsByTagName('td')[0];
|
|
139
|
+
const supportTd = trs[i].getElementsByTagName('td')[1];
|
|
140
|
+
const typeTd = trs[i].getElementsByTagName('td')[2];
|
|
141
|
+
const typeDropdown = typeTd.querySelector('.type-dropdown');
|
|
142
|
+
let showRow =
|
|
143
|
+
(!nameInput || nameTd.textContent.toLowerCase().includes(nameInput)) &&
|
|
144
|
+
(!supportFilter || supportTd.textContent === supportFilter) &&
|
|
145
|
+
(!typeFilter || (typeDropdown ? Array.from(typeDropdown.options).some(option => option.text.toLowerCase() === typeFilter.toLowerCase()) : typeTd.textContent.toLowerCase().includes(typeFilter.toLowerCase())));
|
|
146
|
+
|
|
147
|
+
trs[i].style.display = showRow ? '' : 'none';
|
|
148
|
+
|
|
149
|
+
if (showRow && typeFilter && typeDropdown) {
|
|
150
|
+
const matchingOption = Array.from(typeDropdown.options).find(option => option.text.toLowerCase() === typeFilter.toLowerCase());
|
|
151
|
+
typeDropdown.value = matchingOption.value;
|
|
152
|
+
updateComponentUrl(typeDropdown, false);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function getQueryParams() {
|
|
157
|
+
const params = {};
|
|
158
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
159
|
+
searchParams.forEach((value, key) => {
|
|
160
|
+
params[key] = value;
|
|
161
|
+
});
|
|
162
|
+
return params;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function capitalizeFirstLetter(string) {
|
|
166
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function updateComponentUrl(select, redirect) {
|
|
170
|
+
const anchor = select.closest('tr').querySelector('a');
|
|
171
|
+
anchor.href = select.value;
|
|
172
|
+
if (redirect) {
|
|
173
|
+
window.location.href = select.value; // Redirect to the new URL
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Initialize Choices.js for type dropdowns
|
|
177
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
178
|
+
const params = getQueryParams();
|
|
179
|
+
if (params.search) {
|
|
180
|
+
document.getElementById('componentTableSearch').value = params.search;
|
|
181
|
+
}
|
|
182
|
+
if (params.support) {
|
|
183
|
+
document.getElementById('supportFilter').value = capitalizeFirstLetter(params.support);
|
|
184
|
+
}
|
|
185
|
+
if (params.type) {
|
|
186
|
+
document.getElementById('typeFilter').value = params.type;
|
|
187
|
+
}
|
|
188
|
+
filterComponentTable();
|
|
189
|
+
const typeDropdowns = document.querySelectorAll('.type-dropdown');
|
|
190
|
+
typeDropdowns.forEach(dropdown => {
|
|
191
|
+
new Choices(dropdown, { searchEnabled: false, allowHTML: true});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
</script>`;
|
|
195
|
+
|
|
196
|
+
return self.createBlock(parent, 'pass', tableHtml);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
// Add the block macro for displaying a dropdown of other supported types
|
|
200
|
+
registry.blockMacro(function () {
|
|
201
|
+
const self = this;
|
|
202
|
+
self.named('component_type_dropdown');
|
|
203
|
+
self.process((parent, target, attrs) => {
|
|
204
|
+
const attributes = parent.getDocument().getAttributes();
|
|
205
|
+
const name = attributes['doctitle'];
|
|
206
|
+
const type = attributes['type'];
|
|
207
|
+
|
|
208
|
+
if (!name || !type) {
|
|
209
|
+
console.log('Name or type attribute is missing');
|
|
210
|
+
return self.createBlock(parent, 'pass', '');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const flatComponentsData = context.config?.attributes?.flatComponentsData || []
|
|
214
|
+
const component = flatComponentsData.find(item => item.originalName === name);
|
|
215
|
+
|
|
216
|
+
if (!component || component.types.length <= 1) {
|
|
217
|
+
return self.createBlock(parent, 'pass', '');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Move the page's current type to the first position in the dropdown
|
|
221
|
+
const sortedTypes = [...component.types];
|
|
222
|
+
const currentTypeIndex = sortedTypes.findIndex(typeObj => typeObj.type === type);
|
|
223
|
+
if (currentTypeIndex !== -1) {
|
|
224
|
+
const [currentType] = sortedTypes.splice(currentTypeIndex, 1);
|
|
225
|
+
sortedTypes.unshift(currentType);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const typeDropdown = `
|
|
229
|
+
<div class="page-type-dropdown">
|
|
230
|
+
<p>Type: </p>
|
|
231
|
+
<select class="type-dropdown" onchange="window.location.href=this.value">
|
|
232
|
+
${sortedTypes.map(typeObj => `<option value="${typeObj.url}" data-support="${typeObj.support}">${typeObj.type.charAt(0).toUpperCase() + typeObj.type.slice(1)}</option>`).join('')}
|
|
233
|
+
</select>
|
|
234
|
+
</div>
|
|
235
|
+
<script>
|
|
236
|
+
// Initialize Choices.js for type dropdowns
|
|
237
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
238
|
+
const typeDropdowns = document.querySelectorAll('.type-dropdown');
|
|
239
|
+
typeDropdowns.forEach(dropdown => {
|
|
240
|
+
new Choices(dropdown, { searchEnabled: false, allowHTML: true, shouldSort: false });
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
</script>`;
|
|
244
|
+
|
|
245
|
+
return self.createBlock(parent, 'pass', typeDropdown);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redpanda-data/docs-extensions-and-macros",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "Antora extensions and macros developed for Redpanda documentation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antora",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"require": "./extensions/unlisted-pages.js"
|
|
31
31
|
},
|
|
32
32
|
"./extensions/replace-attributes-in-attachments": "./extensions/replace-attributes-in-attachments.js",
|
|
33
|
+
"./extensions/generate-rp-connect-categories": "./extensions/generate-rp-connect-categories.js",
|
|
33
34
|
"./extensions/add-global-attributes": "./extensions/add-global-attributes.js",
|
|
34
35
|
"./extensions/version-fetcher/set-latest-version": "./extensions/version-fetcher/set-latest-version.js",
|
|
35
36
|
"./extensions/validate-attributes": "./extensions/validate-attributes.js",
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
"./extensions/algolia-indexer/index": "./extensions/algolia-indexer/index.js",
|
|
41
42
|
"./extensions/aggregate-terms": "./extensions/aggregate-terms.js",
|
|
42
43
|
"./macros/glossary": "./macros/glossary.js",
|
|
44
|
+
"./macros/rp-connect-components": "./macros/rp-connect-components.js",
|
|
43
45
|
"./macros/config-ref": "./macros/config-ref.js",
|
|
44
46
|
"./macros/helm-ref": "./macros/helm-ref.js"
|
|
45
47
|
},
|