Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
df2b307
Implement Markdown reference renderer
seesharprun Dec 2, 2025
874dfb2
Add missing whitespace to articles
seesharprun Dec 3, 2025
937122c
Run `npm audit fix`
seesharprun Dec 4, 2025
287aed8
Refactor Index component to improve navigation structure and renderin…
seesharprun Dec 4, 2025
a7fe88f
Add scrolling and focus behavior to navbar
seesharprun Dec 4, 2025
e208f0b
Fix bug with page highlight
seesharprun Dec 4, 2025
cb44aee
Update sample Markdown files
seesharprun Dec 9, 2025
3c56fb9
Update YAML validation
seesharprun Dec 9, 2025
dbd4be8
Update schemas
seesharprun Dec 22, 2025
5188100
Remove content that will now be served from `documentdb/docs`
seesharprun Dec 22, 2025
02eb885
Update documentation landing page
seesharprun Dec 22, 2025
80e9b53
Update gitignore
seesharprun Dec 22, 2025
21e26cd
Update package versions with `npm audit`
seesharprun Dec 22, 2025
99ee615
Add content compilation logic script
seesharprun Dec 22, 2025
305110f
Update Markdown renderer for external files
seesharprun Dec 22, 2025
35cd9bc
Update `articleService` to transform hyperlink files
seesharprun Dec 22, 2025
5ed43c8
Update `referenceService` to serve descriptions from Markdown partial…
seesharprun Dec 22, 2025
cb15d2d
Update engine to allow Markdown for HTML partials
seesharprun Dec 22, 2025
a39eea6
Update reference landing page to use partial
seesharprun Dec 22, 2025
7858738
Update content configuration to use correct repository
seesharprun Dec 22, 2025
42bccdb
Update NPM serve configuration
seesharprun Dec 22, 2025
ff17986
Remove duplicate header
seesharprun Dec 22, 2025
a21aa73
Fix errant URL in `postgres-api`
seesharprun Dec 22, 2025
4c2a149
Simplify workflow to infer and use NPM
seesharprun Dec 22, 2025
74b9557
Update `tsx` package
seesharprun Dec 22, 2025
403a4a4
Update package versions to latest
seesharprun Dec 30, 2025
e3b93d8
Add site build validation
seesharprun Dec 30, 2025
55ad86e
Change title casing for sections in navigation bar
seesharprun Dec 30, 2025
2ecba8c
Update CI workflow title
seesharprun Dec 31, 2025
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
23 changes: 3 additions & 20 deletions .github/workflows/continuous-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,11 @@ jobs:
# Configure DocumentDB version (can be overridden by repository variables)
echo "DOCUMENTDB_VERSION=${{ vars.DOCUMENTDB_VERSION || 'latest' }}" >> $GITHUB_ENV
echo "MULTI_VERSION=${{ vars.MULTI_VERSION || 'true' }}" >> $GITHUB_ENV
- name: Detect package manager
id: detect-package-manager
run: |
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=yarn" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/package.json" ]; then
echo "manager=npm" >> $GITHUB_OUTPUT
echo "command=ci" >> $GITHUB_OUTPUT
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
exit 0
else
echo "Unable to determine package manager"
exit 1
fi
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 24
cache: ${{ steps.detect-package-manager.outputs.manager }}
cache: npm
- name: Restore cache
uses: actions/cache@v4
with:
Expand All @@ -83,11 +66,11 @@ jobs:
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
run: npm ci
- name: Build with Next.js
env:
NEXT_BASE_PATH: ${{ github.event.repository.name }}
run: ${{ steps.detect-package-manager.outputs.runner }} next build
run: npm run build
- name: Download DocumentDB packages from latest release
run: .github/scripts/download_packages.sh
- name: Upload artifact
Expand Down
25 changes: 24 additions & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
name: Validate structured content
# Workflow for validating content structure and build integrity
name: Validate site and content
on:
# Runs on pushes targeting the default branch
push:
branches:
- main
# Runs on pull requests targeting the default branch
pull_request:
branches:
- main
jobs:
# YAML schema validation job
yaml-schema-validation:
name: Validate YAML files against schemas
runs-on: ubuntu-latest
Expand All @@ -25,3 +29,22 @@ jobs:
npx yaml-ls-check .\articles
npx yaml-ls-check .\blogs
npx yaml-ls-check .\reference
# Build validation job
build-validation:
name: Validate Next.js build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
- name: Install dependencies
run: npm ci
- name: Build Next.js site
# Validate that the site builds successfully
#
# Use the built-in NPM script to build the site
run: npm run build
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# Sourced from https://github.com/github/gitignore/blob/main/Nextjs.gitignore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# Temporary content cloning directory
_tmp/

