From 09f9cb46c6d842b4019abe23f687b8b773ca0d98 Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:25:46 -0700 Subject: [PATCH 1/2] Update local preview assets --- lib/icon-macro.js | 61 ++++++++++++++++++++++ lib/svg-macro.js | 101 ++++++++++++++++++------------------- lib/tailwind-processor.js | 84 ++++++++++++++++++++++++------ local-preview-playbook.yml | 11 ++-- package.json | 13 +++-- 5 files changed, 194 insertions(+), 76 deletions(-) create mode 100644 lib/icon-macro.js diff --git a/lib/icon-macro.js b/lib/icon-macro.js new file mode 100644 index 00000000..7f40e6c4 --- /dev/null +++ b/lib/icon-macro.js @@ -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, + `${iconTarget}` + renderName(attrs?.name) + ); + } else { + iconTarget = target + .replace("lucide:", "") + .trim() + return this.createInlinePass( + parent, + `` + renderName(attrs?.name) + ); + } + }); + }; +} + +function renderName(name) { + if (!name) return ""; + return ` ${name}`; +} + +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; diff --git a/lib/svg-macro.js b/lib/svg-macro.js index e7f59282..cb6fbe26 100644 --- a/lib/svg-macro.js +++ b/lib/svg-macro.js @@ -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(" { - 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 = - `
` + - svgContent.replace("
"; - 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 tag with the svg content + const svg = svgContent.replace("]*>/, 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, - `${iconTarget}` - ); - } else { - svgContent = getSvgContent(target, file, contentCatalog); - if (!svgContent) return; - return this.createInlinePass( - parent, - svgContent.replace("]*>/); + const titleTag = `${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 `${svgContent}`; +} + +function renderName(name) { + if (!name) return ""; + return ` ${name}`; } function getSvgContent(target, file, contentCatalog) { @@ -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(" "); @@ -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; diff --git a/lib/tailwind-processor.js b/lib/tailwind-processor.js index d88f1025..0b4238d1 100644 --- a/lib/tailwind-processor.js +++ b/lib/tailwind-processor.js @@ -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; + } }); }; diff --git a/local-preview-playbook.yml b/local-preview-playbook.yml index 0ee2c6e4..9bc43337 100644 --- a/local-preview-playbook.yml +++ b/local-preview-playbook.yml @@ -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 @@ -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. diff --git a/package.json b/package.json index ec231e76..d776dbfe 100644 --- a/package.json +++ b/package.json @@ -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" } } From 1a19390913a04634823c9dbdbba0747566d6e56b Mon Sep 17 00:00:00 2001 From: Eric Schneider <37347760+eric-schneider@users.noreply.github.com> Date: Fri, 6 Jun 2025 08:37:17 -0700 Subject: [PATCH 2/2] Might as well add the latest attributes --- local-preview-playbook.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/local-preview-playbook.yml b/local-preview-playbook.yml index 9bc43337..3f7adcfb 100644 --- a/local-preview-playbook.yml +++ b/local-preview-playbook.yml @@ -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 @@ -56,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' @@ -76,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