Skip to content

DOC-5191: Update local preview assets #203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions lib/icon-macro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* This macro relies on the material-icons font and the lucide icons font being loaded in UI bundle.
*
* @example Material Icon
* icon:material:menu-open[]
*
* @example Lucide Icon
* icon:boom-box[]
*/
function inlineIconMacro() {
return function () {
this.process((parent, target, attrs) => {
if (target.startsWith("material:")) {
iconTarget = target
.replace("material:", "")
.trim()
.replace("-", "_");
return this.createInlinePass(
parent,
`<i ${htmlAttrs(attrs, "material-icons")}>${iconTarget}</i>` + renderName(attrs?.name)
);
} else {
iconTarget = target
.replace("lucide:", "")
.trim()
return this.createInlinePass(
parent,
`<i ${htmlAttrs(attrs, `icon-${iconTarget}`)}></i>` + renderName(attrs?.name)
);
}
});
};
}

function renderName(name) {
if (!name) return "";
return ` <b>${name}</b>`;
}

function htmlAttrs({ size, role, alt, title, ariaLabel, $positional = [] }, klass) {
const [posSize] = $positional;
return [
(size || posSize) && `style="font-size: ${(size || posSize).replace("px", "").trim()}px;"`,
(role || klass) && `class="${[klass, role].filter(Boolean).join(" ")}"`,
title && `title="${title}"`,
(alt || ariaLabel) && `aria-label="${alt || ariaLabel}" role="img"`,
!(alt || ariaLabel) && "aria-hidden='true'",
]
.filter(Boolean)
.join(" ");
}

/**
* @param { import("@asciidoctor/core/types").Asciidoctor.Extensions.Registry } registry
* @param context
*/
function register(registry) {
registry.inlineMacro("icon", inlineIconMacro());
}

