Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1402ffe
docs: setup mkdocs documentation with configuration and build scripts
YosefAshenafi Nov 14, 2025
d85f9f9
docs: implement scalable dynamic mkdocs setup with hooks for auto-dis…
YosefAshenafi Nov 14, 2025
d05ab2d
docs: remove tracked symlinks from docs folder and improve gitignore
YosefAshenafi Nov 14, 2025
ed44c58
docs: move extra.css to root and remove docs folder from git tracking
YosefAshenafi Nov 14, 2025
7b2e243
docs: update header color to #0000e6 and clean generated files
YosefAshenafi Nov 14, 2025
36607ad
ci: update GitHub Actions workflow and build script for hook-based do…
YosefAshenafi Nov 14, 2025
509bb62
remove logo
YosefAshenafi Nov 14, 2025
1d12f71
remove logo
YosefAshenafi Nov 14, 2025
bf59165
fix: restore README.md to original state with working links
YosefAshenafi Nov 14, 2025
f0dc62b
revert readme change
YosefAshenafi Nov 14, 2025
29a9fee
remove: clean up unnecessary documentation scripts and files
YosefAshenafi Nov 14, 2025
646bd54
restore: add back extra.css for mkdocs styling
YosefAshenafi Nov 14, 2025
f272d4e
fix: update workflow to use mkdocs build directly
YosefAshenafi Nov 14, 2025
b39c255
fix: update mkdocs configuration and enhance symlink generation
YosefAshenafi Nov 14, 2025
c925966
fix: implement dynamic mkdocs navigation generation from filesystem
YosefAshenafi Nov 14, 2025
c5a027b
fix: move CSS to docs/static/css and fix mkdocs asset path resolution
YosefAshenafi Nov 14, 2025
6f02edd
fix: remove offline plugin and hide skip links below footer
YosefAshenafi Nov 14, 2025
8aa8fd2
fix: remove data module from documentation navigation
YosefAshenafi Nov 14, 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
212 changes: 212 additions & 0 deletions .claude/mkdocs_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
"""
MkDocs hooks for dynamic README.md discovery and navigation generation.
This hook discovers all README.md files in the htk directory structure,
creates symbolic links, and dynamically generates the navigation config.
"""

from pathlib import Path


