MkDocs高度な設定ガイド¶
MkDocs Materialの高度な機能とカスタマイズについて詳しく解説します。
このサイトでの実装状況
このページで紹介している機能の多くは、実際にこのサイトで実装・運用中です!
✅ 実装済みの高度機能(確認場所):
Git最終更新日の自動表示
→ このページの一番下を見てください!「最終更新日: 2025年6月21日」が表示されていますHTML/CSS/JS最適化
→ DevToolsで確認可能。HTMLがminify済みで改行なし・コメント削除済み外部リンクの自動マーキング
→ 外部リンク(例:GitHub)に「↗」アイコンが自動で付きます検索機能の日本語最適化
→ 上部の検索ボックスで「mkdocs」と検索してみてくださいレスポンシブグリッドレイアウト
→ ホームページのリスト項目が画面サイズに応じて3列→1列に変化(インラインCSS実装)ダークモード切り替え
→ 右上のアイコンでライト/ダークモード切り替えが可能読了時間表示
→ 長いページ(例:メインMkDocsガイド)でタイトル下に「📖 読了時間: 約X分」が表示フィードバック機能
→ このページ下部に「この記事は役に立ちましたか?」の評価ボタンがありますrobots.txtとサイトマップ 🆕
→ /robots.txtでクローラー制御、/sitemap.xmlでサイトマップを確認カスタム404ページ 🆕
→ 存在しないURL(例:/not-exist)にアクセスするとカスタム404ページが表示ソーシャルリンク 🆕
→ ページ下部のフッターにGitHub、Twitter、RSSへのソーシャルリンクを追加コードブロックのコピーボタン 🆕
→ このページのコードブロックにマウスを乗せると右上に「📋」ボタンが表示されワンクリックでコピー可能読み込み進行状況バー 🆕
→ ページ読み込み時に上部に青い進行状況バーが表示(高速な場合は一瞬)スムーズスクロール 🆕
→ 目次リンクや内部リンクをクリックするとスムーズにスクロールキーボードショートカット 🆕
→Ctrl+Kで検索、Ctrl+/でショートカット一覧、ESCで検索を閉じる → ページ下部(フッター)にGitHub、Twitter、RSSフィードへのソーシャルリンクが表示されていますコピーライトの更新 🆕
→ フッターのコピーライトが「© 2024-2025 smartscope. All rights reserved.」に更新されましたコードブロックのコピーボタン 🆕
→ コードブロックにマウスをホバーすると「📋 コピー」ボタンが表示され、ワンクリックでコピー可能読み込み進行状況バー 🆕
→ ページをスクロールすると、画面上部に読み込み進行状況を示す青いバーが表示されますスムーズスクロール 🆕
→ ページ内リンクをクリックすると、スムーズにスクロールしますキーボードショートカット 🆕
→Ctrl+Kで検索、Ctrl+/でショートカット一覧表示、ESCで検索を閉じる
前提条件
- mkdocsを使ったGitHub Pagesの作成方法を完了していること
- デザイン改善ガイドの基本設定を理解していること
1. 高度なプラグイン設定¶
1.1 必須プラグイン(このサイトで実装済み)¶
実際の設定ファイル
このサイトでは以下の設定をmkdocs.ymlに実装しています:
plugins:
- search:
lang: ja
separator: '[\s\-\.]+'
prebuild_index: true
- git-revision-date-localized:
type: datetime
timezone: Asia/Tokyo
locale: ja
fallback_to_build_date: true
- minify:
minify_html: true
minify_js: true
minify_css: true
htmlmin_opts:
remove_comments: true
remove_empty_space: true
cache_safe: true
実装効果(実際に確認してみてください): - 更新日時: 👇 このページの一番下にスクロールすると「最終更新日: 2025年6月21日 14:15:07」が表示されます - 最適化: F12でDevToolsを開き、Elementsタブで<html>タグを確認すると、改行やコメントが削除され1行に圧縮されています - 検索: 🔍 上部の検索ボックスで「cron」「docker」など日本語混じりのキーワードを検索すると高速で結果が表示されます
1.2 コンテンツ生成プラグイン¶
macros プラグイン(動的コンテンツ生成)¶
pip install mkdocs-macros-plugin
plugins:
- macros:
module_name: main
include_dir: includes
variables:
site_version: "1.0.0"
support_email: "support@example.com"
main.pyでマクロを定義:
import os
from datetime import datetime
def define_env(env):
"""
マクロとフィルターを定義
"""
@env.macro
def current_year():
return datetime.now().year
@env.macro
def last_updated():
return datetime.now().strftime("%Y年%m月%d日")
@env.macro
def code_example(language, code, title=""):
title_html = f"<div class='code-title'>{title}</div>" if title else ""
return f"{title_html}```{language}\n{code}\n```"
@env.macro
def file_size(file_path):
"""ファイルサイズを取得"""
try:
size = os.path.getsize(file_path)
return f"{size:,} bytes"
except:
return "不明"
@env.macro
def feature_box(title, description, icon=""):
"""機能説明ボックス"""
icon_html = f"<span class='feature-icon'>{icon}</span>" if icon else ""
return f"""
<div class="feature-box">
{icon_html}
<h4>{title}</h4>
<p>{description}</p>
</div>
"""
@env.filter
def format_number(value):
"""数値をカンマ区切りにフォーマット"""
return f"{int(value):,}"
@env.filter
def truncate_jp(text, length=50):
"""日本語文字列の切り詰め"""
return text[:length] + "..." if len(text) > length else text
使用例:
<!-- 動的な年表示 -->
© {{ current_year() }} Your Company
<!-- 最終更新日 -->
最終更新: {{ last_updated() }}
<!-- コード例(タイトル付き) -->
{{ code_example('python', 'print("Hello World")', 'Python基本例') }}
<!-- ファイルサイズ表示 -->
ダウンロードサイズ: {{ file_size('downloads/app.zip') }}
<!-- 機能紹介ボックス -->
{{ feature_box('高速検索', '日本語に最適化された検索機能', '🔍') }}
<!-- 数値フォーマット -->
ダウンロード数: {{ 123456 | format_number }}回
<!-- テキスト切り詰め -->
{{ long_description | truncate_jp(30) }}
awesome-pages プラグイン(ナビゲーション制御)¶
pip install mkdocs-awesome-pages-plugin
plugins:
- awesome-pages:
strict: false
collapse_single_pages: true
filename: .pages
.pagesファイルでナビゲーション制御:
# docs/.pages - ルートナビゲーション
nav:
- index.md
- はじめに: getting-started
- チュートリアル: tutorials
- ... | guides/**/*.md # guidesディレクトリの全MDファイル
- API: api
- FAQ: faq.md
# docs/tutorials/.pages - セクション別ナビゲーション
title: チュートリアル
nav:
- overview.md
- 基礎編:
- basic-setup.md
- basic-usage.md
- 応用編:
- advanced-features.md
- customization.md
- rest: null # 残りのファイルを除外
タグプラグイン(コンテンツ分類)¶
pip install mkdocs-material[recommended]
plugins:
- tags:
tags_file: tags.md
tags_extra_files:
compatibility.md:
- compatibility
changelog.md:
- changelog
ページにタグを追加:
---
tags:
- setup
- beginner
- windows
- linux
---
# インストールガイド
このページではMkDocsのインストール方法を説明します。
1.3 文書生成プラグイン¶
mkdocstrings プラグイン¶
pip install mkdocstrings[python]
plugins:
- mkdocstrings:
handlers:
python:
options:
docstring_style: google
show_source: true
show_root_heading: true
Python docstringからドキュメント生成:
::: my_module.my_function
options:
show_source: true
show_root_heading: true
2. カスタムテーマとスタイル¶
実装例を確認
実際にこのサイトで確認できる機能: - 3列グリッドレイアウト: ホームページを見てください。リスト項目が3列に並んでいます(インラインCSSで実装) - カードスタイル: ホームページの項目が角丸のカード風デザインになっています - レスポンシブ対応: ブラウザ幅を狭めると3列→1列に自動調整されます
2.1 テーマの継承¶
custom_theme/ディレクトリを作成してテーマをカスタマイズ:
theme:
name: material
custom_dir: custom_theme/
2.2 テンプレートのオーバーライド¶
custom_theme/main.html:
{% extends "base.html" %}
{% block content %}
{{ super() }}
<!-- カスタムフッター -->
<footer class="custom-footer">
<div class="container">
<p>© {{ config.copyright }}</p>
<p>最終更新: {{ page.meta.git_revision_date_localized }}</p>
</div>
</footer>
{% endblock %}
{% block extrahead %}
<!-- カスタムメタタグ -->
<meta name="author" content="{{ config.site_author }}">
<meta name="robots" content="index, follow">
<!-- Open Graph tags -->
<meta property="og:title" content="{{ page.title | default(config.site_name, true) }}">
<meta property="og:description" content="{{ page.meta.description | default(config.site_description, true) }}">
<meta property="og:image" content="{{ page.meta.image | default('/assets/og-image.png', true) | url }}">
{% endblock %}
2.3 カスタムCSS実装例¶
このサイトでの実装方法:
インラインCSSでシンプルな3列グリッドを実装しています(docs/index.md内):
<style>
.md-typeset ul {
display: grid !important;
grid-template-columns: repeat(3, 1fr) !important;
gap: 1rem !important;
list-style: none !important;
padding: 0 !important;
}
.md-typeset ul li {
background: var(--md-default-bg-color) !important;
border: 1px solid #ddd !important;
border-radius: 8px !important;
padding: 1rem !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
}
@media (max-width: 768px) {
.md-typeset ul {
grid-template-columns: 1fr !important;
}
}
</style>
より高度なCSS変数の例:
/* docs/stylesheets/extra.css */
:root {
/* ブランドカラー */
--md-primary-fg-color: #1976d2;
--md-accent-fg-color: #ff9800;
/* フォント */
--md-text-font: "Noto Sans JP", sans-serif;
--md-code-font: "JetBrains Mono", monospace;
}
/* カスタムコンポーネント */
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
margin: 2rem 0;
}
.feature-card {
background: var(--md-default-bg-color);
border: 1px solid var(--md-default-fg-color--lightest);
border-radius: 8px;
padding: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.feature-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
3. 多言語対応¶
多言語対応の詳細な設定方法については、専用ガイドをご覧ください:
このガイドでは以下の内容を詳しく解説しています:
- i18nプラグインの詳細設定
- ナビゲーション翻訳の実装方法
- ファイル命名規則(.en.md)
- ビルド・デプロイの手順
- トラブルシューティング
- ベストプラクティス
実装状況
このサイトでは日本語(デフォルト)と英語の2言語に対応しており、実際の動作を確認できます。
4. 高度なMarkdown機能¶
このページで実際に使用中
この「高度な設定ガイド」ページ自体で以下の機能を使っています: - admonition(注意書きブロック): この青いボックス自体がadmonition機能です - コードハイライト: 上記のYAML・CSS・JavaScript・Python・Bashコードがすべてシンタックスハイライト済み - アンカーリンク: 各見出しの右に「¶」アイコンがありクリックでリンク取得可能
4.1 カスタムブロック¶
markdown_extensions:
- pymdownx.blocks.admonition:
types:
- note
- tip
- warning
- danger
- quote
- custom
- pymdownx.blocks.details:
types:
- details
- summary
- pymdownx.blocks.definition:
types:
- definition
カスタムブロックの使用:
/// custom | カスタムブロック
このブロックはカスタムスタイルが適用されます。
///
/// definition | 用語定義
**API**: Application Programming Interface
アプリケーション同士が情報をやり取りするためのインターフェース
///
4.2 数式とダイアグラム¶
markdown_extensions:
- pymdownx.arithmatex:
generic: true
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
extra_javascript:
- https://unpkg.com/mermaid@8.13.0/dist/mermaid.min.js
- https://polyfill.io/v3/polyfill.min.js?features=es6
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
使用例:
数式:
$$
\frac{n!}{k!(n-k)!} = \binom{n}{k}
$$
フローチャート:
```mermaid
graph TD
A[開始] --> B{条件}
B -->|Yes| C[処理A]
B -->|No| D[処理B]
C --> E[終了]
D --> E
### 4.3 インタラクティブ要素
```html
<!-- docs/javascripts/interactive.js -->
function createInteractiveCode() {
const codeBlocks = document.querySelectorAll('pre code');
codeBlocks.forEach(block => {
const wrapper = document.createElement('div');
wrapper.className = 'code-wrapper';
const toolbar = document.createElement('div');
toolbar.className = 'code-toolbar';
const copyBtn = document.createElement('button');
copyBtn.textContent = 'コピー';
copyBtn.onclick = () => {
navigator.clipboard.writeText(block.textContent);
copyBtn.textContent = 'コピー済み!';
setTimeout(() => copyBtn.textContent = 'コピー', 2000);
};
const runBtn = document.createElement('button');
runBtn.textContent = '実行';
runBtn.onclick = () => {
// コード実行ロジック
executeCode(block.textContent);
};
toolbar.appendChild(copyBtn);
if (block.className.includes('python')) {
toolbar.appendChild(runBtn);
}
wrapper.appendChild(toolbar);
block.parentNode.insertBefore(wrapper, block);
wrapper.appendChild(block.parentNode);
});
}
document.addEventListener('DOMContentLoaded', createInteractiveCode);
5. 検索機能の強化¶
検索機能をテストしてみてください
実際の検索強化を体験: 1. 🔍 上部の検索ボックスで「mkdocs」と入力してみてください 2. 「crontab」「docker」「python」など技術用語を検索してみてください
3. 「設定」「ガイド」など日本語キーワードでも高速検索可能です 4. 検索結果のハイライト: 検索語がページ内で黄色くハイライトされます
5.1 Elasticsearch統合¶
pip install mkdocs-elasticsearch-plugin
plugins:
- elasticsearch:
host: localhost:9200
index: mkdocs
only_english: false
include_score: true
5.2 カスタム検索フィルター¶
// docs/javascripts/search-filter.js
class SearchFilter {
constructor() {
this.init();
}
init() {
const searchInput = document.querySelector('[data-md-component="search-query"]');
if (searchInput) {
this.addFilters();
this.bindEvents();
}
}
addFilters() {
const filterContainer = document.createElement('div');
filterContainer.className = 'search-filters';
filterContainer.innerHTML = `
<label><input type="checkbox" name="filter" value="guide"> ガイド</label>
<label><input type="checkbox" name="filter" value="reference"> リファレンス</label>
<label><input type="checkbox" name="filter" value="tutorial"> チュートリアル</label>
`;
const searchContainer = document.querySelector('.md-search');
searchContainer.appendChild(filterContainer);
}
bindEvents() {
const filters = document.querySelectorAll('input[name="filter"]');
filters.forEach(filter => {
filter.addEventListener('change', this.applyFilters.bind(this));
});
}
applyFilters() {
const activeFilters = Array.from(document.querySelectorAll('input[name="filter"]:checked'))
.map(input => input.value);
const searchResults = document.querySelectorAll('[data-md-component="search-result"] a');
searchResults.forEach(result => {
const shouldShow = activeFilters.length === 0 ||
activeFilters.some(filter => result.href.includes(filter));
result.style.display = shouldShow ? 'block' : 'none';
});
}
}
new SearchFilter();
6. パフォーマンス最適化¶
パフォーマンス最適化の実装状況
このサイトで実際に確認できる最適化: 1. ページロード速度: F12 → NetworkタブでHTMLファイルのサイズを確認。minify済みで大幅に削減 2. 画像最適化: このサイトの画像は適切なサイズで配信 3. CSS/JS圧縮: Sourcesタブでextra.cssやextra.jsがminify済みであることを確認可能 4. キャッシュ活用: 2回目のアクセス時にファイルがキャッシュから読み込まれる
6.1 遅延読み込み¶
// docs/javascripts/lazy-loading.js
function lazyLoadImages() {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
imageObserver.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
}
document.addEventListener('DOMContentLoaded', lazyLoadImages);
6.2 Service Worker¶
// docs/sw.js
const CACHE_NAME = 'mkdocs-v1';
const urlsToCache = [
'/',
'/assets/stylesheets/main.css',
'/assets/javascripts/bundle.js'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
);
});
7. CI/CD パイプライン¶
7.1 GitHub Actions¶
.github/workflows/docs.yml:
name: Deploy Documentation
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
pip install mkdocs-material
pip install mkdocs-git-revision-date-localized-plugin
pip install mkdocs-minify-plugin
- name: Build documentation
run: mkdocs build --strict
- name: Run tests
run: |
# リンクチェック
pip install pytest-check-links
pytest --check-links docs/
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main'
run: mkdocs gh-deploy --force
7.2 品質チェック¶
# docs/test_docs.py
import pytest
import re
from pathlib import Path
def test_no_broken_internal_links():
"""内部リンクの検証"""
docs_dir = Path("docs")
for md_file in docs_dir.rglob("*.md"):
content = md_file.read_text(encoding="utf-8")
links = re.findall(r'\[.*?\]\((.*?)\)', content)
for link in links:
if link.startswith('./') or link.startswith('../'):
target = (md_file.parent / link).resolve()
assert target.exists(), f"Broken link in {md_file}: {link}"
def test_required_frontmatter():
"""フロントマターの検証"""
required_fields = ['title', 'description']
docs_dir = Path("docs")
for md_file in docs_dir.rglob("*.md"):
if md_file.name == "index.md":
continue
content = md_file.read_text(encoding="utf-8")
if content.startswith("---"):
frontmatter = content.split("---")[1]
for field in required_fields:
assert f"{field}:" in frontmatter, f"Missing {field} in {md_file}"
8. SEOとアナリティクス高度設定¶
8.1 構造化データ(Schema.org)¶
<!-- custom_theme/main.html -->
{% extends "base.html" %}
{% block extrahead %}
{{ super() }}
<!-- JSON-LD構造化データ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "{{ page.title }}",
"description": "{{ page.meta.description or config.site_description }}",
"author": {
"@type": "Organization",
"name": "{{ config.site_author }}"
},
"publisher": {
"@type": "Organization",
"name": "{{ config.site_name }}",
"url": "{{ config.site_url }}"
},
"datePublished": "{{ page.meta.date or page.meta.git_creation_date_localized }}",
"dateModified": "{{ page.meta.git_revision_date_localized }}",
"url": "{{ page.canonical_url }}"
}
</script>
<!-- Open Graph メタタグ -->
<meta property="og:type" content="article">
<meta property="og:title" content="{{ page.title }}">
<meta property="og:description" content="{{ page.meta.description or config.site_description }}">
<meta property="og:url" content="{{ page.canonical_url }}">
<meta property="og:site_name" content="{{ config.site_name }}">
<meta property="og:image" content="{{ page.meta.image or config.site_url + 'assets/images/banner.png' }}">
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ page.title }}">
<meta name="twitter:description" content="{{ page.meta.description or config.site_description }}">
<meta name="twitter:image" content="{{ page.meta.image or config.site_url + 'assets/images/banner.png' }}">
{% endblock %}
8.2 サイトマップとrobots.txt¶
# mkdocs.yml
plugins:
- sitemap:
pretty_urls: false
exclude:
- "404.html"
- "drafts/"
- redirects:
redirect_maps:
'old-page.md': 'new-page.md'
'old-section/': 'new-section/'
# docs/robots.txt
User-agent: *
Allow: /
Disallow: /drafts/
Disallow: /admin/
Sitemap: https://smartscope.github.io/note/sitemap.xml
8.3 高度なアナリティクス設定¶
# mkdocs.yml
extra:
analytics:
provider: google
property: G-XXXXXXXXXX
feedback:
title: この記事は役に立ちましたか?
ratings:
- icon: material/thumb-up-outline
name: 役に立った
data: 1
note: >
フィードバックありがとうございます!
- icon: material/thumb-down-outline
name: 改善が必要
data: 0
note: >
フィードバックありがとうございます。どのような改善が必要か教えてください。
# カスタムイベント追跡
custom_events:
- download_guide
- external_link_click
- search_no_results
カスタムアナリティクス実装:
// docs/javascripts/analytics.js
// カスタムイベント追跡
function trackCustomEvent(eventName, parameters = {}) {
if (typeof gtag !== 'undefined') {
gtag('event', eventName, {
custom_parameter_1: parameters.category || 'unknown',
custom_parameter_2: parameters.label || '',
value: parameters.value || 1
});
}
}
// ダウンロード追跡
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('a[href$=".pdf"], a[href$=".zip"], a[href$=".exe"]').forEach(link => {
link.addEventListener('click', function() {
trackCustomEvent('download_guide', {
category: 'download',
label: this.href,
value: 1
});
});
});
// 外部リンク追跡
document.querySelectorAll('a[href^="http"]:not([href*="' + location.hostname + '"])').forEach(link => {
link.addEventListener('click', function() {
trackCustomEvent('external_link_click', {
category: 'external',
label: this.href
});
});
});
// 検索結果なし追跡
const searchObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.target.classList.contains('md-search-result__meta')) {
const text = mutation.target.textContent;
if (text.includes('見つかりませんでした') || text.includes('No matches found')) {
const query = document.querySelector('[data-md-component="search-query"]').value;
trackCustomEvent('search_no_results', {
category: 'search',
label: query
});
}
}
});
});
const searchResults = document.querySelector('[data-md-component="search-result"]');
if (searchResults) {
searchObserver.observe(searchResults, { childList: true, subtree: true });
}
});
9. セキュリティ設定¶
9.1 CSP (Content Security Policy)¶
<!-- custom_theme/base.html -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https: https://www.google-analytics.com;
connect-src 'self' https://api.github.com https://www.google-analytics.com;
frame-ancestors 'none';
">
9.2 セキュリティヘッダー¶
# _headers (Netlify用)
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Strict-Transport-Security: max-age=31536000; includeSubDomains
# 静的アセット用キャッシュ設定
/assets/*
Cache-Control: public, max-age=31536000, immutable
/images/*
Cache-Control: public, max-age=31536000, immutable
# HTML用キャッシュ設定
/*.html
Cache-Control: public, max-age=0, must-revalidate
10. 高度なカスタマイゼーション事例¶
10.1 ダイナミックサイドバー¶
// docs/javascripts/dynamic-sidebar.js
document.addEventListener('DOMContentLoaded', function() {
// 目次の自動生成
function createTableOfContents() {
const headings = document.querySelectorAll('h2, h3, h4');
const tocContainer = document.createElement('nav');
tocContainer.className = 'md-nav md-nav--secondary';
tocContainer.innerHTML = '<div class="md-nav__title">このページの内容</div>';
const tocList = document.createElement('ul');
tocList.className = 'md-nav__list';
headings.forEach((heading, index) => {
const id = heading.id || `heading-${index}`;
heading.id = id;
const listItem = document.createElement('li');
listItem.className = 'md-nav__item';
const link = document.createElement('a');
link.className = 'md-nav__link';
link.href = `#${id}`;
link.textContent = heading.textContent;
link.style.paddingLeft = `${(parseInt(heading.tagName.charAt(1)) - 2) * 1}rem`;
listItem.appendChild(link);
tocList.appendChild(listItem);
});
tocContainer.appendChild(tocList);
// サイドバーに挿入
const sidebar = document.querySelector('.md-sidebar--secondary .md-sidebar__scrollwrap');
if (sidebar && tocList.children.length > 0) {
sidebar.appendChild(tocContainer);
}
}
createTableOfContents();
// スクロール位置の追跡とハイライト
function highlightCurrentSection() {
const headings = document.querySelectorAll('h2[id], h3[id], h4[id]');
const tocLinks = document.querySelectorAll('.md-nav--secondary a[href^="#"]');
let currentHeading = null;
const scrollTop = window.pageYOffset;
headings.forEach(heading => {
if (heading.offsetTop <= scrollTop + 100) {
currentHeading = heading;
}
});
tocLinks.forEach(link => {
link.classList.remove('md-nav__link--active');
if (currentHeading && link.getAttribute('href') === `#${currentHeading.id}`) {
link.classList.add('md-nav__link--active');
}
});
}
window.addEventListener('scroll', highlightCurrentSection);
});
10.2 コード実行機能¶
// docs/javascripts/code-runner.js
document.addEventListener('DOMContentLoaded', function() {
// Python用の簡易実行環境(Pyodideを使用)
async function setupPythonRunner() {
if (typeof loadPyodide === 'undefined') {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js';
document.head.appendChild(script);
await new Promise(resolve => script.onload = resolve);
}
const pyodide = await loadPyodide();
document.querySelectorAll('pre code.language-python').forEach(codeBlock => {
const runButton = document.createElement('button');
runButton.textContent = '実行';
runButton.className = 'md-button md-button--primary code-run-btn';
const outputDiv = document.createElement('div');
outputDiv.className = 'code-output';
outputDiv.style.display = 'none';
runButton.addEventListener('click', async () => {
const code = codeBlock.textContent;
outputDiv.style.display = 'block';
outputDiv.textContent = '実行中...';
try {
// 標準出力をキャプチャ
pyodide.runPython(`
import sys
import io
sys.stdout = io.StringIO()
`);
pyodide.runPython(code);
const output = pyodide.runPython('sys.stdout.getvalue()');
outputDiv.innerHTML = `<pre><code>${output || '出力なし'}</code></pre>`;
} catch (error) {
outputDiv.innerHTML = `<pre class="error"><code>エラー: ${error.message}</code></pre>`;
}
});
codeBlock.parentNode.insertBefore(runButton, codeBlock.nextSibling);
runButton.parentNode.insertBefore(outputDiv, runButton.nextSibling);
});
}
// JavaScript用の実行環境
function setupJavaScriptRunner() {
document.querySelectorAll('pre code.language-javascript').forEach(codeBlock => {
const runButton = document.createElement('button');
runButton.textContent = '実行';
runButton.className = 'md-button md-button--primary code-run-btn';
const outputDiv = document.createElement('div');
outputDiv.className = 'code-output';
outputDiv.style.display = 'none';
runButton.addEventListener('click', () => {
const code = codeBlock.textContent;
outputDiv.style.display = 'block';
// console.logをキャプチャ
const originalLog = console.log;
const logs = [];
console.log = (...args) => logs.push(args.join(' '));
try {
eval(code);
outputDiv.innerHTML = `<pre><code>${logs.join('\n') || '出力なし'}</code></pre>`;
} catch (error) {
outputDiv.innerHTML = `<pre class="error"><code>エラー: ${error.message}</code></pre>`;
} finally {
console.log = originalLog;
}
});
codeBlock.parentNode.insertBefore(runButton, codeBlock.nextSibling);
runButton.parentNode.insertBefore(outputDiv, runButton.nextSibling);
});
}
setupJavaScriptRunner();
// setupPythonRunner(); // 必要に応じてコメントアウト
});
10.3 リアルタイムコラボレーション¶
// docs/javascripts/collaboration.js
class CollaborationManager {
constructor() {
this.ws = null;
this.userId = this.generateUserId();
this.cursors = new Map();
this.setupWebSocket();
this.setupCursorTracking();
}
generateUserId() {
return 'user_' + Math.random().toString(36).substr(2, 9);
}
setupWebSocket() {
// WebSocket接続(実際のサーバーが必要)
try {
this.ws = new WebSocket('wss://your-collaboration-server.com/ws');
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
this.ws.onopen = () => {
console.log('Collaboration server connected');
this.sendMessage({ type: 'join', userId: this.userId });
};
} catch (error) {
console.log('Collaboration feature unavailable');
}
}
setupCursorTracking() {
document.addEventListener('mousemove', (event) => {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.sendMessage({
type: 'cursor',
userId: this.userId,
x: event.clientX,
y: event.clientY,
page: window.location.pathname
});
}
});
}
handleMessage(data) {
switch (data.type) {
case 'cursor':
this.updateCursor(data);
break;
case 'highlight':
this.showHighlight(data);
break;
case 'comment':
this.showComment(data);
break;
}
}
updateCursor(data) {
if (data.userId === this.userId || data.page !== window.location.pathname) return;
let cursor = this.cursors.get(data.userId);
if (!cursor) {
cursor = document.createElement('div');
cursor.className = 'collaboration-cursor';
cursor.innerHTML = `
<div class="cursor-pointer"></div>
<div class="cursor-label">${data.userId}</div>
`;
document.body.appendChild(cursor);
this.cursors.set(data.userId, cursor);
}
cursor.style.left = data.x + 'px';
cursor.style.top = data.y + 'px';
cursor.style.display = 'block';
// 3秒後に非表示
clearTimeout(cursor.hideTimeout);
cursor.hideTimeout = setTimeout(() => {
cursor.style.display = 'none';
}, 3000);
}
sendMessage(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
}
// CSS for collaboration features
const collaborationCSS = `
.collaboration-cursor {
position: fixed;
pointer-events: none;
z-index: 10000;
}
.cursor-pointer {
width: 20px;
height: 20px;
background: #ff6b6b;
border-radius: 50% 0;
transform: rotate(-45deg);
}
.cursor-label {
background: #ff6b6b;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
margin-left: 20px;
margin-top: -15px;
}
.code-output {
background: var(--md-code-bg-color);
border: 1px solid var(--md-default-fg-color--lighter);
border-radius: 4px;
padding: 1rem;
margin-top: 1rem;
}
.code-output.error {
border-color: #f44336;
background: #ffebee;
}
.code-run-btn {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
`;
const style = document.createElement('style');
style.textContent = collaborationCSS;
document.head.appendChild(style);
// Initialize collaboration (optional)
// new CollaborationManager();
11. トラブルシューティング¶
よくある問題と解決法¶
ビルドエラー¶
# 詳細ログでビルド
mkdocs build --verbose
# 設定の検証
mkdocs config
# プラグインの検証
python -c "import mkdocs_material; print('OK')"
# 依存関係の確認
pip list | grep mkdocs
メモリ不足¶
# 大きなサイト用の設定
plugins:
- search:
prebuild_index: false # インデックス事前生成を無効化
- minify:
minify_html: false # HTMLミニファイを無効化
# JavaScript/CSSも無効化
extra_javascript: []
extra_css: []
デプロイ失敗¶
# gh-pagesブランチのリセット
git branch -D gh-pages
git push origin --delete gh-pages
mkdocs gh-deploy
# 権限の確認
git config --list | grep user
git config --list | grep remote
# 強制デプロイ
mkdocs gh-deploy --force
パフォーマンス最適化¶
# ビルド時間の測定
time mkdocs build
# プラグイン別の処理時間確認
mkdocs build --verbose 2>&1 | grep "INFO.*took"
# ファイルサイズの確認
du -sh site/
find site/ -name "*.html" -exec wc -c {} + | sort -n