Mkdocs search fixers
This commit is contained in:
@@ -7,10 +7,10 @@
|
||||
"stars_count": 0,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 23,
|
||||
"updated_at": "2026-03-08T18:11:30-06:00",
|
||||
"updated_at": "2026-03-09T12:23:17-06:00",
|
||||
"created_at": "2025-05-28T14:54:59-06:00",
|
||||
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
|
||||
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-03-08T18:11:30-06:00"
|
||||
"last_build_update": "2026-03-09T12:23:17-06:00"
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.",
|
||||
"html_url": "https://github.com/anthropics/claude-code",
|
||||
"language": "Shell",
|
||||
"stars_count": 75344,
|
||||
"forks_count": 6074,
|
||||
"open_issues_count": 5793,
|
||||
"updated_at": "2026-03-09T00:15:16Z",
|
||||
"stars_count": 75798,
|
||||
"forks_count": 6114,
|
||||
"open_issues_count": 5868,
|
||||
"updated_at": "2026-03-09T21:59:41Z",
|
||||
"created_at": "2025-02-22T17:41:21Z",
|
||||
"clone_url": "https://github.com/anthropics/claude-code.git",
|
||||
"ssh_url": "git@github.com:anthropics/claude-code.git",
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "VS Code in the browser",
|
||||
"html_url": "https://github.com/coder/code-server",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 76540,
|
||||
"forks_count": 6539,
|
||||
"stars_count": 76554,
|
||||
"forks_count": 6541,
|
||||
"open_issues_count": 169,
|
||||
"updated_at": "2026-03-08T21:32:19Z",
|
||||
"updated_at": "2026-03-09T20:21:15Z",
|
||||
"created_at": "2019-02-27T16:50:41Z",
|
||||
"clone_url": "https://github.com/coder/code-server.git",
|
||||
"ssh_url": "git@github.com:coder/code-server.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-03-06T12:59:10Z"
|
||||
"last_build_update": "2026-03-09T19:30:51Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.",
|
||||
"html_url": "https://github.com/gethomepage/homepage",
|
||||
"language": "JavaScript",
|
||||
"stars_count": 28793,
|
||||
"forks_count": 1811,
|
||||
"stars_count": 28818,
|
||||
"forks_count": 1812,
|
||||
"open_issues_count": 1,
|
||||
"updated_at": "2026-03-08T23:44:14Z",
|
||||
"updated_at": "2026-03-09T21:38:58Z",
|
||||
"created_at": "2022-08-24T07:29:42Z",
|
||||
"clone_url": "https://github.com/gethomepage/homepage.git",
|
||||
"ssh_url": "git@github.com:gethomepage/homepage.git",
|
||||
"default_branch": "dev",
|
||||
"last_build_update": "2026-03-08T12:16:51Z"
|
||||
"last_build_update": "2026-03-09T17:02:06Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD",
|
||||
"html_url": "https://github.com/go-gitea/gitea",
|
||||
"language": "Go",
|
||||
"stars_count": 54180,
|
||||
"forks_count": 6438,
|
||||
"open_issues_count": 2846,
|
||||
"updated_at": "2026-03-08T23:25:42Z",
|
||||
"stars_count": 54194,
|
||||
"forks_count": 6448,
|
||||
"open_issues_count": 2850,
|
||||
"updated_at": "2026-03-09T21:55:30Z",
|
||||
"created_at": "2016-11-01T02:13:26Z",
|
||||
"clone_url": "https://github.com/go-gitea/gitea.git",
|
||||
"ssh_url": "git@github.com:go-gitea/gitea.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-03-08T20:49:59Z"
|
||||
"last_build_update": "2026-03-09T10:12:24Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.",
|
||||
"html_url": "https://github.com/knadh/listmonk",
|
||||
"language": "Go",
|
||||
"stars_count": 19221,
|
||||
"stars_count": 19236,
|
||||
"forks_count": 1945,
|
||||
"open_issues_count": 99,
|
||||
"updated_at": "2026-03-08T23:08:55Z",
|
||||
"updated_at": "2026-03-09T21:14:22Z",
|
||||
"created_at": "2019-06-26T05:08:39Z",
|
||||
"clone_url": "https://github.com/knadh/listmonk.git",
|
||||
"ssh_url": "git@github.com:knadh/listmonk.git",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2026-03-08T14:04:45Z"
|
||||
"last_build_update": "2026-03-09T03:05:44Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Create & scan cute qr codes easily \ud83d\udc7e",
|
||||
"html_url": "https://github.com/lyqht/mini-qr",
|
||||
"language": "Vue",
|
||||
"stars_count": 1898,
|
||||
"forks_count": 240,
|
||||
"open_issues_count": 21,
|
||||
"updated_at": "2026-03-08T15:02:09Z",
|
||||
"stars_count": 1897,
|
||||
"forks_count": 241,
|
||||
"open_issues_count": 22,
|
||||
"updated_at": "2026-03-09T15:15:51Z",
|
||||
"created_at": "2023-04-21T14:20:14Z",
|
||||
"clone_url": "https://github.com/lyqht/mini-qr.git",
|
||||
"ssh_url": "git@github.com:lyqht/mini-qr.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-03-05T13:18:42Z"
|
||||
"last_build_update": "2026-03-09T07:31:47Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.",
|
||||
"html_url": "https://github.com/n8n-io/n8n",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 178152,
|
||||
"forks_count": 55558,
|
||||
"open_issues_count": 1405,
|
||||
"updated_at": "2026-03-09T00:14:18Z",
|
||||
"stars_count": 178343,
|
||||
"forks_count": 55592,
|
||||
"open_issues_count": 1414,
|
||||
"updated_at": "2026-03-09T21:52:13Z",
|
||||
"created_at": "2019-06-22T09:24:21Z",
|
||||
"clone_url": "https://github.com/n8n-io/n8n.git",
|
||||
"ssh_url": "git@github.com:n8n-io/n8n.git",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2026-03-09T00:10:51Z"
|
||||
"last_build_update": "2026-03-09T21:33:49Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 A Free & Self-hostable Airtable Alternative",
|
||||
"html_url": "https://github.com/nocodb/nocodb",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 62384,
|
||||
"forks_count": 4655,
|
||||
"open_issues_count": 627,
|
||||
"updated_at": "2026-03-08T23:36:57Z",
|
||||
"stars_count": 62431,
|
||||
"forks_count": 4659,
|
||||
"open_issues_count": 629,
|
||||
"updated_at": "2026-03-09T19:42:51Z",
|
||||
"created_at": "2017-10-29T18:51:48Z",
|
||||
"clone_url": "https://github.com/nocodb/nocodb.git",
|
||||
"ssh_url": "git@github.com:nocodb/nocodb.git",
|
||||
"default_branch": "develop",
|
||||
"last_build_update": "2026-03-07T10:48:26Z"
|
||||
"last_build_update": "2026-03-09T12:25:14Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Get up and running with Kimi-K2.5, GLM-5, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.",
|
||||
"html_url": "https://github.com/ollama/ollama",
|
||||
"language": "Go",
|
||||
"stars_count": 164479,
|
||||
"forks_count": 14834,
|
||||
"open_issues_count": 2613,
|
||||
"updated_at": "2026-03-09T00:13:40Z",
|
||||
"stars_count": 164642,
|
||||
"forks_count": 14860,
|
||||
"open_issues_count": 2609,
|
||||
"updated_at": "2026-03-09T21:37:49Z",
|
||||
"created_at": "2023-06-26T19:39:32Z",
|
||||
"clone_url": "https://github.com/ollama/ollama.git",
|
||||
"ssh_url": "git@github.com:ollama/ollama.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-03-08T06:32:28Z"
|
||||
"last_build_update": "2026-03-09T21:19:34Z"
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Documentation that simply works",
|
||||
"html_url": "https://github.com/squidfunk/mkdocs-material",
|
||||
"language": "Python",
|
||||
"stars_count": 26208,
|
||||
"forks_count": 4048,
|
||||
"stars_count": 26215,
|
||||
"forks_count": 4051,
|
||||
"open_issues_count": 2,
|
||||
"updated_at": "2026-03-08T17:43:54Z",
|
||||
"updated_at": "2026-03-09T19:46:16Z",
|
||||
"created_at": "2016-01-28T22:09:23Z",
|
||||
"clone_url": "https://github.com/squidfunk/mkdocs-material.git",
|
||||
"ssh_url": "git@github.com:squidfunk/mkdocs-material.git",
|
||||
|
||||
BIN
mkdocs/docs/hooks/__pycache__/wikilinks_hook.cpython-311.pyc
Normal file
BIN
mkdocs/docs/hooks/__pycache__/wikilinks_hook.cpython-311.pyc
Normal file
Binary file not shown.
BIN
mkdocs/docs/hooks/__pycache__/wikilinks_hook.cpython-313.pyc
Normal file
BIN
mkdocs/docs/hooks/__pycache__/wikilinks_hook.cpython-313.pyc
Normal file
Binary file not shown.
199
mkdocs/docs/hooks/wikilinks_hook.py
Normal file
199
mkdocs/docs/hooks/wikilinks_hook.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
MkDocs Hook for Wiki-Link Resolution
|
||||
|
||||
Converts Obsidian-style [[wiki-links]] to standard Markdown links/images.
|
||||
|
||||
Supported syntax:
|
||||
[[page-name]] → [page-name](../page-name/)
|
||||
[[page-name|Display Text]] → [Display Text](../page-name/)
|
||||
[[page-name#heading]] → [page-name](../page-name/#heading)
|
||||
![[image.png]] → 
|
||||
![[image.png|alt text]] → 
|
||||
|
||||
Links inside fenced code blocks and inline code are skipped.
|
||||
Unresolved links are left as-is with a warning logged.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Module-level file index: filename (lowercase) → relative path from docs root
|
||||
_file_index: Dict[str, str] = {}
|
||||
|
||||
# Whether directory URLs are enabled
|
||||
_use_directory_urls: bool = True
|
||||
|
||||
# Image extensions
|
||||
_IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp', '.tiff', '.avif'}
|
||||
|
||||
|
||||
def on_files(files: Any, config: Dict[str, Any]) -> None:
|
||||
"""Build a file index mapping lowercased filenames to their paths."""
|
||||
global _file_index, _use_directory_urls
|
||||
_file_index.clear()
|
||||
_use_directory_urls = config.get('use_directory_urls', True)
|
||||
|
||||
for f in files:
|
||||
# f.src_path is relative to docs_dir, e.g. "docs/getting-started/installation.md"
|
||||
src = f.src_path
|
||||
basename = os.path.basename(src)
|
||||
# For .md files, also index without extension
|
||||
name_lower = basename.lower()
|
||||
_file_index[name_lower] = src
|
||||
|
||||
if name_lower.endswith('.md'):
|
||||
stem = name_lower[:-3]
|
||||
# Don't overwrite if stem already exists (first wins)
|
||||
if stem not in _file_index:
|
||||
_file_index[stem] = src
|
||||
|
||||
logger.info(f"[wikilinks] Indexed {len(_file_index)} files for wiki-link resolution")
|
||||
|
||||
|
||||
def _resolve_link(name: str) -> Optional[str]:
|
||||
"""Look up a filename in the index (case-insensitive). Returns src_path or None."""
|
||||
key = name.lower().strip()
|
||||
return _file_index.get(key)
|
||||
|
||||
|
||||
def _compute_relative_path(from_page_src: str, to_src: str, anchor: str = '') -> str:
|
||||
"""Compute relative URL from one page to another, accounting for directory URLs."""
|
||||
from_dir = os.path.dirname(from_page_src)
|
||||
|
||||
_, ext = os.path.splitext(to_src)
|
||||
is_image = ext.lower() in _IMAGE_EXTENSIONS
|
||||
is_md = ext.lower() == '.md'
|
||||
|
||||
if is_image:
|
||||
# Images: direct relative path to the file
|
||||
rel = os.path.relpath(to_src, from_dir)
|
||||
return rel.replace(os.sep, '/')
|
||||
|
||||
if is_md and _use_directory_urls:
|
||||
# With directory URLs, pages become page-name/index.html
|
||||
# So we link to the directory (without .md)
|
||||
page_dir = to_src[:-3] # strip .md
|
||||
# Handle index.md → links to the directory itself
|
||||
if os.path.basename(to_src).lower() == 'index.md':
|
||||
page_dir = os.path.dirname(to_src)
|
||||
rel = os.path.relpath(page_dir, from_dir)
|
||||
url = rel.replace(os.sep, '/') + '/'
|
||||
elif is_md:
|
||||
# Without directory URLs, link directly to .md (MkDocs converts to .html)
|
||||
rel = os.path.relpath(to_src, from_dir)
|
||||
url = rel.replace(os.sep, '/').replace('.md', '.html')
|
||||
else:
|
||||
# Other files: direct path
|
||||
rel = os.path.relpath(to_src, from_dir)
|
||||
url = rel.replace(os.sep, '/')
|
||||
|
||||
if anchor:
|
||||
url += '#' + anchor
|
||||
|
||||
return url
|
||||
|
||||
|
||||
# Regex to match wiki-links: optional ! prefix, then [[content]]
|
||||
_WIKILINK_RE = re.compile(r'(!?)\[\[([^\]]+)\]\]')
|
||||
|
||||
|
||||
def _replace_wikilinks(markdown: str, page_src: str) -> str:
|
||||
"""Replace wiki-links in markdown, skipping code blocks."""
|
||||
lines = markdown.split('\n')
|
||||
result_lines = []
|
||||
in_fenced_block = False
|
||||
|
||||
for line in lines:
|
||||
# Track fenced code blocks (``` or ~~~)
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith('```') or stripped.startswith('~~~'):
|
||||
in_fenced_block = not in_fenced_block
|
||||
result_lines.append(line)
|
||||
continue
|
||||
|
||||
if in_fenced_block:
|
||||
result_lines.append(line)
|
||||
continue
|
||||
|
||||
# Process wiki-links in this line, but skip inline code
|
||||
# Strategy: split by inline code spans, only process non-code parts
|
||||
parts = re.split(r'(`[^`]+`)', line)
|
||||
processed_parts = []
|
||||
for part in parts:
|
||||
if part.startswith('`') and part.endswith('`'):
|
||||
# Inside inline code — leave as-is
|
||||
processed_parts.append(part)
|
||||
else:
|
||||
# Process wiki-links in this segment
|
||||
processed_parts.append(_WIKILINK_RE.sub(
|
||||
lambda m: _resolve_wikilink(m, page_src),
|
||||
part
|
||||
))
|
||||
result_lines.append(''.join(processed_parts))
|
||||
|
||||
return '\n'.join(result_lines)
|
||||
|
||||
|
||||
def _resolve_wikilink(match: re.Match, page_src: str) -> str:
|
||||
"""Resolve a single wiki-link match to markdown."""
|
||||
is_embed = match.group(1) == '!'
|
||||
inner = match.group(2).strip()
|
||||
|
||||
# Parse: name|display and name#anchor
|
||||
display = None
|
||||
anchor = ''
|
||||
|
||||
if '|' in inner:
|
||||
name_part, display = inner.split('|', 1)
|
||||
display = display.strip()
|
||||
name_part = name_part.strip()
|
||||
else:
|
||||
name_part = inner
|
||||
|
||||
if '#' in name_part:
|
||||
name_part, anchor = name_part.split('#', 1)
|
||||
anchor = anchor.strip()
|
||||
|
||||
name_part = name_part.strip()
|
||||
|
||||
# Resolve the target file
|
||||
target_src = _resolve_link(name_part)
|
||||
|
||||
if target_src is None:
|
||||
# Try with .md appended
|
||||
target_src = _resolve_link(name_part + '.md')
|
||||
|
||||
if target_src is None:
|
||||
# Try common image extensions
|
||||
for ext in ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp']:
|
||||
target_src = _resolve_link(name_part + ext)
|
||||
if target_src:
|
||||
break
|
||||
|
||||
if target_src is None:
|
||||
logger.warning(f"[wikilinks] Unresolved wiki-link: [[{inner}]] in {page_src}")
|
||||
return match.group(0) # Leave as-is
|
||||
|
||||
url = _compute_relative_path(page_src, target_src, anchor)
|
||||
|
||||
if is_embed:
|
||||
# Image embed: ![[image.png]] or ![[image.png|alt text]]
|
||||
alt = display or name_part
|
||||
return f''
|
||||
else:
|
||||
# Document link: [[page]] or [[page|Display Text]]
|
||||
label = display or (name_part + (f'#{anchor}' if anchor else ''))
|
||||
return f'[{label}]({url})'
|
||||
|
||||
|
||||
def on_page_markdown(markdown: str, page: Any, config: Dict[str, Any], files: Any) -> str:
|
||||
"""Process wiki-links in page markdown content."""
|
||||
# Quick check — skip pages without wiki-links
|
||||
if '[[' not in markdown:
|
||||
return markdown
|
||||
|
||||
return _replace_wikilinks(markdown, page.file.src_path)
|
||||
@@ -5,3 +5,10 @@ hide:
|
||||
- toc
|
||||
title: "Test Page"
|
||||
---
|
||||
---
|
||||
template: test-page.html
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
title: "Test Page"
|
||||
---
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Testing page.
|
||||
|
||||
[[test-page]]]]
|
||||
|
||||
|
||||
<div class="photo-block" data-photo-id="1" data-size="large" data-caption="" data-link-to-gallery="true" data-alignment="center">Loading...</div>
|
||||
|
||||
|
||||
@@ -1,2 +1,19 @@
|
||||
# testing
|
||||
# testing
|
||||
|
||||
## Wiki-Link Tests
|
||||
|
||||
- Doc link: [[installation]]
|
||||
- Doc link with display text: [[installation|Install Guide]]
|
||||
- Doc link with anchor: [[installation#prerequisites]]
|
||||
- Image embed: ![[logo.png]]
|
||||
- Image with alt: ![[logo.png|Site Logo]]
|
||||
- Code block (should NOT be converted):
|
||||
|
||||
```
|
||||
[[this-should-stay-as-is]]
|
||||
```
|
||||
|
||||
- Inline code (should NOT be converted): `[[not-a-link]]`
|
||||
- Unresolved link (should stay as-is): [[nonexistent-page]]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user