def get_title_from_readme(path: Path) -> str:
"""Extract the first heading from a README file to use as title."""
try:
with open(path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line.startswith('# '):
return line[2:].strip()
except Exception:
pass
return None


def format_name(dirname: str) -> str:
"""Convert directory name to readable format."""
# Convert snake_case to Title Case
words = dirname.replace('_', ' ').split()
return ' '.join(word.capitalize() for word in words)


def generate_nav_config(htk_base: Path) -> list:
"""
Generate complete mkdocs navigation structure from filesystem.
Scans for README.md files and builds nested navigation dynamically.
"""
nav_config = []

# Home
nav_config.append({'Home': 'index.md'})

# Top-level modules (direct children of htk/)
top_level_modules = [
'admin', 'admintools', 'api', 'cache', 'constants',
'decorators', 'extensions', 'forms', 'middleware', 'models',
'scripts', 'test_scaffold', 'templatetags', 'utils', 'validators'
]

for module in top_level_modules:
module_path = htk_base / module
if (module_path / 'README.md').exists():
title = get_title_from_readme(module_path / 'README.md')
if not title:
title = format_name(module)
nav_config.append({title: f'{module}.md'})

# Django Apps with submenu
apps_path = htk_base / 'apps'
if apps_path.exists():
apps_nav = [{'Overview': 'apps.md'}]
apps = sorted([
d for d in apps_path.iterdir()
if d.is_dir() and not d.name.startswith(('_', '__'))
and (d / 'README.md').exists()
])

for app_dir in apps:
title = get_title_from_readme(app_dir / 'README.md')
if not title:
title = format_name(app_dir.name)
apps_nav.append({title: f'apps/{app_dir.name}.md'})

nav_config.append({'Django Apps': apps_nav})

# Libraries with submenu
lib_path = htk_base / 'lib'
if lib_path.exists():
libs_nav = [{'Overview': 'lib.md'}]
libs = sorted([
d for d in lib_path.iterdir()
if d.is_dir() and not d.name.startswith(('_', '__'))
and (d / 'README.md').exists()
])

for lib_dir in libs:
title = get_title_from_readme(lib_dir / 'README.md')
if not title:
title = format_name(lib_dir.name)
libs_nav.append({title: f'lib/{lib_dir.name}.md'})

nav_config.append({'Libraries': libs_nav})

return nav_config


def create_symlinks(docs_dir: Path, htk_base: Path):
"""
Create necessary symbolic links from docs directory to actual README.md files.
This allows mkdocs to find all READMEs dynamically.
"""
# Ensure docs/apps and docs/lib directories exist
(docs_dir / 'apps').mkdir(exist_ok=True)
(docs_dir / 'lib').mkdir(exist_ok=True)

# Map of top-level directory names to documentation file names
# This creates symlinks like: admin.md -> ../admin/README.md
top_level_dirs = [
'admin', 'admintools', 'api', 'cache', 'constants', 'decorators',
'extensions', 'forms', 'middleware', 'models', 'utils', 'validators',
'test_scaffold', 'scripts', 'templatetags'
]

# Create symlinks for top-level modules
for module_name in top_level_dirs:
module_dir = htk_base / module_name
if module_dir.exists() and module_dir.is_dir():
readme = module_dir / 'README.md'
if readme.exists():
symlink = docs_dir / f'{module_name}.md'
try:
if symlink.exists() or symlink.is_symlink():
symlink.unlink()
symlink.symlink_to(readme)
except Exception:
pass

# Create symlink for index.md from main README.md
main_readme = htk_base / 'README.md'
if main_readme.exists():
index_symlink = docs_dir / 'index.md'
try:
if index_symlink.exists() or index_symlink.is_symlink():
index_symlink.unlink()
index_symlink.symlink_to(main_readme)
except Exception:
pass

# Create symlinks for apps.md and lib.md overview files
apps_readme = htk_base / 'apps' / 'README.md'
if apps_readme.exists():
apps_symlink = docs_dir / 'apps.md'
try:
if apps_symlink.exists() or apps_symlink.is_symlink():
apps_symlink.unlink()
apps_symlink.symlink_to(apps_readme)
except Exception:
pass

lib_readme = htk_base / 'lib' / 'README.md'
if lib_readme.exists():
lib_symlink = docs_dir / 'lib.md'
try:
if lib_symlink.exists() or lib_symlink.is_symlink():
lib_symlink.unlink()
lib_symlink.symlink_to(lib_readme)
except Exception:
pass

# Clean up old directory-level README.md symlinks that shouldn't exist
# (We use top-level .md symlinks instead)
old_dirs = ['api', 'cache', 'decorators', 'forms', 'middleware', 'models', 'utils', 'validators']
for dir_name in old_dirs:
old_readme = docs_dir / dir_name / 'README.md'
if old_readme.exists() or old_readme.is_symlink():
try:
old_readme.unlink()
except Exception:
pass

# Create symlinks for apps
apps_dir = htk_base / 'apps'
if apps_dir.exists():
# Create symlinks for individual apps
for app_path in sorted(apps_dir.iterdir()):
if app_path.is_dir() and not app_path.name.startswith(('_', '__', 'README')):
readme = app_path / 'README.md'
if readme.exists():
symlink = docs_dir / 'apps' / f'{app_path.name}.md'
try:
if symlink.exists() or symlink.is_symlink():
symlink.unlink()
symlink.symlink_to(readme)
except Exception:
pass

# Create symlinks for libraries
lib_dir = htk_base / 'lib'
if lib_dir.exists():
for lib_path in sorted(lib_dir.iterdir()):
if lib_path.is_dir() and not lib_path.name.startswith(('_', '__', 'README')):
readme = lib_path / 'README.md'
if readme.exists():
symlink = docs_dir / 'lib' / f'{lib_path.name}.md'
try:
if symlink.exists() or symlink.is_symlink():
symlink.unlink()
symlink.symlink_to(readme)
except Exception:
pass


def on_pre_build(config, **kwargs):
"""
Pre-build hook to create symlinks and dynamically generate navigation.
This ensures mkdocs has access to all README.md files and the nav is always in sync.
"""
docs_dir = Path(config['docs_dir'])
htk_base = docs_dir.parent

# Create all necessary symlinks
create_symlinks(docs_dir, htk_base)

# Generate and set dynamic navigation config
config['nav'] = generate_nav_config(htk_base)
39 changes: 39 additions & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Build and Deploy Documentation

on:
push:
branches:
- master
- main
paths:
- 'README.md'
- '*/README.md'
- 'mkdocs.yml'
- 'docs/**'
- '.github/workflows/deploy-docs.yml'

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Install dependencies
run: |
pip install mkdocs mkdocs-material mkdocs-include-markdown-plugin

- name: Build documentation
run: |
mkdocs build

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
*.pyc

# MkDocs generated files
# The docs/ folder is generated at build time by the hook
docs/
# The site/ folder is the HTML build output
site/
66 changes: 66 additions & 0 deletions docs/static/css/mkdocs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* Custom color scheme */
:root {
--md-primary-fg-color: #0000e6;
--md-primary-fg-color-light: #1a1aff;
--md-primary-fg-color-dark: #0000b3;
}

/* Disable bounce/rubber band effect on entire page while allowing scroll */
html,
body {
overscroll-behavior: none;
}

/* Disable pull-to-refresh and overscroll bounce on mobile */
* {
overscroll-behavior: none;
}

/* Disable footer bounce/animation on scroll */
.md-footer {
position: relative !important;
animation: none !important;
}

/* Ensure footer stays fixed and doesn't bounce */
.md-footer__inner {
animation: none !important;
}

/* Remove any transition animations on footer */
.md-footer,
.md-footer__inner {
transition: none !important;
}

/* Disable header bounce */
.md-header {
animation: none !important;
transition: none !important;
}

/* Hide offline plugin messages and skip links that appear below footer */
.md-skip,
.md-announce,
[data-md-component="announce"] {
display: none !important;
}

/* Ensure page doesn't have overflow issues */
html {
overflow-x: hidden;
}

/* Ensure footer is the last visible element */
body {
display: flex;
flex-direction: column;
min-height: 100vh;
overflow-x: hidden;
}

.md-container {
display: flex;
flex-direction: column;
flex-grow: 1;
}
Loading
Loading