Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion site/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ not comprehensive. just the things I'm worried about forgetting
- animations
- docs features
- /docs/[slug] redirect ("share this guide")
- search
- update READMEs following dev
2 changes: 2 additions & 0 deletions site/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import vercel from '@astrojs/vercel';

import tailwindcss from '@tailwindcss/vite';
import { defineConfig, fontProviders } from 'astro/config';
import pagefind from './integrations/pagefind';
import rehypePrepareCodeBlocks from './src/utils/rehypePrepareCodeBlocks';
import remarkConditionalHeadings from './src/utils/remarkConditionalHeadings';
import { remarkReadingTime } from './src/utils/remarkReadingTime.mjs';
Expand All @@ -21,6 +22,7 @@ export default defineConfig({
integrations: [
mdx({ extendMarkdownConfig: true }),
sitemap(),
pagefind(),
react({
babel: {
plugins: [['babel-plugin-react-compiler', { target: '18' }]],
Expand Down
99 changes: 99 additions & 0 deletions site/integrations/pagefind.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type { AstroIntegration } from 'astro';
import { spawn } from 'node:child_process';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import sirv from 'sirv';

export interface PagefindOptions {
/**
* The path to the built site to index.
* Defaults to the Astro output directory.
*/
site?: string;
}

export default function pagefind(options: PagefindOptions = {}): AstroIntegration {
return {
name: 'pagefind',
hooks: {
'astro:server:setup': ({ server, logger }) => {
// Serve Pagefind index from previous build during development
const rootDir = server.config.root;
const indexDir = join(rootDir, 'dist', 'client');
const pagefindDir = join(indexDir, 'pagefind');

// Warn if index doesn't exist yet
if (!existsSync(pagefindDir)) {
logger.warn(
'Pagefind index not found. Run `pnpm build` first to generate '
+ 'the search index for development mode.',
);
} else {
logger.debug(`Serving Pagefind index from ${indexDir}`);
}

// Create sirv middleware to serve static files
// approach adapted from https://github.com/shishkin/astro-pagefind
const serve = sirv(indexDir, {
dev: true, // No caching in dev mode
etag: true, // Enable cache validation
});

// Mount middleware for /pagefind/* routes only
server.middlewares.use((req, res, next) => {
if (req.url?.startsWith('/pagefind/')) {
serve(req, res, next);
} else {
next();
}
});
},

'astro:build:done': async ({ dir, logger }) => {
// Determine the site directory to index
// The dir parameter already points to the correct static output directory
const siteDir = options.site || fileURLToPath(dir);

// Map Astro logger levels to Pagefind CLI flags
const logLevel = logger.options.level;
const logFlags: string[] = [];

if (logLevel === 'silent' || logLevel === 'error') {
logFlags.push('--silent');
} else if (logLevel === 'warn') {
logFlags.push('--quiet');
} else if (logLevel === 'debug') {
logFlags.push('--verbose');
}
// 'info' level uses no flag (default)

logger.info('Running Pagefind indexer...');

return new Promise<void>((resolve, reject) => {
const pagefindProcess = spawn(
'npx',
['-y', 'pagefind', ...logFlags, '--site', siteDir],
{
stdio: 'inherit',
shell: true,
},
);

pagefindProcess.on('close', (code) => {
if (code === 0) {
logger.info('Pagefind indexing complete');
resolve();
} else {
reject(new Error(`Pagefind process exited with code ${code}`));
}
});

pagefindProcess.on('error', (error) => {
reject(new Error(`Failed to start Pagefind: ${error.message}`));
});
});
},
},
};
}
2 changes: 2 additions & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@astrojs/vercel": "^8.2.9",
"@base-ui-components/react": "1.0.0-beta.4",
"@nanostores/react": "^1.0.0",
"@pagefind/default-ui": "^1.4.0",
"@tailwindcss/vite": "^4.1.14",
"@videojs/html": "workspace:*",
"@videojs/react": "workspace:*",
Expand Down Expand Up @@ -53,6 +54,7 @@
"@vitest/ui": "^3.2.4",
"babel-plugin-react-compiler": "1.0.0",
"jsdom": "^27.0.0",
"sirv": "^3.0.2",
"vitest": "^3.2.4"
}
}
8 changes: 5 additions & 3 deletions site/src/components/FormattedDate.astro
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
---
interface Props {
import type { HTMLAttributes } from 'astro/types';

interface Props extends Omit<HTMLAttributes<'time'>, 'datetime'> {
date: Date;
}

const { date } = Astro.props;
const { date, ...rest } = Astro.props;
---

<time datetime={date.toISOString()}>
<time {...rest} datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
Expand Down
3 changes: 3 additions & 0 deletions site/src/components/NavBar/NavBar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ArrowUpRight } from 'lucide-react';
import Discord from '@/components/icons/Discord';
import GitHub from '@/components/icons/GitHub';
import FilmGrain from '../FilmGrain';
import Search from '@/components/Search';

export interface Props {
dark?: boolean;
Expand Down Expand Up @@ -41,6 +42,8 @@ const { class: className, dark = false } = Astro.props;
{/* Logo */}
<a href="/" class="flex items-center px-3 text-h5">Video.js v10</a>

<Search dark={dark} class="ml-auto mr-4 sm:self-center" />

{/* Desktop nav links */}
<div class="hidden sm:flex -mb-px pr-3">
{
Expand Down
111 changes: 111 additions & 0 deletions site/src/components/Search/Search.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
---
/**
* We wrap Search.tsx in an .astro entry point for
* 1. easy CSS support, since we can't use Tailwind on pagefind-ui
* 2. a synchronous script to set the meta key (ctrl or cmd) without FOUC or hydration errors
*/
import SearchClient from './Search';
import { join } from 'node:path/posix';
import SearchIcon from './searchIcon.svg';
import searchIconString from './searchIcon.svg?raw';
import clsx from 'clsx';

const baseUrl = import.meta.env.BASE_URL || '/';
const bundlePath = join(baseUrl, 'pagefind/');

interface Props {
class?: string;
dark: boolean;
}
const { class: className, dark } = Astro.props;
---

<SearchClient
client:load
className={clsx(
'inline-flex items-center gap-2 cursor-pointer min-h-6 px-2 sm:border intent:border-current rounded-full',
dark ? 'border-dark-80' : 'border-light-40 dark:border-dark-80',
className,
)}
baseUrl={baseUrl}
bundlePath={bundlePath}
searchId="pagefind-ui"
searchStyle={{
'--search-icon-url': `url('data:image/svg+xml;utf8,${encodeURIComponent(searchIconString)}')`,
} as React.CSSProperties}
>
<SearchIcon class="w-4 h-4 sm:w-3 sm:h-3" />
<kbd data-platform-key="default" class="font-sans text-sm hidden sm:inline">Ctrl K</kbd>
Comment thread
decepulis marked this conversation as resolved.
<kbd data-platform-key="mac" class="font-sans text-sm hidden sm:inline">⌘K</kbd>
</SearchClient>

{
/**
* Adapted from https://github.com/withastro/starlight/blob/8a72a19e2cfec235941b4e1401b69b44e6695068/packages/starlight/components/Search.astro#L55C1-L75C1
* This is intentionally inlined to avoid briefly showing an invalid shortcut.
* Purposely using the deprecated `navigator.platform` property to detect Apple devices, as the
* user agent is spoofed by some browsers when opening the devtools.
*/
}
<script is:inline>
(() => {
const defaultPlatformKey = document.querySelector('kbd[data-platform-key="default"]');
const macPlatformKey = document.querySelector('kbd[data-platform-key="mac"]');
if (/Mac|iPhone|iPod|iPad/i.test(navigator.platform)) {
if (defaultPlatformKey) defaultPlatformKey.style.display = 'none';
} else {
if (macPlatformKey) macPlatformKey.style.display = 'none';
}
})();
</script>

<style is:global>
@import url('@pagefind/default-ui/css/ui.css');

#pagefind-ui {
--pagefind-ui-scale: 1;
--pagefind-ui-primary: var(--color-yellow);
--pagefind-ui-text: var(--color-dark-100);
--pagefind-ui-background: var(--color-light-100);
--pagefind-ui-border: var(--color-light-40);
--pagefind-ui-tag: green;
--pagefind-ui-border-width: 1px;
--pagefind-ui-border-radius: var(--radius-lg);
--pagefind-ui-font: inherit;
}

.dark #pagefind-ui {
--pagefind-ui-text: var(--color-light-80);
--pagefind-ui-background: var(--color-dark-100);
--pagefind-ui-border: var(--color-dark-80);
}

#pagefind-ui .pagefind-ui__form::before {
-webkit-mask-image: var(--search-icon-url);
mask-image: var(--search-icon-url);
}
#pagefind-ui .pagefind-ui__search-input {
font-size: var(--text-lg);
line-height: var(--text-lg--line-height);
letter-spacing: var(--text-lg--letter-spacing);
font-weight: var(--text-lg--font-weight);
}
#pagefind-ui .pagefind-ui__filter-block {
border-bottom: solid 1px var(--pagefind-ui-border);
}
#pagefind-ui .pagefind-ui__message {
font-weight: var(--font-weight-normal);
}
#pagefind-ui .pagefind-ui__result-title,
#pagefind-ui .pagefind-ui__filter-name {
font-weight: var(--font-weight-semibold);
}
#pagefind-ui .pagefind-ui__result-nested .pagefind-ui__result-link::before {
font-weight: var(--font-weight-normal);
font-size: 0.9em;
}
#pagefind-ui mark {
background-color: var(--color-yellow);
color: var(--color-dark-100);
}
</style>
Loading