diff --git a/package-lock.json b/package-lock.json index a657283bca..2b9dd11e92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "devDependencies": { "@resvg/resvg-js": "^2.6.2", "highlight.js": "11.11.1", + "lucide": "1.24.0", "markdown-it": "14.2.0", "markdown-it-anchor": "9.2.0", "mermaid": "11.16.0", @@ -1494,6 +1495,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lucide": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/lucide/-/lucide-1.24.0.tgz", + "integrity": "sha512-oMAaeuNDc5VCnBb3IjwKYGRT56tqanUm1fyDFT5Tl8hWSZND59gztgjvXje08jKLPVAq0gHJcwZUE8GCQxzBeg==", + "dev": true, + "license": "ISC" + }, "node_modules/markdown-it": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", diff --git a/package.json b/package.json index d94f0cf025..9d6f5d4b22 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "devDependencies": { "@resvg/resvg-js": "^2.6.2", "highlight.js": "11.11.1", + "lucide": "1.24.0", "markdown-it": "14.2.0", "markdown-it-anchor": "9.2.0", "mermaid": "11.16.0", diff --git a/scripts/docs-site/mdx-ish.mjs b/scripts/docs-site/mdx-ish.mjs index f60d803ff1..f702d18821 100644 --- a/scripts/docs-site/mdx-ish.mjs +++ b/scripts/docs-site/mdx-ish.mjs @@ -20,6 +20,7 @@ import sql from "highlight.js/lib/languages/sql"; import typescript from "highlight.js/lib/languages/typescript"; import xml from "highlight.js/lib/languages/xml"; import yaml from "highlight.js/lib/languages/yaml"; +import { icons as lucideIcons } from "lucide"; const markerPrefix = "OPENCLAW_DOCS_MARKER"; const inlineMarkerPrefix = "OPENCLAW_DOCS_INLINE"; @@ -636,20 +637,98 @@ function tileHtml(rawAttrs, selfClosing) { return `${icon}
${escapeHtml(title)}${end}`; } +// Docs author icons with Lucide names (docs.json declares icons.library "lucide"); +// legacy Font Awesome-style names still in the corpus map through ICON_ALIASES. +// Unknown names keep the generic fallback tile so a typo never breaks a build. +const ICON_ALIASES = { + "arrow-up-right-from-square": "external-link", + "arrows-rotate": "refresh-cw", + "arrows-turn-right": "corner-down-right", + "bars-staggered": "align-left", + bolt: "zap", + "car-side": "car", + "cart-shopping": "shopping-cart", + "circle-question": "circle-help", + "cloud-arrow-up": "cloud-upload", + "code-compare": "git-compare", + "code-pull-request": "git-pull-request", + comments: "messages-square", + compress: "minimize-2", + couch: "sofa", + cube: "box", + desktop: "monitor", + "diagram-project": "waypoints", + docker: "container", + "ear-listen": "ear", + "file-invoice-dollar": "receipt", + "file-lines": "file-text", + fire: "flame", + flask: "flask-conical", + gear: "settings", + gears: "settings", + grid: "grid-2x2", + "grid-2": "grid-2x2", + hashtag: "hash", + home: "house", + "house-signal": "house-wifi", + language: "languages", + "layer-group": "layers", + "list-check": "list-checks", + "magnifying-glass": "search", + message: "message-square", + "message-lines": "message-square-text", + microchip: "cpu", + microphone: "mic", + mobile: "smartphone", + "people-roof": "users", + "pen-ruler": "pencil-ruler", + "photo-film": "images", + "plane-departure": "plane-takeoff", + print: "printer", + "puzzle-piece": "puzzle", + "ranking-star": "trophy", + ring: "circle", + robot: "bot", + "scale-balanced": "scale", + seedling: "sprout", + "shield-exclamation": "shield-alert", + "shield-halved": "shield-half", + sitemap: "network", + // Lucide carries no brand marks; hash is the Slack channel metaphor. + slack: "hash", + sliders: "sliders-horizontal", + "square-poll-horizontal": "chart-bar", + "toggle-on": "toggle-right", + train: "train-front", + vial: "test-tube", + "volume-high": "volume-2", + "walkie-talkie": "radio", + "wand-magic-sparkles": "wand-sparkles", + "waveform-lines": "audio-waveform", + "window-maximize": "app-window", + "wine-glass": "wine", +}; + +const ICON_FALLBACK = ``; +const iconCache = new Map(); + +function lucideIconBody(kebab) { + const pascal = kebab.split("-").map((part) => part ? part[0].toUpperCase() + part.slice(1) : part).join(""); + const nodes = lucideIcons[pascal]; + if (!nodes) return null; + return nodes + .map(([tag, attrs]) => `<${tag} ${Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ")}/>`) + .join(""); +} + function iconSvg(name) { - const paths = { - rocket: ``, - sparkles: ``, - "layout-dashboard": ``, - terminal: ``, - settings: ``, - book: ``, - globe: ``, - wrench: ``, - gear: `` - }; - const path = paths[slug(name)] ?? ``; - return ``; + const key = slug(name); + let body = iconCache.get(key); + if (body === undefined) { + body = lucideIconBody(ICON_ALIASES[key] ?? key) ?? ICON_FALLBACK; + iconCache.set(key, body); + } + return ``; } function cardGridClass(rawAttrs) {