[Docs Site] Adopt eslint (#19263)

* [Docs Site] Adopt eslint

* Demonstrate a fixable suggestion, add VSCode plugin and package.json script

* Fix slice in ModelCatalog

* Remove test error in AnchorHeading

* recreate package-lock.json

* update new .jsx components to .tsx

* amend deps, fix react types, organise ec plugins

* another attempt at fixing platform-specific deps

* fix FieldCatalog filters, remove test block from code.mdx

* use opacity instead of brightness for ruleid

* fix lockfile

* amend ruleid opacity styling

* test onetrust

* enable prefer const rule, remove onetrust test

* add save-dev
This commit is contained in:
Kian 2025-01-21 18:28:16 +00:00 committed by GitHub
parent 8adca6b9c2
commit a1bf485920
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 8153 additions and 4801 deletions

View file

@ -30,6 +30,12 @@ jobs:
- run: npm ci - run: npm ci
- run: npm run check - run: npm run check
- uses: reviewdog/action-eslint@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
fail_level: "error"
- run: npm run format:core:check - run: npm run format:core:check
## TODO: content formatting checks ## TODO: content formatting checks
- run: npm run build - run: npm run build

2
.npmrc Normal file
View file

@ -0,0 +1,2 @@
save-dev=true
save-exact=true

View file

@ -6,6 +6,7 @@
"unifiedjs.vscode-mdx", "unifiedjs.vscode-mdx",
"bradlc.vscode-tailwindcss", "bradlc.vscode-tailwindcss",
"redhat.vscode-yaml", "redhat.vscode-yaml",
"esbenp.prettier-vscode" "esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
] ]
} }

View file