# Reference files
api-reference/
reference/

# Documentation articles
articles/*/
articles/**/*.md
articles/**/*.yml
!articles/content.yml
!articles/demo.yml

# dependencies
/node_modules
/.pnp
Expand Down
6 changes: 0 additions & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@
"./schema/reference.content.schema.json": [
"reference/content.{yml,yaml}"
],
"./schema/reference.data.schema.json": [
"reference/*/**/*.{yml,yaml}"
],
"./schema/articles.content.schema.json": [
"articles/**/content.{yml,yaml}"
],
"./schema/articles.navigation.schema.json": [
"articles/**/navigation.{yml,yaml}"
]
},
"editor.tabSize": 2,
Expand Down
2 changes: 1 addition & 1 deletion app/components/Breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function Breadcrumb({ type, category, name }: {
<>
<span className="mx-2">/</span>
<Link href={`/docs/reference/${type}`} className="hover:text-blue-400 transition-colors capitalize">
{capitalCase(pluralize(type))}
{capitalCase(type)}
</Link>
</>
)}
Expand Down
229 changes: 165 additions & 64 deletions app/components/Index.tsx
Original file line number Diff line number Diff line change
@@ -1,92 +1,193 @@
"use client";
'use client'

import Link from 'next/link';
import { usePathname } from 'next/navigation';
import type { ReferencePage } from '../services/referenceService';
import { capitalCase } from 'change-case';
import pluralize from 'pluralize';
import React, { useEffect, useRef } from 'react';

type ReferencePage = {
name: string;
description: string;
reference: string;
type: string;
category?: string;
};

type NavigationStructure = {
type: string;
displayName: string;
categories: {
category: string;
displayName: string;
items: ReferencePage[];
}[];
}[];

const icons = {
chevronRight: (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
),
};