module.exports.register = register;
101 changes: 50 additions & 51 deletions lib/svg-macro.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ const logger = require("@antora/logger")("asciidoctor:svg-macro");
* svg:ROOT:ui/icons/vector.svg[]
*/
function inlineSvgMacro({ contentCatalog, file }) {
/**
* @this { import("@asciidoctor/core/types").Asciidoctor.Extensions.InlineMacroProcessorInstance }
*/
return function () {
this.process((parent, target, attrs) => {
svgContent = getSvgContent(target, file, contentCatalog);
const svgContent = getSvgContent(target, file, contentCatalog);
if (!svgContent) return;
const html = svgContent.replace("<svg", `<svg ${htmlAttrs(attrs, "inline-block")}`);
return this.createInlinePass(
parent,
svgContent.replace("<svg", `<svg ${htmlAttrs(attrs)}`)
insertLink(insertTitle(html, attrs.title), attrs.link, attrs.window) + renderName(attrs.name)
);
});
};
Expand All @@ -22,54 +26,49 @@ function inlineSvgMacro({ contentCatalog, file }) {
* svg::home:diagrams/graphic.svg[alt="My Graphic"]
*/
function blockSvgMacro({ contentCatalog, file }) {
/**
* @this { import("@asciidoctor/core/types").Asciidoctor.Extensions.BlockMacroProcessorInstance }
*/
return function () {
this.process((parent, target, attrs) => {
svgContent = getSvgContent(target, file, contentCatalog);
const svgContent = getSvgContent(target, file, contentCatalog);
if (!svgContent) return;
const svgHtmlAttrs = htmlAttrs({ ...attrs, role: undefined });
const containerHtmlAttrs = attrs.role
? `class="imageblock ${attrs.role}"`
: 'class="imageblock"';
const html =
`<div ${containerHtmlAttrs}><div class="content">` +
svgContent.replace("<svg", `<svg ${svgHtmlAttrs}`) +
"</div></div>";
return this.createBlock(parent, "pass", html);
// create an image block and convert it to an html string
const imageBlockNode = this.createImageBlock(parent, { ...attrs, target});
imageBlockNode.setId(attrs.id);
const imageBlockContent = imageBlockNode.convert();
// replace the <img> tag with the svg content
const svg = svgContent.replace("<svg", `<svg ${htmlAttrs({ ...attrs, role: undefined, id: undefined })}`);
const svgBlockContent = imageBlockContent.replace(/<img [^>]*>/, svg);
// return a passthrough block with the html content
return this.createPassBlock(parent, svgBlockContent);
});
};
}

/**
* This macro relies on the material-icons font being loaded in UI bundle.
*
* @example Material Icon
* icon:material-icons:menu_open[]
*
* @example Embedded SVG
* icon:ROOT:ui/icons/vector.svg[]
*/
function inlineIconMacro({ contentCatalog, file }) {
return function () {
this.process((parent, target, attrs) => {
if (target.startsWith("material-icons")) {
iconTarget = target
.replace("material-icons:", "")
.trim()
.replace("-", "_");
return this.createInlinePass(
parent,
`<i ${htmlAttrs(attrs, "material-icons icon")}>${iconTarget}</i>`
);
} else {
svgContent = getSvgContent(target, file, contentCatalog);
if (!svgContent) return;
return this.createInlinePass(
parent,
svgContent.replace("<svg", `<svg ${htmlAttrs(attrs, "svg icon")}`)
);
}
});
};
function insertTitle(svgContent, title) {
if (!title) return svgContent;
const svgMatch = svgContent.match(/<svg[^>]*>/);
const titleTag = `<title>${title}</title>`;
const svgOpenTag = svgMatch[0];
const insertionIndex = svgContent.indexOf(svgOpenTag) + svgOpenTag.length;
const updatedSvgContent =
svgContent.slice(0, insertionIndex) +
titleTag +
svgContent.slice(insertionIndex);

return updatedSvgContent;
}

function insertLink(svgContent, href, target) {
if (!href) return svgContent;
const targetAttr = target ? ` target="${target}"` : "";
return `<a href="${href}"${targetAttr}>${svgContent}</a>`;
}

function renderName(name) {
if (!name) return "";
return ` <b>${name}</b>`;
}

function getSvgContent(target, file, contentCatalog) {
Expand All @@ -84,14 +83,15 @@ function getSvgContent(target, file, contentCatalog) {
return svgContent;
}

function htmlAttrs({ width, height, role, alt, title }, klass = "svg") {
function htmlAttrs({ id, width, height, role, alt, ariaLabel, $positional = [] }, klass) {
const [posAlt, posWidth, posHeight] = $positional;
return [
width && `width="${width}"`,
height && `height="${height}"`,
role ? `class="${klass} ${role}"` : `class="${klass}"`,
alt && `aria-label="${alt}"`,
title && `aria-label="${title}"`,
(alt || title) && 'role="img"',
id && `id="${id}"`,
(width || posWidth) && `width="${width || posWidth}"`,
(height || posHeight) && `height="${height || posHeight}"`,
(role || klass) && `class="${[klass, role].filter(Boolean).join(" ")}"`,
(alt || ariaLabel || posAlt) && `aria-label="${alt || ariaLabel || posAlt}" role="img"`,
!(alt || ariaLabel || posAlt) && "aria-hidden='true'",
]
.filter(Boolean)
.join(" ");
Expand All @@ -104,7 +104,6 @@ function htmlAttrs({ width, height, role, alt, title }, klass = "svg") {
function register(registry, context) {
registry.inlineMacro("svg", inlineSvgMacro(context));
registry.blockMacro("svg", blockSvgMacro(context));
registry.inlineMacro("icon", inlineIconMacro(context));
}

module.exports.register = register;
84 changes: 69 additions & 15 deletions lib/tailwind-processor.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,79 @@
"use strict";

const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");

function updateTailwindConfig(configPath, outputDir, logger) {
try {
// Read the existing config
const configContent = fs.readFileSync(configPath, 'utf8');

// Create a temporary file with updated content
const updatedContent = configContent.replace(
/content:\s*\[.*?'\.\/build\/site\/.*?'\]/s,
`content: ['${outputDir}/**/*.{html,js}']`
);

const tempConfigPath = path.join(path.dirname(configPath), 'tailwind.temp.js');
fs.writeFileSync(tempConfigPath, updatedContent);
logger.info(`Created temporary Tailwind config with updated output dir: ${outputDir}`);

return tempConfigPath;
} catch (error) {
logger.error(`Error updating Tailwind config: ${error.message}`);
throw error;
}
}

function cleanup(tempConfigPath, logger) {
try {
if (fs.existsSync(tempConfigPath)) {
fs.unlinkSync(tempConfigPath);
logger.info('Cleaned up temporary Tailwind config');
}
} catch (error) {
logger.warn(`Error cleaning up temporary config: ${error.message}`);
}
}

module.exports.register = (context) => {
context.once("sitePublished", ({ playbook }) => {
const logger = context.getLogger('tailwind-processor-extension')
const logger = context.getLogger('tailwind-processor-extension');
const outputDir = playbook?.output?.dir || "build/site";

logger.info("Building Tailwind");
var configPath = execSync(`find ${outputDir} -name tailwind.config.js`)
.toString()
.trim();
var cssPath = execSync(`find ${outputDir} -name site*.css`)
.toString()
.trim();
logger.info(
`npm run tailwindcss --tailwind-config-path=${configPath} --css-path=${cssPath}`
);
execSync(
`npm run tailwindcss --tailwind-config-path=${configPath} --css-path=${cssPath}`,
{ stdio: "inherit" }
);
logger.info("Tailwind Build Successful");

try {
// Find the config and CSS files
const configPath = execSync(`find ${outputDir} -name tailwind.config.js`)
.toString()
.trim();

const cssPath = execSync(`find ${outputDir} -name site*.css`)
.toString()
.trim();

// Create temporary config with updated output directory
const tempConfigPath = updateTailwindConfig(configPath, outputDir, logger);

// Run Tailwind with the temporary config
logger.info(
`Running Tailwind with config: ${tempConfigPath} and CSS: ${cssPath}`
);

execSync(
`npm run tailwindcss --tailwind-config-path=${tempConfigPath} --css-path=${cssPath}`,
{ stdio: "inherit" }
);

// Clean up
cleanup(tempConfigPath, logger);

logger.info("Tailwind Build Successful");
} catch (error) {
logger.error(`Tailwind build failed: ${error.message}`);
throw error;
}
});
};
25 changes: 21 additions & 4 deletions local-preview-playbook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ runtime:
failure_level: warn
git:
# ensure_git_suffix: false # Enable if necessary -- some git services don’t recognize the URL if it contains the .git extension.
fetch_concurrency: 10
fetch_concurrency: 8

site:
title: DataStax Docs
Expand All @@ -15,13 +15,17 @@ content:
sources:
- url: .
branches: HEAD
- url: https://github.com/riptano/docs-common.git
# To incorporate the currently-checked-out branch (HEAD) from your _local_ docs-common clone:
# - url: ../docs-common
# branches: HEAD

antora:
extensions:
- '@antora/atlas-extension'
- '@antora/collector-extension'
- lib/assets-processor.js
- lib/tailwind-processor.js
- '@antora/atlas-extension'
- id: unlisted-pages
enabled: true
require: lib/unlisted-pages-extension.js
Expand All @@ -31,10 +35,11 @@ antora:
asciidoc:
extensions:
- '@asciidoctor/tabs'
- asciidoctor-external-callout
- asciidoctor-kroki
- lib/icon-macro.js
- lib/remote-include-processor.js
- lib/svg-macro.js
- asciidoctor-kroki
- asciidoctor-external-callout
attributes:
# BUILT-IN ATTRIBUTES
# allow-uri-read: '' # Quality-of-life benefit for IntelliJ users. CAUTION: Opens the door to malicious code insertion - must remain disabled in prod build environment.
Expand All @@ -51,13 +56,19 @@ asciidoc:
example-caption: false
figure-caption: false
table-caption: false
table-stripes: 'hover'
xrefstyle: short
# CUSTOM ATTRIBUTES
company: 'DataStax'
astra_db: 'Astra DB'
astra_stream: 'Astra Streaming'
astra-stream: 'Astra Streaming' # AIM: Once all instances of astra_stream are removed, keep only the astra-stream attribute.
astra_ui: 'Astra Portal'
astra_cli: 'Astra CLI'
astra-cli: 'Astra CLI' # AIM: Once all instances of astra_cli are removed, keep only the astra-cli attribute.
scb: 'Secure Connect Bundle (SCB)'
scb-short: 'SCB'
scb-brief: 'Secure Connect Bundle'
astra-streaming-examples-repo: 'https://raw.githubusercontent.com/datastax/astra-streaming-examples/master'
luna-streaming-examples-repo: 'https://raw.githubusercontent.com/datastaxdevs/luna-streaming-examples/main'
support_url: 'https://support.datastax.com'
Expand All @@ -71,6 +82,12 @@ asciidoc:
classic_cap: 'Classic'
serverless: 'serverless'
serverless_cap: 'Serverless'
py-client-api-ref-url: 'xref:astra-api-docs:ROOT:attachment$python-client-1x/astrapy'
ts-client-api-ref-url: 'xref:astra-api-docs:ROOT:attachment$typescript-client-1x'
java-client-api-ref-url: 'xref:astra-api-docs:ROOT:attachment$java-client-1x'
py-client-api-ref-url-2x: 'xref:astra-api-docs:ROOT:attachment$python-client/astrapy'
ts-client-api-ref-url-2x: 'xref:astra-api-docs:ROOT:attachment$typescript-client'
java-client-api-ref-url-2x: 'xref:astra-api-docs:ROOT:attachment$java-client'
# Antora Atlas
primary-site-url: https://docs.datastax.com/en
primary-site-manifest-url: https://docs.datastax.com/en/site-manifest.json
Expand Down
13 changes: 6 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
},
"dependencies": {
"@antora/atlas-extension": "^1.0.0-alpha.2",
"@antora/collector-extension": "^1.0.0-alpha.3",
"@antora/collector-extension": "^1.0.1",
"@asciidoctor/tabs": "^1.0.0-beta.6",
"antora": "3.2.0-alpha.4",
"asciidoctor-external-callout": "~1.2.1",
"asciidoctor-kroki": "~0.18.1",
"csv-parser": "^3.0.0",
"antora": "3.2.0-alpha.8",
"asciidoctor-external-callout": "^1.2.1",
"asciidoctor-kroki": "^0.18.1",
"linkinator": "^5.0.2",
"lodash": "^4.17.21",
"npm-run-all": "^4.1.5",
"tailwindcss": "^3.3.5"
"tailwindcss": "^3.3.3"
}
}