@ -6,7 +6,8 @@ async function main() {
let numInfiniteRedirects = 0; let numInfiniteRedirects = 0;
let numUrlsWithFragment = 0; let numUrlsWithFragment = 0;
let numDuplicateRedirects = 0; let numDuplicateRedirects = 0;
let redirectSourceUrls: string[] = [];
const redirectSourceUrls: string[] = [];
for (const line of redirects.split("\n")) { for (const line of redirects.split("\n")) {
if (line.startsWith("#") || line.trim() === "") continue; if (line.startsWith("#") || line.trim() === "") continue;

View file

@ -2,162 +2,17 @@
import darkTheme from "solarflare-theme/themes/cloudflare-dark-color-theme.json" with { type: "json" }; import darkTheme from "solarflare-theme/themes/cloudflare-dark-color-theme.json" with { type: "json" };
import lightTheme from "solarflare-theme/themes/cloudflare-light-color-theme.json" with { type: "json" }; import lightTheme from "solarflare-theme/themes/cloudflare-light-color-theme.json" with { type: "json" };
import { definePlugin } from "@expressive-code/core"; import pluginWorkersPlayground from "./plugins/expressive-code/workers-playground.js";
import { h } from "@expressive-code/core/hast"; import pluginOutputFrame from "./plugins/expressive-code/output-frame.js";
import pluginDefaultTitles from "./plugins/expressive-code/default-titles.js";
import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections"; import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";
import lzstring from "lz-string";
/**
* @param {string} code
*/
export function serialiseWorker(code) {
const formData = new FormData();
const metadata = {
main_module: "index.js",
};
formData.set(
"index.js",
new Blob([code], {
type: "application/javascript+module",
}),
"index.js",
);
formData.set(
"metadata",
new Blob([JSON.stringify(metadata)], { type: "application/json" }),
);
return formData;
}
/**
* @param {FormData} worker
*/
export async function compressWorker(worker) {
const serialisedWorker = new Response(worker);
return lzstring.compressToEncodedURIComponent(
`${serialisedWorker.headers.get(
"content-type",
)}:${await serialisedWorker.text()}`,
);
}
function workersPlaygroundButton() {
return definePlugin({
name: "Adds 'Run Worker' button to JS codeblocks",
baseStyles: `
.run {
display: flex;
gap: 0.25rem;
flex-direction: row;
position: absolute;
inset-block-start: calc(var(--ec-brdWd) + var(--button-spacing));
inset-inline-end: calc(var(--ec-brdWd) + var(--ec-uiPadInl) * 3);
direction: ltr;
unicode-bidi: isolate;
text-decoration-color: var(--sl-color-accent);
span {
color: var(--sl-color-white);
font-family: var(--sl-font-system);
}
}
`,
hooks: {
postprocessRenderedBlock: async (context) => {
if (!context.codeBlock.meta.includes("playground")) return;
const serialised = await compressWorker(
serialiseWorker(context.codeBlock.code),
);
const url = `https://workers.cloudflare.com/playground#${serialised}`;
const runButton = h("a.run", { href: url, target: "__blank" }, [
h("span", "Run Worker in Playground"),
]);
const ast = context.renderData.blockAst;
ast.children.push(runButton);
context.renderData.blockAst = ast;
},
},
});
}
function outputCodeblocks() {
return definePlugin({
name: "Adds the '.code-output' class if 'output' is passed on the opening codefence.",
hooks: {
preprocessMetadata: async (context) => {
if (!context.codeBlock.meta.includes("output")) return;
context.codeBlock.props.frame = "none";
},
postprocessRenderedBlock: async (context) => {
if (!context.codeBlock.meta.includes("output")) return;
context.renderData.blockAst.properties.className ??= [];
if (Array.isArray(context.renderData.blockAst.properties.className)) {
context.renderData.blockAst.properties.className.push("code-output");
}
context.addStyles(`
div.expressive-code:has(figure.code-output) {
margin-top: 0 !important;
}
.code-output .copy {
display: none !important;
}
.code-output > pre {
border-top-width: 0 !important;
background: var(--sl-color-gray-6) !important;
}
.code-output > pre > code {
user-select: none;
transition: opacity 0.5s ease;
}
.code-output > pre > code:hover {
cursor: default;
opacity: 0.5;
}
`);
},
},
});
}
function defaultLanguageTitles() {
return definePlugin({
name: "Adds language-specific default titles.",
hooks: {
preprocessLanguage: async (context) => {
switch (context.codeBlock.language) {
case "powershell": {
context.codeBlock.props.title ??= "PowerShell";
break;
}
default: {
return;
}
}
},
},
});
}
export default { export default {
plugins: [ plugins: [
workersPlaygroundButton(), pluginWorkersPlayground(),
outputCodeblocks(), pluginOutputFrame(),
defaultLanguageTitles(), pluginDefaultTitles(),
pluginCollapsibleSections(), pluginCollapsibleSections(),
], ],
themes: [darkTheme, lightTheme], themes: [darkTheme, lightTheme],

42
eslint.config.js Normal file
View file

@ -0,0 +1,42 @@
import pluginJavaScript from "@eslint/js";
import pluginTypeScript from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import pluginAstro from "eslint-plugin-astro";
import pluginReactA11y from "eslint-plugin-jsx-a11y";
import globals from "globals";
/** @type {import('eslint').Linter.Config[]} */
export default [
{
languageOptions: {
globals: {
...globals.node,
},
},
},
pluginJavaScript.configs.recommended,
...pluginTypeScript.configs.recommended,
...pluginAstro.configs.recommended,
...pluginAstro.configs["jsx-a11y-recommended"],
{
files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
...pluginReact.configs.flat.recommended,
...pluginReactA11y.flatConfigs.recommended,
...pluginReact.configs.flat["jsx-runtime"],
},
{
ignores: [".astro/", ".wrangler/", "dist/", ".github/"],
},
{
rules: {
"no-var": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ ignoreRestSiblings: true },
],
},
},
];

12067
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,88 +17,92 @@
"format:core:check": "npm run format:core -- --check", "format:core:check": "npm run format:core -- --check",
"format:content": "npx prettier --write \"**/*.{md,mdx,astro}\"", "format:content": "npx prettier --write \"**/*.{md,mdx,astro}\"",
"format:data": "npx prettier --write \"**/*.{json,yaml,yml}\"", "format:data": "npx prettier --write \"**/*.{json,yaml,yml}\"",
"postinstall": "npx patch-package && npm run sync", "postinstall": "npm run sync",
"preview": "npx astro preview", "preview": "npx astro preview",
"script:optimize-svgs": "npx tsx scripts/optimize-svgs.ts", "script:optimize-svgs": "npx tsx scripts/optimize-svgs.ts",
"start": "npx astro dev", "start": "npx astro dev",
"sync": "npx astro sync", "sync": "npx astro sync",
"test": "npx vitest" "test": "npx vitest",
"lint": "npx eslint"
}, },
"devDependencies": { "devDependencies": {
"@astro-community/astro-embed-youtube": "^0.5.6", "@astrojs/check": "0.9.4",
"@astrojs/check": "^0.9.4", "@astrojs/react": "4.1.5",
"@astrojs/react": "^3.6.3", "@astrojs/rss": "4.0.11",
"@astrojs/rss": "^4.0.11", "@astrojs/sitemap": "3.2.1",
"@astrojs/sitemap": "^3.2.1", "@astrojs/starlight": "0.29.3",
"@astrojs/starlight": "^0.29.3", "@astrojs/starlight-docsearch": "0.3.0",
"@astrojs/starlight-docsearch": "^0.3.0", "@astrojs/starlight-tailwind": "2.0.3",
"@astrojs/starlight-tailwind": "^2.0.3", "@astrojs/tailwind": "5.1.4",
"@astrojs/tailwind": "^5.1.4", "@cloudflare/puppeteer": "0.0.14",
"@cloudflare/puppeteer": "^0.0.14", "@cloudflare/vitest-pool-workers": "0.6.0",
"@cloudflare/vitest-pool-workers": "^0.6.0", "@cloudflare/workers-types": "4.20250109.0",
"@cloudflare/workers-types": "^4.20241218.0", "@codingheads/sticky-header": "1.0.2",
"@codingheads/sticky-header": "^1.0.2", "@expressive-code/plugin-collapsible-sections": "0.38.3",
"@expressive-code/plugin-collapsible-sections": "^0.40.0", "@iarna/toml": "2.2.5",
"@iarna/toml": "^2.2.5", "@iconify-json/material-symbols": "1.2.12",
"@iconify-json/material-symbols": "^1.2.12", "@stoplight/json-schema-tree": "4.0.0",
"@stoplight/json-schema-tree": "^4.0.0", "@types/hast": "3.0.4",
"@types/hast": "^3.0.4", "@types/he": "1.2.3",
"@types/he": "^1.2.3", "@types/node": "22.10.7",
"@types/node": "^22.10.6", "@types/react": "19.0.7",
"@types/react": "^18.3.12", "@types/react-dom": "19.0.3",
"@types/react-dom": "^18.3.1", "@typescript-eslint/parser": "8.20.0",
"algoliasearch": "^5.19.0", "algoliasearch": "5.19.0",
"astro": "^4.16.18", "astro": "4.16.18",
"astro-breadcrumbs": "^3.3.1", "astro-breadcrumbs": "3.3.1",
"astro-icon": "^1.1.2", "astro-icon": "1.1.5",
"astro-live-code": "^0.0.5", "astro-live-code": "0.0.5",
"date-fns": "^4.1.0", "date-fns": "4.1.0",
"dedent": "^1.5.3", "dedent": "1.5.3",
"detype": "1.0.12",
"dompurify": "3.2.3", "dompurify": "3.2.3",
"dot-prop": "^9.0.0", "dot-prop": "9.0.0",
"fast-glob": "^3.3.3", "eslint": "9.18.0",
"github-slugger": "^2.0.0", "eslint-plugin-astro": "1.3.1",
"hastscript": "^9.0.0", "eslint-plugin-jsx-a11y": "6.10.2",
"he": "^1.2.0", "eslint-plugin-react": "7.37.4",
"instantsearch.css": "^8.5.1", "fast-glob": "3.3.3",
"instantsearch.js": "^4.75.7", "github-slugger": "2.0.0",
"jsonc-parser": "^3.3.1", "globals": "15.14.0",
"lz-string": "^1.5.0", "hastscript": "9.0.0",
"marked": "^15.0.6", "he": "1.2.0",
"mdast-util-mdx-expression": "^2.0.1", "instantsearch.css": "8.5.1",
"mermaid": "^11.4.1", "instantsearch.js": "4.77.0",
"node-html-parser": "^6.1.13", "jsonc-parser": "3.3.1",
"patch-package": "^8.0.0", "lz-string": "1.5.0",
"playwright": "^1.49.1", "marked": "15.0.6",
"prettier": "^3.4.2", "mdast-util-mdx-expression": "2.0.1",
"prettier-plugin-astro": "^0.14.1", "mermaid": "11.4.1",
"node-html-parser": "7.0.1",
"playwright": "1.49.1",
"prettier": "3.4.2",
"prettier-plugin-astro": "0.14.1",
"prettier-plugin-tailwindcss": "0.6.9",
"pretty-bytes": "6.1.1", "pretty-bytes": "6.1.1",
"prettier-plugin-tailwindcss": "^0.6.9", "puppeteer": "24.1.0",
"puppeteer": "^24.0.0", "react": "19.0.0",
"react": "^18.3.1", "react-dom": "19.0.0",
"react-dom": "^18.3.1", "react-markdown": "9.0.3",
"react-markdown": "^9.0.3", "redirects-in-workers": "0.0.5",
"react-textarea-autosize": "^8.5.7", "rehype-autolink-headings": "7.1.0",
"redirects-in-workers": "^0.0.5", "rehype-external-links": "3.0.0",
"rehype-autolink-headings": "^7.1.0", "rehype-mermaid": "3.0.0",
"rehype-external-links": "^3.0.0", "rehype-title-figure": "0.1.2",
"rehype-mermaid": "^3.0.0", "sharp": "0.33.5",
"rehype-title-figure": "^0.1.2", "solarflare-theme": "0.0.2",
"sharp": "^0.33.5", "starlight-image-zoom": "0.9.0",
"solarflare-theme": "^0.0.2", "starlight-links-validator": "0.14.1",
"starlight-image-zoom": "^0.9.0", "starlight-package-managers": "0.9.0",
"starlight-links-validator": "^0.14.1", "svgo": "3.3.2",
"starlight-package-managers": "^0.9.0", "tailwindcss": "3.4.17",
"svgo": "^3.3.2", "tippy.js": "6.3.7",
"tailwindcss": "^3.4.17", "ts-blank-space": "0.5.0",
"tippy.js": "^6.3.7", "tsx": "4.19.2",
"tsx": "^4.19.2", "typescript": "5.7.3",
"typescript": "^5.7.3", "typescript-eslint": "8.20.0",
"unist-util-visit": "^5.0.0", "unist-util-visit": "5.0.0",
"vitest": "2.1.8", "vitest": "2.1.8",
"wrangler": "^3.101.0", "wrangler": "3.101.0"
"yaml": "^2.7.0"
}, },
"engines": { "engines": {
"node": ">=22" "node": ">=22"

View file

@ -0,0 +1,20 @@
import { definePlugin } from "@expressive-code/core";
export default () => {
return definePlugin({
name: "Adds language-specific default titles.",
hooks: {
preprocessLanguage: async (context) => {
switch (context.codeBlock.language) {
case "powershell": {
context.codeBlock.props.title ??= "PowerShell";
break;
}
default: {
return;
}
}
},
},
});
};

View file

@ -0,0 +1,45 @@
import { definePlugin } from "@expressive-code/core";
export default () => {
return definePlugin({
name: "Adds the '.code-output' class if 'output' is passed on the opening codefence.",
hooks: {
preprocessMetadata: async (context) => {
if (!context.codeBlock.meta.includes("output")) return;
context.codeBlock.props.frame = "none";
},
postprocessRenderedBlock: async (context) => {
if (!context.codeBlock.meta.includes("output")) return;
context.renderData.blockAst.properties.className ??= [];
if (Array.isArray(context.renderData.blockAst.properties.className)) {
context.renderData.blockAst.properties.className.push("code-output");
}
context.addStyles(`
div.expressive-code:has(figure.code-output) {
margin-top: 0 !important;
}
.code-output .copy {
display: none !important;
}
.code-output > pre {
border-top-width: 0 !important;
background: var(--sl-color-gray-6) !important;
}
.code-output > pre > code {
user-select: none;
transition: opacity 0.5s ease;
}
.code-output > pre > code:hover {
cursor: default;
opacity: 0.5;
}
`);
},
},
});
};

View file

@ -0,0 +1,80 @@
import { definePlugin } from "@expressive-code/core";
import { h } from "@expressive-code/core/hast";
import lzstring from "lz-string";
export function serialiseWorker(code) {
const formData = new FormData();
const metadata = {
main_module: "index.js",
};
formData.set(
"index.js",
new Blob([code], {
type: "application/javascript+module",
}),
"index.js",
);
formData.set(
"metadata",
new Blob([JSON.stringify(metadata)], { type: "application/json" }),
);
return formData;
}
export async function compressWorker(worker) {
const serialisedWorker = new Response(worker);
return lzstring.compressToEncodedURIComponent(
`${serialisedWorker.headers.get(
"content-type",
)}:${await serialisedWorker.text()}`,
);
}
export default () => {
return definePlugin({
name: "Adds 'Run Worker' button to JS codeblocks",
baseStyles: `
.run {
display: flex;
gap: 0.25rem;
flex-direction: row;
position: absolute;
inset-block-start: calc(var(--ec-brdWd) + var(--button-spacing));
inset-inline-end: calc(var(--ec-brdWd) + var(--ec-uiPadInl) * 3);
direction: ltr;
unicode-bidi: isolate;
text-decoration-color: var(--sl-color-accent);
span {
color: var(--sl-color-white);
font-family: var(--sl-font-system);
}
}
`,
hooks: {
postprocessRenderedBlock: async (context) => {
if (!context.codeBlock.meta.includes("playground")) return;
const serialised = await compressWorker(
serialiseWorker(context.codeBlock.code),
);
const url = `https://workers.cloudflare.com/playground#${serialised}`;
const runButton = h("a.run", { href: url, target: "__blank" }, [
h("span", "Run Worker in Playground"),
]);
const ast = context.renderData.blockAst;
ast.children.push(runButton);
context.renderData.blockAst = ast;
},
},
});
};

View file

@ -60,7 +60,7 @@ async function run() {
const end = performance.now(); const end = performance.now();
const duration = end - start; const duration = end - start;
const seconds = Math.floor(duration / 1000); const seconds = Math.floor(duration / 1000);
console.log(`Optimized ${files.length} SVG files in ${seconds}s`); console.log(`Optimized ${processed} SVG files in ${seconds}s`);
console.log( console.log(
`Original size: ~${formatBytes(originalSize)}, optimized size: ~${formatBytes(optimizedSize)}. Saved ~${formatBytes(originalSize - optimizedSize)}`, `Original size: ~${formatBytes(originalSize)}, optimized size: ~${formatBytes(optimizedSize)}. Saved ~${formatBytes(originalSize - optimizedSize)}`,
); );

View file

@ -1,13 +1,23 @@
import { useState } from "react"; import { useState } from "react";
import FieldBadges from "./fields/FieldBadges"; import FieldBadges from "./fields/FieldBadges";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import type { CollectionEntry } from "astro:content";
const FieldCatalog = ({ fields }) => { type Fields = CollectionEntry<"fields">["data"]["entries"];
const [filters, setFilters] = useState({
type Filters = {
search: string;
categories: string[];
keywords: string[];
};
const FieldCatalog = ({ fields }: { fields: Fields }) => {
const [filters, setFilters] = useState<Filters>({
search: "", search: "",
categories: [], categories: [],
keywords: [], keywords: [],
}); });
const mapped = fields.sort((f1, f2) => { const mapped = fields.sort((f1, f2) => {
return f1.name < f2.name ? -1 : 1; return f1.name < f2.name ? -1 : 1;
}); });
@ -31,7 +41,7 @@ const FieldCatalog = ({ fields }) => {
if (filters.search) { if (filters.search) {
// search keywords // search keywords
let keywordFound = field.keywords?.some( const keywordFound = field.keywords?.some(
(kw) => kw.indexOf(filters.search) >= 0, (kw) => kw.indexOf(filters.search) >= 0,
); );
@ -70,16 +80,18 @@ const FieldCatalog = ({ fields }) => {
className="mr-2" className="mr-2"
value={category} value={category}
onClick={(e) => { onClick={(e) => {
if (e.target.checked) { const target = e.target as HTMLInputElement;
if (target.checked) {
setFilters({ setFilters({
...filters, ...filters,
categories: [...filters.categories, e.target.value], categories: [...filters.categories, target.value],
}); });
} else { } else {
setFilters({ setFilters({
...filters, ...filters,
categories: filters.categories.filter( categories: filters.categories.filter(
(f) => f !== e.target.value, (f) => f !== target.value,
), ),
}); });
} }

View file

@ -62,7 +62,7 @@ if (lines) {
} }
contentLines = contentLines.filter( contentLines = contentLines.filter(
(line) => !/<[\/]?docs-tag name=".*">/.test(line), (line) => !/<[/]?docs-tag name=".*">/.test(line),
); );
--- ---

View file

@ -1,4 +1,5 @@
--- ---
/* eslint-disable astro/jsx-a11y/no-noninteractive-tabindex */
import { z } from "astro:schema"; import { z } from "astro:schema";
import { getGlossaryEntry } from "~/util/glossary"; import { getGlossaryEntry } from "~/util/glossary";
import { marked } from "marked"; import { marked } from "marked";
@ -29,7 +30,7 @@ definition = definition.split(/\r?\n/)[0];
id={tooltip.term} id={tooltip.term}
data-tooltip data-tooltip
data-content={marked.parse(definition)} data-content={marked.parse(definition)}
class="border-b-2 border-dashed border-accent-600" class="border-b-2 border-dashed border-accent"
tabindex="0" tabindex="0"
>{ >{
link ? ( link ? (

View file

@ -1,6 +1,7 @@
--- ---
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import type { Props } from "@astrojs/starlight/props"; import type { Props } from "@astrojs/starlight/props";
import type { ImageMetadata } from "astro";
const { data } = Astro.props.entry; const { data } = Astro.props.entry;
const { title = data.title, tagline, image } = data.hero || {}; const { title = data.title, tagline, image } = data.hero || {};

View file

@ -2,14 +2,23 @@ import { useState } from "react";
import ModelInfo from "./models/ModelInfo"; import ModelInfo from "./models/ModelInfo";
import ModelBadges from "./models/ModelBadges"; import ModelBadges from "./models/ModelBadges";
import { authorData } from "./models/data"; import { authorData } from "./models/data";
import type { WorkersAIModelsSchema } from "~/schemas";
const ModelCatalog = ({ models }) => { type Filters = {
const [filters, setFilters] = useState({ search: string;
authors: string[];
tasks: string[];
capabilities: string[];
};
const ModelCatalog = ({ models }: { models: WorkersAIModelsSchema[] }) => {
const [filters, setFilters] = useState<Filters>({
search: "", search: "",
authors: [], authors: [],
tasks: [], tasks: [],
capabilities: [], capabilities: [],
}); });
const mapped = models.map((model) => ({ const mapped = models.map((model) => ({
model: { model: {
...model, ...model,
@ -22,6 +31,8 @@ const ModelCatalog = ({ models }) => {
if (property_id === "function_calling" && value === "true") { if (property_id === "function_calling" && value === "true") {
return "Function calling"; return "Function calling";
} }
return [];
}) })
.filter((p) => Boolean(p)), .filter((p) => Boolean(p)),
}, },
@ -101,15 +112,17 @@ const ModelCatalog = ({ models }) => {
className="mr-2" className="mr-2"
value={task} value={task}
onClick={(e) => { onClick={(e) => {
if (e.target.checked) { const target = e.target as HTMLInputElement;
if (target.checked) {
setFilters({ setFilters({
...filters, ...filters,
tasks: [...filters.tasks, e.target.value], tasks: [...filters.tasks, target.value],
}); });
} else { } else {
setFilters({ setFilters({
...filters, ...filters,
tasks: filters.tasks.filter((f) => f !== e.target.value), tasks: filters.tasks.filter((f) => f !== target.value),
}); });
} }
}} }}
@ -131,16 +144,18 @@ const ModelCatalog = ({ models }) => {
value={capability} value={capability}
className="mr-2" className="mr-2"
onClick={(e) => { onClick={(e) => {
if (e.target.checked) { const target = e.target as HTMLInputElement;
if (target.checked) {
setFilters({ setFilters({
...filters, ...filters,
capabilities: [...filters.capabilities, e.target.value], capabilities: [...filters.capabilities, target.value],
}); });
} else { } else {
setFilters({ setFilters({
...filters, ...filters,
capabilities: filters.capabilities.filter( capabilities: filters.capabilities.filter(
(f) => f !== e.target.value, (f) => f !== target.value,
), ),
}); });
} }
@ -163,16 +178,18 @@ const ModelCatalog = ({ models }) => {
className="mr-2" className="mr-2"
value={author} value={author}
onClick={(e) => { onClick={(e) => {
if (e.target.checked) { const target = e.target as HTMLInputElement;
if (target.checked) {
setFilters({ setFilters({
...filters, ...filters,
authors: [...filters.authors, e.target.value], authors: [...filters.authors, target.value],
}); });
} else { } else {
setFilters({ setFilters({
...filters, ...filters,
authors: filters.authors.filter( authors: filters.authors.filter(
(f) => f !== e.target.value, (f) => f !== target.value,
), ),
}); });
} }
@ -199,9 +216,8 @@ const ModelCatalog = ({ models }) => {
property_id === "beta" && value === "true", property_id === "beta" && value === "true",
); );
const author = const author = model.model.name.split("/")[1];
authorData[model.model.name.split("/")[1]]?.name ?? const authorInfo = authorData[author];
model.model.name.split("/")[1];
return ( return (
<a <a
@ -210,14 +226,15 @@ const ModelCatalog = ({ models }) => {
href={`/workers-ai/models/${model.model_display_name}`} href={`/workers-ai/models/${model.model_display_name}`}
> >
<div className="-mb-1 flex items-center"> <div className="-mb-1 flex items-center">
{authorData[model.model.name.split("/")[1]]?.logo ? ( {authorInfo?.logo ? (
<img <img
className="mr-2 block w-6" className="mr-2 block w-6"
src={authorData[model.model.name.split("/")[1]]?.logo} src={authorInfo.logo}
alt={`${authorInfo.name} logo`}
/> />
) : ( ) : (
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-gray-100 text-sm font-black uppercase text-gray-400"> <div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-gray-100 text-sm font-black uppercase text-gray-400">
{author.substr(0, 1)} {author.slice(0, 1)}
</div> </div>
)} )}
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-lg font-semibold"> <span className="overflow-hidden text-ellipsis whitespace-nowrap text-lg font-semibold">

View file

@ -1,39 +1,24 @@
{ ---
import.meta.env.PROD ? ( const isProduction = import.meta.env.PROD;
<>
<script const uuid = isProduction
src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js" ? "b1e05d49-f072-4bae-9116-bdb78af15448"
type="text/javascript" : "b1e05d49-f072-4bae-9116-bdb78af15448-test";
charset="UTF-8" ---
data-domain-script="b1e05d49-f072-4bae-9116-bdb78af15448"
is:inline <script
/> src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js"
<script type="text/javascript" is:inline> type="text/javascript"
function OptanonWrapper() {} charset="UTF-8"
</script> data-domain-script={uuid}
</> is:inline></script>
) : ( <script type="text/javascript" is:inline>
<> // eslint-disable-next-line @typescript-eslint/no-unused-vars
<script function OptanonWrapper() {}
src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js" </script>
type="text/javascript" <!-- OneTrust Cookies Settings button start -->
charset="UTF-8" <button id="ot-sdk-btn" class="ot-sdk-show-settings">Cookie Settings</button>
data-domain-script="b1e05d49-f072-4bae-9116-bdb78af15448-test" <!-- OneTrust Cookies Settings button end -->
is:inline
/>
<script type="text/javascript" is:inline>
function OptanonWrapper() {}
</script>
</>
)
}
<span class="DocsFooter--content-additional-wrapper">
<!-- OneTrust Cookies Settings button start -->
<a role="button" id="ot-sdk-btn" class="ot-sdk-show-settings"
>Cookie Settings</a
>
<!-- OneTrust Cookies Settings button end -->
</span>
<style> <style>
#ot-sdk-btn.ot-sdk-show-settings { #ot-sdk-btn.ot-sdk-show-settings {
@ -43,6 +28,7 @@
line-height: inherit !important; line-height: inherit !important;
padding: inherit !important; padding: inherit !important;
font-family: var(--sl-font-family) !important; font-family: var(--sl-font-family) !important;
background-color: inherit !important;
} }
#ot-sdk-btn.ot-sdk-show-settings:hover { #ot-sdk-btn.ot-sdk-show-settings:hover {

View file

@ -3,7 +3,7 @@ import { getEntry } from "astro:content";
const entry = await getEntry("pages-framework-presets", "index"); const entry = await getEntry("pages-framework-presets", "index");
const presets = entry.data.build_configs; const presets = entry.data.build_configs;
const entries = Object.entries(presets); const entries = Object.values(presets);
--- ---
<table> <table>
@ -14,7 +14,7 @@ const entries = Object.entries(presets);
</thead> </thead>
<tbody> <tbody>
{ {
entries.map(([_, value]) => ( entries.map((value) => (
<tr> <tr>
<td>{value.display_name}</td> <td>{value.display_name}</td>
<td> <td>

View file

@ -27,15 +27,14 @@ const props = z
}), }),
); );
// @ts-ignore // @ts-expect-error plans are not typed
const { id, type } = props.parse(Astro.props); const { id, type } = props.parse(Astro.props);
let availability; let availability;
if (type) { if (type) {
// @ts-ignore // @ts-expect-error plans are not typed
availability = mappings[type]; availability = mappings[type];
} else { } else {
// @ts-ignore
availability = await indexPlans(id); availability = await indexPlans(id);
} }
--- ---

View file

@ -7,11 +7,13 @@ export type ProductData = CollectionEntry<"products"> & {
groups: string[]; groups: string[];
}; };
type Filters = {
search: string;
groups: string[];
};
const ProductCatalog = ({ products }: { products: ProductData[] }) => { const ProductCatalog = ({ products }: { products: ProductData[] }) => {
const [filters, setFilters] = useState<{ const [filters, setFilters] = useState<Filters>({
search: string;
groups: string[];
}>({
search: "", search: "",
groups: [], groups: [],
}); });

View file

@ -14,10 +14,9 @@ const props = z.object({
id: zodEnumFromObjKeys(mappings), id: zodEnumFromObjKeys(mappings),
}); });
// @ts-ignore
const { id } = props.parse(Astro.props); const { id } = props.parse(Astro.props);
let stat = mappings[id]; const stat = mappings[id];
--- ---
{stat} {stat}

View file

@ -22,7 +22,7 @@ const { header, href, product } = props.parse(Astro.props);
<strong> <strong>
<a <a
href={href} href={href}
class="!text-black dark:!text-white decoration-[color:var(--orange-accent-200)]" class="!text-black decoration-[color:var(--orange-accent-200)]"
>{header}</a >{header}</a
> >
</strong> </strong>

View file

@ -1,6 +1,5 @@
--- ---
import { z } from "astro:schema"; import { z } from "astro:schema";
import { Icon } from "@astrojs/starlight/components";
type Props = z.infer<typeof props>; type Props = z.infer<typeof props>;
@ -13,11 +12,42 @@ const props = z
const { id } = props.parse(Astro.props); const { id } = props.parse(Astro.props);
--- ---
<code <rule-id id={id}>
title="Click to copy the full ID" <button title="Click to copy the full ID" class="px-0">
onclick=`navigator.clipboard.writeText("${id}")` <code class="flex">
class="inline-flex w-fit hover:bg-accent-600/50 hover:cursor-pointer active:bg-accent-600" {`...${id.slice(-8)}`}
> </code>
{`...${id.slice(-8)}`} </button>
<Icon name="document" class="!inline justify-center" /> </rule-id>
</code>
<style>
button {
transition: opacity 100ms ease;
opacity: 0.75;
&:hover {
cursor: copy;
opacity: 1;
}
}
</style>
<script>
import { addTooltip } from "~/util/tippy";
class RuleID extends HTMLElement {
handleCopy() {
navigator.clipboard.writeText(this.id);
}
connectedCallback() {
const button = this.querySelector<HTMLButtonElement>("button");
addTooltip(this, "Copied", { trigger: "click", hideAfter: 1000 });
button?.addEventListener("click", () => this.handleCopy());
}
}
customElements.define("rule-id", RuleID);
</script>

View file

@ -1,6 +1,7 @@
--- ---
import { z } from "astro:content"; import { z } from "astro:content";
import { transform } from "detype"; import tsBlankSpace from "ts-blank-space";
import { format } from "prettier";
import { parse } from "node-html-parser"; import { parse } from "node-html-parser";
import { Code, Tabs, TabItem } from "@astrojs/starlight/components"; import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
@ -58,11 +59,7 @@ if (!code) {
code = code.replace(/\u007f/g, "\n"); code = code.replace(/\u007f/g, "\n");
const js = await transform(code, "placeholder.ts", { const js = await format(tsBlankSpace(code), { parser: "babel", useTabs: true });
prettierOptions: {
useTabs: true,
},
});
const TabsWrapper = tabsWrapper ? Tabs : Fragment; const TabsWrapper = tabsWrapper ? Tabs : Fragment;
--- ---

View file

@ -1,4 +1,5 @@
--- ---
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck xmlns in SVGs makes Astro very upset, so we'll just ignore this file // @ts-nocheck xmlns in SVGs makes Astro very upset, so we'll just ignore this file
--- ---

View file

@ -9,7 +9,7 @@ const props = z.object({
products: z.array(reference("products")), products: z.array(reference("products")),
}); });
const { products } = Astro.props; const { products } = await props.parseAsync(Astro.props);
const data = await getEntries(products); const data = await getEntries(products);
--- ---

View file

@ -1,4 +1,4 @@
const FieldBadges = ({ badges }) => { const FieldBadges = ({ badges }: { badges: string[] }) => {
return ( return (
<ul className="list-none m-0 p-0 inline-flex items-center gap-2 text-xs"> <ul className="list-none m-0 p-0 inline-flex items-center gap-2 text-xs">
{badges.map((badge) => ( {badges.map((badge) => (

View file

@ -74,10 +74,7 @@ const blocks = [
size="1.5rem" size="1.5rem"
color="var(--sl-color-white)" color="var(--sl-color-white)"
/> />
<a <a href={link.href} class="pl-2 no-underline !text-black">
href={link.href}
class="pl-2 no-underline !text-black dark:!text-white"
>
{link.text} {link.text}
</a> </a>
</li> </li>

View file

@ -1,4 +1,6 @@
const ModelBadges = ({ model }) => { import type { WorkersAIModelsSchema } from "~/schemas";
const ModelBadges = ({ model }: { model: WorkersAIModelsSchema }) => {
const badges = model.properties.flatMap(({ property_id, value }) => { const badges = model.properties.flatMap(({ property_id, value }) => {
if (property_id === "lora" && value === "true") { if (property_id === "lora" && value === "true") {
return { return {

View file

@ -1,6 +1,7 @@
import type { WorkersAIModelsSchema } from "~/schemas";
import { authorData } from "./data"; import { authorData } from "./data";
const ModelInfo = ({ model }) => { const ModelInfo = ({ model }: { model: WorkersAIModelsSchema }) => {
const author = const author =
authorData[model.name.split("/")[1]]?.name ?? model.name.split("/")[1]; authorData[model.name.split("/")[1]]?.name ?? model.name.split("/")[1];
return ( return (

View file

@ -1,44 +0,0 @@
import { useState } from "react";
import TextareaAutosize from "react-textarea-autosize";
const ModelPlayground = ({ model }) => {
const [response, setResponse] = useState("");
const [prompt, setPrompt] = useState("");
const handleSubmmit = () => {
fetch("https://ai.cloudflare.com/api/inference", {
method: "POST",
body: JSON.stringify({
model: model.name,
prompt,
}),
});
};
return (
<div className="shadow-2xl shadow-zinc-100 border border-gray-200 rounded-xl">
<div className="flex m-3 border border-gray-200 rounded-md items-start hover:bg-gray-50">
<TextareaAutosize
className="w-full p-2 resize-none outline-none bg-transparent"
minRows={1}
maxRows={4}
placeholder="Enter prompt..."
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
/>
<button
onClick={handleSubmmit}
className="!m-2 rounded-md px-3 bg-gray-200 hover:bg-gray-300 cursor-pointer"
>
Submit
</button>
</div>
<div className="bg-orange-50 min-h-32 mx-3 rounded-md">{response}</div>
<span className="m-3 block text-sm text-gray-400 font-mono">
{model.name}
</span>
</div>
);
};
export default ModelPlayground;

View file

@ -6,7 +6,7 @@ import stabilityai from "../../assets/images/workers-ai/stabilityai.svg";
import huggingface from "../../assets/images/workers-ai/huggingface.svg"; import huggingface from "../../assets/images/workers-ai/huggingface.svg";
import google from "../../assets/images/workers-ai/google.svg"; import google from "../../assets/images/workers-ai/google.svg";
export const authorData = { export const authorData: Record<string, { name: string; logo: string }> = {
openai: { openai: {
name: "OpenAI", name: "OpenAI",
logo: openai.src, logo: openai.src,

View file

@ -128,15 +128,12 @@ if (
<Default {...Astro.props} /> <Default {...Astro.props} />
<div id="footer-links" class="flex flex-wrap items-center"> <div id="footer-links" class="flex flex-wrap items-center">
{Object.entries(links).map(([text, href]) => ( {Object.entries(links).map(([text, href]) => (
<a <a href={href} class="mx-2 my-2 text-xs text-black decoration-accent">
href={href}
class="mx-2 my-2 text-xs text-black decoration-accent-600 dark:text-white dark:decoration-accent-200"
>
<span>{text}</span> <span>{text}</span>
</a> </a>
))} ))}
{isProduction && ( {isProduction && (
<div class="mx-2 my-2 text-xs text-black underline decoration-accent-600 dark:text-white dark:decoration-accent-200"> <div class="mx-2 my-2 text-xs text-black [&>button]:underline [&>button]:decoration-accent">
<OneTrust /> <OneTrust />
</div> </div>
)} )}

View file

@ -4,27 +4,18 @@ import Default from "@astrojs/starlight/components/Sidebar.astro";
import { Icon as AstroIcon } from "astro-icon/components"; import { Icon as AstroIcon } from "astro-icon/components";
import { getEntry } from "astro:content"; import { getEntry } from "astro:content";
import { z } from "astro:schema";
import { Badge } from "@astrojs/starlight/components"; import { Badge } from "@astrojs/starlight/components";
import type { ComponentProps, HTMLAttributes } from "astro/types"; import type { ComponentProps, HTMLAttributes } from "astro/types";
import type { AstroBuiltinAttributes } from "astro";
const { sidebar, slug } = Astro.props; const { sidebar, slug } = Astro.props;
const linkHTMLAttributesSchema = z.record(
z.union([z.string(), z.number(), z.boolean(), z.undefined()]),
) as z.Schema<
Omit<HTMLAttributes<"a">, keyof AstroBuiltinAttributes | "children">
>;
type LinkHTMLAttributes = z.infer<typeof linkHTMLAttributesSchema>;
interface Link { interface Link {
type: "link"; type: "link";
label: string; label: string;
href: string; href: string;
isCurrent: boolean; isCurrent: boolean;
badge: ComponentProps<typeof Badge> | undefined; badge: ComponentProps<typeof Badge> | undefined;
attrs: LinkHTMLAttributes; attrs: HTMLAttributes<"a">;
order: number; order: number;
} }
type SidebarEntry = Link | Group; type SidebarEntry = Link | Group;
@ -37,7 +28,6 @@ interface Group {
badge: ComponentProps<typeof Badge> | undefined; badge: ComponentProps<typeof Badge> | undefined;
order: number; order: number;
} }
``;
const currentSection = slug?.split("/")[0]; const currentSection = slug?.split("/")[0];
@ -116,7 +106,7 @@ async function handleGroup(group: Group): Promise<SidebarEntry> {
) as number; ) as number;
if (indexPage.data.sidebar.group?.hideIndex) { if (indexPage.data.sidebar.group?.hideIndex) {
group.entries.splice(indexIdx, 1)[0]; group.entries.splice(indexIdx, 1);
return group; return group;
} }
@ -197,7 +187,7 @@ const lookupProductTitle = async (slug: string) => {
name={currentSection} name={currentSection}
class="mr-2 text-4xl text-[color:var(--orange-accent-200)]" class="mr-2 text-4xl text-[color:var(--orange-accent-200)]"
/> />
<span class="text-black dark:text-white"> <span class="text-black">
<strong> <strong>
{lookupProductTitle(slug)} {lookupProductTitle(slug)}
</strong> </strong>
@ -248,6 +238,10 @@ const lookupProductTitle = async (slug: string) => {
:root[data-theme="dark"] { :root[data-theme="dark"] {
.sidebar-content { .sidebar-content {
--sl-color-hairline-light: #444444 !important; --sl-color-hairline-light: #444444 !important;
& > * a[aria-current="page"] {
color: var(--sl-color-accent-high) !important;
}
} }
} }
</style> </style>

View file

@ -1,6 +1,5 @@
import { z, defineCollection } from "astro:content"; import { z, defineCollection } from "astro:content";
import { docsSchema, i18nSchema } from "@astrojs/starlight/schema"; import { docsSchema, i18nSchema } from "@astrojs/starlight/schema";
import { file } from "astro/loaders";
import { import {
appsSchema, appsSchema,
changelogsSchema, changelogsSchema,
@ -12,8 +11,8 @@ import {
glossarySchema, glossarySchema,
learningPathsSchema, learningPathsSchema,
videosSchema, videosSchema,
workersAiModelsSchema,
warpReleasesSchema, warpReleasesSchema,
workersAiSchema,
changelogsNextSchema, changelogsNextSchema,
fieldsSchema, fieldsSchema,
} from "~/schemas"; } from "~/schemas";
@ -67,7 +66,7 @@ export const collections = {
type: "data", type: "data",
}), }),
"workers-ai-models": defineCollection({ "workers-ai-models": defineCollection({
schema: workersAiSchema, schema: workersAiModelsSchema,
type: "data", type: "data",
}), }),
videos: defineCollection({ videos: defineCollection({

View file

@ -103,7 +103,13 @@ export default {
## TypeScript examples ## TypeScript examples
The `TypeScriptExample` component uses [`detype`](https://www.npmjs.com/package/detype) to remove TypeScript-specific syntax from your example and provide a JavaScript tab. This reduces maintenance burden by only having a single example to maintain. The `TypeScriptExample` component uses [`ts-blank-space`](https://github.com/bloomberg/ts-blank-space) to remove TypeScript-specific syntax from your example and provide a JavaScript tab. This reduces maintenance burden by only having a single example to maintain.
:::note
Some TypeScript syntax influences runtime behaviour, and cannot be stripped.
Please refer to the [Unsupported Syntax](https://github.com/bloomberg/ts-blank-space?tab=readme-ov-file#unsupported-syntax) section of the project's README.
:::
### Input ### Input

View file

@ -217,7 +217,7 @@ const recommendedSection = {
<a <a
href={link.href} href={link.href}
target={link.target} target={link.target}
class="!text-black hover:!text-accent-600 dark:!text-accent-200 dark:hover:!text-white" class="!text-black hover:!text-accent dark:!text-accent-high dark:hover:!text-black"
> >
{link.text} {link.text}
</a> </a>

View file

@ -40,7 +40,7 @@ const products = lpProducts(learningPaths);
</Description> </Description>
<div class="flex"> <div class="flex">
<div class="w-1/4 pr-4 hidden lg:block"> <div class="w-1/4 pr-4 hidden lg:block">
<div class="border-b-2 border-accent-600 dark:border-accent-200"> <div class="border-b-2 border-accent">
<h2>Filters</h2> <h2>Filters</h2>
</div> </div>
<div id="areas-filter"> <div id="areas-filter">

View file

@ -1,6 +1,6 @@
--- ---
import { getCollection, type CollectionEntry } from "astro:content"; import { getCollection, type CollectionEntry } from "astro:content";
// @ts-ignore virtual module // @ts-expect-error virtual module
import iconCollection from "virtual:astro-icon"; import iconCollection from "virtual:astro-icon";
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
import { getIconData, iconToSVG } from "@iconify/utils"; import { getIconData, iconToSVG } from "@iconify/utils";

View file

@ -1,12 +1,12 @@
--- ---
import type { GetStaticPaths, MarkdownHeading } from "astro"; import type { GetStaticPaths } from "astro";
import { marked } from "marked"; import { marked } from "marked";
import { getEntry } from "astro:content"; import { getEntry } from "astro:content";
import StarlightPage, { import StarlightPage, {
type StarlightPageProps, type StarlightPageProps,
} from "@astrojs/starlight/components/StarlightPage.astro"; } from "@astrojs/starlight/components/StarlightPage.astro";
import { Code, Aside, Type } from "~/components"; import { Code, Aside, Type } from "~/components";
import FieldBadges from "~/components/fields/FieldBadges.jsx"; import FieldBadges from "~/components/fields/FieldBadges.tsx";
export const getStaticPaths = (async () => { export const getStaticPaths = (async () => {
const fields = await getEntry("fields", "index"); const fields = await getEntry("fields", "index");
@ -28,7 +28,6 @@ const { field } = Astro.props;
// set in /src/pages/changelog/index/index.xml.ts // set in /src/pages/changelog/index/index.xml.ts
marked.use({ walkTokens: null }); marked.use({ walkTokens: null });
let CodeExamples = null;
const description = field.description; const description = field.description;
// Strong type coercion needed due to Starlight's component override for hideTitle // Strong type coercion needed due to Starlight's component override for hideTitle
@ -119,7 +118,7 @@ const starlightPageProps = {
<div class="!mt-8"> <div class="!mt-8">
<span class="text-xs" <span class="text-xs"
>Categories: <FieldBadges badges={field.categories} /></span >Categories: <FieldBadges badges={field.categories as string[]} /></span
> >
</div> </div>
</StarlightPage> </StarlightPage>

View file

@ -1,7 +1,7 @@
--- ---
import { getEntry } from "astro:content"; import { getEntry } from "astro:content";
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
import FieldCatalog from "~/components/FieldCatalog.jsx"; import FieldCatalog from "~/components/FieldCatalog.tsx";
const fieldData = await getEntry("fields", "index"); const fieldData = await getEntry("fields", "index");
const fields = fieldData.data.entries; const fields = fieldData.data.entries;

View file

@ -131,7 +131,7 @@ import "instantsearch.css/themes/satellite.css";
return (widgetParams: any) => { return (widgetParams: any) => {
const widget = makeWidget(widgetParams); const widget = makeWidget(widgetParams);
let state: { const state: {
windowClickListener?: (event: MouseEvent) => void; windowClickListener?: (event: MouseEvent) => void;
} = {}; } = {};
let rootElem: HTMLElement | null; let rootElem: HTMLElement | null;

View file

@ -5,8 +5,8 @@ import StarlightPage, {
type StarlightPageProps, type StarlightPageProps,
} from "@astrojs/starlight/components/StarlightPage.astro"; } from "@astrojs/starlight/components/StarlightPage.astro";
import { LinkButton, Tabs, TabItem, Code, Aside } from "~/components"; import { LinkButton, Tabs, TabItem, Code, Aside } from "~/components";
import ModelInfo from "~/components/models/ModelInfo.jsx"; import ModelInfo from "~/components/models/ModelInfo.tsx";
import ModelBadges from "~/components/models/ModelBadges.jsx"; import ModelBadges from "~/components/models/ModelBadges.tsx";
import SchemaViewer from "~/components/models/SchemaViewer.astro"; import SchemaViewer from "~/components/models/SchemaViewer.astro";
import TextGenerationCode from "~/components/models/code/TextGenerationCode.astro"; import TextGenerationCode from "~/components/models/code/TextGenerationCode.astro";
@ -123,7 +123,11 @@ const starlightPageProps = {
<div class="flex align-center"> <div class="flex align-center">
{ {
author ? ( author ? (
<img class="mr-4 w-12 h-12 block" src={author.logo} /> <img
class="mr-4 w-12 h-12 block"
src={author.logo}
alt={`${author.name} logo`}
/>
) : ( ) : (
<div class="w-12 h-12 mr-4 rounded-md bg-gray-100 text-gray-400 uppercase text-2xl font-black flex justify-center items-center"> <div class="w-12 h-12 mr-4 rounded-md bg-gray-100 text-gray-400 uppercase text-2xl font-black flex justify-center items-center">
{model.name.split("/")[1].substring(0, 1)} {model.name.split("/")[1].substring(0, 1)}

View file

@ -2,7 +2,7 @@
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
import ModelCatalog from "~/components/ModelCatalog.jsx"; import ModelCatalog from "~/components/ModelCatalog.tsx";
const modelData = await getCollection("workers-ai-models"); const modelData = await getCollection("workers-ai-models");
const models = modelData.map(({ data }) => data); const models = modelData.map(({ data }) => data);

View file

@ -19,7 +19,7 @@ export async function GET() {
} }
// omit sort_date from output // omit sort_date from output
const { sort_date, ...data } = x.data; const { sort_date: _, ...data } = x.data;
return { return {
...data, ...data,
description: x.body.trim(), description: x.body.trim(),

View file

@ -1,6 +1,8 @@
import { z } from "astro:schema"; import { z } from "astro:schema";
export const workersAiSchema = z.object({ export type WorkersAIModelsSchema = z.infer<typeof workersAiModelsSchema>;
export const workersAiModelsSchema = z.object({
id: z.string(), id: z.string(),
source: z.number(), source: z.number(),
name: z.string(), name: z.string(),

View file

@ -1,8 +1,23 @@
import tippy from "tippy.js"; import tippy from "tippy.js";
import { type Props } from "tippy.js";
export function addTooltip(element: HTMLElement, content: string) { type Options = Partial<Props & { hideAfter: number }>;
export function addTooltip(
element: HTMLElement,
content: string,
opts?: Options,
) {
const id = "#" + CSS.escape(element.id); const id = "#" + CSS.escape(element.id);
if (opts?.hideAfter) {
opts.onShow = (instance) => {
setTimeout(() => {
instance.hide();
}, opts.hideAfter);
};
}
tippy(id, { tippy(id, {
content, content,
allowHTML: true, allowHTML: true,
@ -14,5 +29,6 @@ export function addTooltip(element: HTMLElement, content: string) {
// cutoff by the sidebar // cutoff by the sidebar
// https://atomiks.github.io/tippyjs/v6/faq/#my-tooltip-appears-cut-off-or-is-not-showing-at-all // https://atomiks.github.io/tippyjs/v6/faq/#my-tooltip-appears-cut-off-or-is-not-showing-at-all
appendTo: document.body, appendTo: document.body,
...opts,
}); });
} }

View file

@ -23,6 +23,7 @@ export default {
}, },
accent: { accent: {
DEFAULT: "var(--sl-color-accent)", DEFAULT: "var(--sl-color-accent)",
high: "var(--tw-accent-200)",
200: "var(--tw-accent-200)", 200: "var(--tw-accent-200)",
600: "var(--tw-accent-600)", 600: "var(--tw-accent-600)",
900: "var(--tw-accent-900)", 900: "var(--tw-accent-900)",