export default function Index({
groupedReferences
}: {
groupedReferences: Record<string, Record<string, ReferencePage[]>>;
}) {
const pathname = usePathname();
const activeItemRef = useRef<HTMLAnchorElement>(null);
const containerRef = useRef<HTMLElement>(null);

// Auto-scroll to active item when pathname changes
useEffect(() => {
if (activeItemRef.current && containerRef.current) {
const container = containerRef.current;
const activeItem = activeItemRef.current;

// Calculate position to scroll within the container
const containerRect = container.getBoundingClientRect();
const itemRect = activeItem.getBoundingClientRect();
const scrollOffset = itemRect.top - containerRect.top - (containerRect.height / 2) + (itemRect.height / 2);

// Instant scroll without animation
container.scrollBy({
top: scrollOffset,
behavior: 'instant'
});
}
}, [pathname]);

// Get all types and sort them
const types = Object.keys(groupedReferences).sort();
// Convert old structure to new structure
const navigationStructure: NavigationStructure = Object.entries(groupedReferences)
.sort(([a], [b]) => a.localeCompare(b))
.map(([type, categories]) => ({
type,
displayName: type,
categories: Object.entries(categories)
.sort(([a], [b]) => a.localeCompare(b))
.map(([category, items]) => ({
category,
displayName: category,
items: items.sort((a, b) => a.name.localeCompare(b.name))
}))
}));

// Parse pathname to determine current location
const pathParts = pathname.split('/').filter(Boolean);
const currentType = pathParts[2]; // /docs/reference/[type]
const currentCategory = pathParts[3]; // /docs/reference/[type]/[category]
const currentName = pathParts[4] ? decodeURIComponent(pathParts[4]) : undefined; // /docs/reference/[type]/[category]/[name]

return (
<section className="flex-1 p-4 overflow-y-auto">
<section ref={containerRef} className="flex-1 overflow-y-auto p-4 min-h-0">
<nav className="space-y-1">
{types.map((type, index) => {
const categories = Object.keys(groupedReferences[type]).sort();
{navigationStructure.map((typeSection, typeIndex) => {
// Types are always expanded
const isTypeExpanded = true;
const isTypeActive = pathname === `/docs/reference/${typeSection.type}`;

return (
<div key={type}>
{/* Add horizontal rule between sections (not before the first one) */}
{index > 0 && (
<div key={typeSection.type}>
{/* Separator between type sections (not before first) */}
{typeIndex > 0 && (
<div className="my-4 border-t border-neutral-700/50"></div>
)}

{/* Type header */}
{categories.length > 0 && (
<>
<Option
key={type}
target={`/docs/reference/${type}`}
display={capitalCase(type) + 's'}
className="uppercase"
currentPath={pathname}
/>
{/* Type row */}
<div className="flex items-center gap-1">
<div className="p-1 flex-shrink-0">
<svg
className="w-4 h-4 text-gray-500 rotate-90"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{icons.chevronRight}
</svg>
</div>
<Link
href={`/docs/reference/${typeSection.type}`}
ref={isTypeActive ? activeItemRef : null}
className={`flex-1 px-3 py-3 rounded-lg text-sm font-semibold uppercase transition-all duration-200 ${
isTypeActive
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
: "text-gray-300 hover:text-white hover:bg-neutral-700/50"
}`}
>
{pluralize(capitalCase(typeSection.displayName))}
</Link>
</div>

{/* Category links */}
{categories.map((category) => (
<Option
key={`${type}-${category}`}
target={`/docs/reference/${type}/${category}`}
display={capitalCase(category)}
currentPath={pathname}
/>
))}
</>
)}
{/* Categories */}
{isTypeExpanded && typeSection.categories.map((categorySection) => {
// Category is expanded if we're on that category page or on one of its item pages
const isCategoryExpanded = currentType === typeSection.type && currentCategory === categorySection.category;
const isCategoryActive = pathname === `/docs/reference/${typeSection.type}/${categorySection.category}`;

return (
<div key={`${typeSection.type}-${categorySection.category}`}>
{/* Category row */}
<div className="flex items-center gap-1 pl-4">
<div className="p-1 flex-shrink-0">
<svg
className={`w-3 h-3 text-gray-500 transition-transform ${isCategoryExpanded ? 'rotate-90' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{icons.chevronRight}
</svg>
</div>
<Link
href={`/docs/reference/${typeSection.type}/${categorySection.category}`}
ref={isCategoryActive ? activeItemRef : null}
className={`flex-1 px-3 py-3 rounded-lg text-sm transition-all duration-200 ${
isCategoryActive
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
: "text-gray-300 hover:text-white hover:bg-neutral-700/50"
}`}
>
{capitalCase(categorySection.displayName)}
</Link>
</div>

{/* Individual reference items */}
{isCategoryExpanded && categorySection.items.map((item) => {
const itemFilename = item.reference.split('/').pop();
const itemPath = `/docs/reference/${typeSection.type}/${categorySection.category}/${itemFilename}`;
// Decode pathname to match against the constructed path
const decodedPathname = decodeURIComponent(pathname);
const isItemActive = decodedPathname === itemPath || pathname === itemPath;

return (
<div key={item.reference} className="flex items-center pl-10 ml-4">
<Link
href={itemPath}
ref={isItemActive ? activeItemRef : null}
className={`flex-1 px-3 py-3 rounded-lg text-sm transition-all duration-200 ${
isItemActive
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
: "text-gray-400 hover:text-white hover:bg-neutral-700/50"
}`}
>
{item.name}
</Link>
</div>
);
})}
</div>
);
})}
</div>
);
})}
</nav>
</section>
);
}

function Option({
target,
display,
className,
currentPath
}: {
target: string;
display: string;
className?: string;
currentPath: string;
}) {
// Determine if this option should be highlighted
// Highlight if:
// 1. Exact match (e.g., on /docs/reference/operator, highlight "Operators")
// 2. Category match (e.g., on /docs/reference/operator/accumulator or /docs/reference/operator/accumulator/avg, highlight "Operators" and "Accumulator")
const isExactMatch = currentPath === target;
const isCategoryMatch = currentPath.startsWith(target + '/');
const isActive = isExactMatch || isCategoryMatch;

return (
<Link
href={target}
className={`block w-full text-left px-4 py-3 rounded-lg text-sm transition-all duration-200 ${
isActive
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
: "text-gray-300 hover:text-white hover:bg-neutral-700/50"
}${className ? ' ' + className : ''}`}
>
{display}
</Link>
);
}
Loading