diff --git a/CLAUDE.md b/CLAUDE.md index d3a8d3b9..b3b7bac1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,7 @@ bunx prettier -w . # prettier 修正 # 実行 bunx tsx path/to/file.ts # TypeScript実行 make lab # JupyterLab起動 +python generate_index.py # public/index.html 再生成 ``` パッケージマネージャは**Bun**。`npm`ではなく`bun install`を使用。 @@ -77,6 +78,19 @@ make lab # JupyterLab起動 - **Prettier**: semi, singleQuote, tabWidth: 4, printWidth: 100 - **Python**: ruff + black +### インデックスページ生成 + +- `public/index.html` は `python generate_index.py` で自動生成。直接編集禁止 +- テンプレートは `generate_index.py` 内に埋め込み(Python `.format()` 使用、`{{`/`}}` でブレースエスケープ) +- `public/` 配下の全ファイルは生成物。変更は必ずジェネレータ側で行う +- テンプレート内JS で `innerHTML` 禁止(セキュリティフックがブロック)→ `textContent` + DOM API を使用 +- HTML出力に埋め込む文字列は `html.escape()` 必須(XSS防止) + +### ブラウザテスト(Playwright MCP) + +- `file://` URLはブロックされる → `python -m http.server 8765 --directory public` でローカルサーバー起動 +- `ruff` / `black` はグローバル未インストールの場合あり → `python -c "import py_compile; ..."` でシンタックスチェック代替 + ## SVGフローチャートガイドライン `.agent/workflows/svg_flowchart_guidelines.md` に詳細あり。主要ポイント: diff --git a/generate_index.py b/generate_index.py index d2b5ab32..1a56f0f7 100644 --- a/generate_index.py +++ b/generate_index.py @@ -76,7 +76,14 @@ def copy_vendor_files(self, output_dir: str) -> None: print(f"Copied FontAwesome webfonts: {fa_fonts_src} -> {fa_fonts_dest}") def rewrite_html_content(self, content: str) -> str: - """Replaces CDN links with local vendor links.""" + """ + Rewrite known CDN asset URLs in an HTML document to their corresponding local vendor paths. + + Replaces specific external CDN links (React, Babel, Tailwind, PrismJS, FontAwesome, etc.) with local /vendor/... paths so the returned HTML references vendored assets. + + Returns: + The input HTML string with matched CDN URLs substituted by local vendor URLs. + """ replacements = [ # React ('https://unpkg.com/react@18/umd/react.development.js', '/vendor/react/react.development.js'), @@ -104,6 +111,11 @@ def rewrite_html_content(self, content: str) -> str: return content def generate_index(self) -> None: + """ + Generate a static index HTML page under the public directory listing repository HTML files grouped by category. + + Scans the repository (excluding common build, VCS, virtualenv and vendor directories), copies required vendor assets into public/vendor, rewrites HTML files to reference local vendored assets, copies those files into the public tree, and produces a themed index at public/index.html with category tabs, search, pagination, per-category icons, total counts, and a UTC generation timestamp. Files in hidden categories (category name starting with '.') or in excluded directories are skipped. If reading or rewriting a file fails, the file is copied as a fallback. + """ root_dir = "." output_dir = "public" index_file = "index.html" @@ -158,7 +170,10 @@ def generate_index(self) -> None: if category.startswith('.'): continue - title = self.get_html_title(filepath) + try: + title = self.get_html_title(filepath) + except Exception: + title = os.path.basename(filepath) structure[category].append((title, rel_path)) # Sort categories and files @@ -166,38 +181,456 @@ def generate_index(self) -> None: for cat in structure: structure[cat].sort(key=lambda x: x[0]) + # カテゴリアイコンと合計数 + category_icons = { + 'All': '\U0001F30D', + 'Algorithm': '\U0001F9E9', + 'Concurrency': '\u26A1', + 'DataStructures': '\U0001F3D7\uFE0F', + 'JavaScript': '\U0001F4DC', + 'Mathematics': '\U0001F4D0', + 'SQL': '\U0001F5C3\uFE0F', + } + total_count = sum(len(v) for v in structure.values()) + domain_count = len(sorted_categories) current_time = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") - # HTML Template + # HTML Template — "Refined Lab" デザイン html_template = """ - + - Algorithm Study Index + Algorithm Study Lab + + + -

Algorithm Study Index

+ + + + +
+ \U0001F50D + + + +
- + {tabs}
@@ -205,34 +638,252 @@ def generate_index(self) -> None: +
\U0001F50ENo results found
{tab_contents} """ + # HTML生成 tabs_html = "" tab_contents_html = "" all_files_html = "" @@ -240,24 +891,31 @@ def generate_index(self) -> None: for category in sorted_categories: files = structure[category] count = len(files) - tabs_html += f'\n' + icon = category_icons.get(category, '') + css_cat = category.lower() + safe_category = html.escape(category, quote=True) + + tabs_html += f'\n' file_list_html = '' - tab_contents_html += f'
\n{file_list_html}\n
\n' + tab_contents_html += f'
\n{file_list_html}\n
\U0001F50ENo results found
\n
\n' final_html = html_template.format( tabs=tabs_html, all_files=all_files_html, tab_contents=tab_contents_html, - timestamp=current_time + timestamp=current_time, + total_count=total_count, + domain_count=domain_count, ) output_index_path = os.path.join(output_dir, index_file) @@ -267,4 +925,4 @@ def generate_index(self) -> None: print(f"Successfully updated {output_index_path} with vendored assets at {current_time}") if __name__ == "__main__": - Solution().generate_index() + Solution().generate_index() \ No newline at end of file diff --git a/public/index.html b/public/index.html index 1440e298..371a7213 100644 --- a/public/index.html +++ b/public/index.html @@ -1,395 +1,1024 @@ - + - Algorithm Study Index + Algorithm Study Lab + + + -

Algorithm Study Index

+ + + + +
+ 🔍 + + + +
- - - - - - - + + + + + + +
+
🔎No results found
+
🔎No results found
+
🔎No results found
+
🔎No results found
+
🔎No results found
+
🔎No results found
+
🔎No results found
\ No newline at end of file