[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 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
## TODO: content formatting checks
- 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",
"bradlc.vscode-tailwindcss",
"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 numUrlsWithFragment = 0;
let numDuplicateRedirects = 0;
let redirectSourceUrls: string[] = [];
const redirectSourceUrls: string[] = [];
for (const line of redirects.split("\n")) {
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 lightTheme from "solarflare-theme/themes/cloudflare-light-color-theme.json" with { type: "json" };
import { definePlugin } from "@expressive-code/core";
import { h } from "@expressive-code/core/hast";
import pluginWorkersPlayground from "./plugins/expressive-code/workers-playground.js";
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 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 {
plugins: [
workersPlaygroundButton(),
outputCodeblocks(),
defaultLanguageTitles(),
pluginWorkersPlayground(),
pluginOutputFrame(),
pluginDefaultTitles(),
pluginCollapsibleSections(),
],
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 },
],
},
},
];

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

View file

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

View file

@ -62,7 +62,7 @@ if (lines) {
}
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 { getGlossaryEntry } from "~/util/glossary";
import { marked } from "marked";
@ -29,7 +30,7 @@ definition = definition.split(/\r?\n/)[0];
id={tooltip.term}
data-tooltip
data-content={marked.parse(definition)}
class="border-b-2 border-dashed border-accent-600"
class="border-b-2 border-dashed border-accent"
tabindex="0"
>{
link ? (

View file

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

View file

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

View file

@ -1,39 +1,24 @@
{
import.meta.env.PROD ? (
<>
<script
---
const isProduction = import.meta.env.PROD;
const uuid = isProduction
? "b1e05d49-f072-4bae-9116-bdb78af15448"
: "b1e05d49-f072-4bae-9116-bdb78af15448-test";
---
<script
src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js"
type="text/javascript"
charset="UTF-8"
data-domain-script="b1e05d49-f072-4bae-9116-bdb78af15448"
is:inline
/>
<script type="text/javascript" is:inline>
data-domain-script={uuid}
is:inline></script>
<script type="text/javascript" is:inline>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function OptanonWrapper() {}
</script>
</>
) : (
<>
<script
src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js"
type="text/javascript"
charset="UTF-8"
data-domain-script="b1e05d49-f072-4bae-9116-bdb78af15448-test"
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>
</script>
<!-- OneTrust Cookies Settings button start -->
<button id="ot-sdk-btn" class="ot-sdk-show-settings">Cookie Settings</button>
<!-- OneTrust Cookies Settings button end -->
<style>
#ot-sdk-btn.ot-sdk-show-settings {
@ -43,6 +28,7 @@
line-height: inherit !important;
padding: inherit !important;
font-family: var(--sl-font-family) !important;
background-color: inherit !important;
}
#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 presets = entry.data.build_configs;
const entries = Object.entries(presets);
const entries = Object.values(presets);
---
<table>
@ -14,7 +14,7 @@ const entries = Object.entries(presets);
</thead>
<tbody>
{
entries.map(([_, value]) => (
entries.map((value) => (
<tr>
<td>{value.display_name}</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);
let availability;
if (type) {
// @ts-ignore
// @ts-expect-error plans are not typed
availability = mappings[type];
} else {
// @ts-ignore
availability = await indexPlans(id);
}
---

View file

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

View file

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

View file

@ -22,7 +22,7 @@ const { header, href, product } = props.parse(Astro.props);
<strong>
<a
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
>
</strong>

View file

@ -1,6 +1,5 @@
---
import { z } from "astro:schema";
import { Icon } from "@astrojs/starlight/components";
type Props = z.infer<typeof props>;
@ -13,11 +12,42 @@ const props = z
const { id } = props.parse(Astro.props);
---
<code
title="Click to copy the full ID"
onclick=`navigator.clipboard.writeText("${id}")`
class="inline-flex w-fit hover:bg-accent-600/50 hover:cursor-pointer active:bg-accent-600"
>
<rule-id id={id}>
<button title="Click to copy the full ID" class="px-0">
<code class="flex">
{`...${id.slice(-8)}`}
<Icon name="document" class="!inline justify-center" />
</code>
</code>
</button>
</rule-id>
<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 { transform } from "detype";
import tsBlankSpace from "ts-blank-space";
import { format } from "prettier";
import { parse } from "node-html-parser";
import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
@ -58,11 +59,7 @@ if (!code) {
code = code.replace(/\u007f/g, "\n");
const js = await transform(code, "placeholder.ts", {
prettierOptions: {
useTabs: true,
},
});
const js = await format(tsBlankSpace(code), { parser: "babel", useTabs: true });
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
---

View file

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

View file

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

View file

@ -74,10 +74,7 @@ const blocks = [
size="1.5rem"
color="var(--sl-color-white)"
/>
<a
href={link.href}
class="pl-2 no-underline !text-black dark:!text-white"
>
<a href={link.href} class="pl-2 no-underline !text-black">
{link.text}
</a>
</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 }) => {
if (property_id === "lora" && value === "true") {
return {

View file

@ -1,6 +1,7 @@
import type { WorkersAIModelsSchema } from "~/schemas";
import { authorData } from "./data";
const ModelInfo = ({ model }) => {
const ModelInfo = ({ model }: { model: WorkersAIModelsSchema }) => {
const author =
authorData[model.name.split("/")[1]]?.name ?? model.name.split("/")[1];
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 google from "../../assets/images/workers-ai/google.svg";
export const authorData = {
export const authorData: Record<string, { name: string; logo: string }> = {
openai: {
name: "OpenAI",
logo: openai.src,

View file

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

View file

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

View file

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

View file

@ -103,7 +103,13 @@ export default {
## 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

View file

@ -217,7 +217,7 @@ const recommendedSection = {
<a
href={link.href}
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}
</a>

View file

@ -40,7 +40,7 @@ const products = lpProducts(learningPaths);
</Description>
<div class="flex">
<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>
</div>
<div id="areas-filter">

View file

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

View file

@ -1,7 +1,7 @@
---
import { getEntry } from "astro:content";
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 fields = fieldData.data.entries;

View file

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

View file

@ -5,8 +5,8 @@ import StarlightPage, {
type StarlightPageProps,
} from "@astrojs/starlight/components/StarlightPage.astro";
import { LinkButton, Tabs, TabItem, Code, Aside } from "~/components";
import ModelInfo from "~/components/models/ModelInfo.jsx";
import ModelBadges from "~/components/models/ModelBadges.jsx";
import ModelInfo from "~/components/models/ModelInfo.tsx";
import ModelBadges from "~/components/models/ModelBadges.tsx";
import SchemaViewer from "~/components/models/SchemaViewer.astro";
import TextGenerationCode from "~/components/models/code/TextGenerationCode.astro";
@ -123,7 +123,11 @@ const starlightPageProps = {
<div class="flex align-center">
{
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">
{model.name.split("/")[1].substring(0, 1)}

View file

@ -2,7 +2,7 @@
import { getCollection } from "astro:content";
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 models = modelData.map(({ data }) => data);

View file

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

View file

@ -1,6 +1,8 @@
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(),
source: z.number(),
name: z.string(),

View file

@ -1,8 +1,23 @@
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);
if (opts?.hideAfter) {
opts.onShow = (instance) => {
setTimeout(() => {
instance.hide();
}, opts.hideAfter);
};
}
tippy(id, {
content,
allowHTML: true,
@ -14,5 +29,6 @@ export function addTooltip(element: HTMLElement, content: string) {
// cutoff by the sidebar
// https://atomiks.github.io/tippyjs/v6/faq/#my-tooltip-appears-cut-off-or-is-not-showing-at-all
appendTo: document.body,
...opts,
});
}

View file

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