mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
- Improved the reading of the discovery index by adding a fallback mechanism to scan directories for discovery folders if the index.json is invalid or missing. - Added sorting of discoveries by creation time in descending order. - Enhanced the `appendToIssuesJsonl` function to include deduplication logic for issues based on ID and source finding ID. - Updated the discovery route handler to reflect the number of issues added and skipped during export. - Introduced UI elements for selecting and deselecting findings in the dashboard. - Added CSS styles for exported findings and action buttons. - Implemented search functionality for filtering findings based on title, file, and description. - Added internationalization support for new UI elements. - Created scripts for automated API extraction from various project types, including FastAPI and TypeScript. - Documented the API extraction process and library bundling instructions.
328 lines
10 KiB
HTML
328 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="description" content="{{SOFTWARE_NAME}} - Interactive Software Manual">
|
|
<meta name="generator" content="software-manual-skill">
|
|
<title>{{SOFTWARE_NAME}} v{{VERSION}} - User Manual</title>
|
|
<style>
|
|
{{EMBEDDED_CSS}}
|
|
</style>
|
|
</head>
|
|
<body class="wiki-container" data-theme="light">
|
|
<!-- Sidebar Navigation -->
|
|
<aside class="wiki-sidebar">
|
|
<!-- Logo and Title -->
|
|
<div class="wiki-logo">
|
|
<div class="logo-placeholder">{{SOFTWARE_NAME}}</div>
|
|
<h1>{{SOFTWARE_NAME}}</h1>
|
|
<span class="version">v{{VERSION}}</span>
|
|
</div>
|
|
|
|
<!-- Search Box -->
|
|
<div class="wiki-search">
|
|
<input type="text" id="searchInput" placeholder="Search documentation..." aria-label="Search">
|
|
<div id="searchResults" class="search-results" aria-live="polite"></div>
|
|
</div>
|
|
|
|
<!-- Tag Navigation (Dynamic) -->
|
|
<nav class="wiki-tags" aria-label="Filter by category">
|
|
<button class="tag active" data-tag="all">全部</button>
|
|
{{TAG_BUTTONS_HTML}}
|
|
</nav>
|
|
|
|
<!-- Table of Contents -->
|
|
{{TOC_HTML}}
|
|
</aside>
|
|
|
|
<!-- Main Content Area -->
|
|
<main class="wiki-content">
|
|
<!-- Header Bar -->
|
|
<header class="content-header">
|
|
<button class="sidebar-toggle" id="sidebarToggle" aria-label="Toggle sidebar">
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
</button>
|
|
<div class="header-actions">
|
|
<button class="expand-all" id="expandAll">Expand All</button>
|
|
<button class="collapse-all" id="collapseAll">Collapse All</button>
|
|
<button class="print-btn" id="printBtn">Print</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Tiddler Container -->
|
|
<div class="tiddler-container">
|
|
{{TIDDLERS_HTML}}
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<footer class="wiki-footer">
|
|
<p>Generated by <strong>software-manual-skill</strong></p>
|
|
<p>Last updated: <time datetime="{{TIMESTAMP}}">{{TIMESTAMP}}</time></p>
|
|
</footer>
|
|
</main>
|
|
|
|
<!-- Theme Toggle Button -->
|
|
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
|
|
<span class="sun-icon">☀</span>
|
|
<span class="moon-icon">☾</span>
|
|
</button>
|
|
|
|
<!-- Back to Top Button -->
|
|
<button class="back-to-top" id="backToTop" aria-label="Back to top">↑</button>
|
|
|
|
<!-- Search Index Data -->
|
|
<script id="search-index" type="application/json">
|
|
{{SEARCH_INDEX_JSON}}
|
|
</script>
|
|
|
|
<!-- Embedded JavaScript -->
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
// ========== Search Functionality ==========
|
|
class WikiSearch {
|
|
constructor(indexData) {
|
|
this.index = indexData;
|
|
}
|
|
|
|
search(query) {
|
|
if (!query || query.length < 2) return [];
|
|
|
|
const results = [];
|
|
const lowerQuery = query.toLowerCase();
|
|
const queryWords = lowerQuery.split(/\s+/);
|
|
|
|
for (const [id, content] of Object.entries(this.index)) {
|
|
let score = 0;
|
|
|
|
// Title match (higher weight)
|
|
const titleLower = content.title.toLowerCase();
|
|
if (titleLower.includes(lowerQuery)) {
|
|
score += 10;
|
|
}
|
|
queryWords.forEach(word => {
|
|
if (titleLower.includes(word)) score += 3;
|
|
});
|
|
|
|
// Body match
|
|
const bodyLower = content.body.toLowerCase();
|
|
if (bodyLower.includes(lowerQuery)) {
|
|
score += 5;
|
|
}
|
|
queryWords.forEach(word => {
|
|
if (bodyLower.includes(word)) score += 1;
|
|
});
|
|
|
|
// Tag match
|
|
if (content.tags) {
|
|
content.tags.forEach(tag => {
|
|
if (tag.toLowerCase().includes(lowerQuery)) score += 4;
|
|
});
|
|
}
|
|
|
|
if (score > 0) {
|
|
results.push({
|
|
id,
|
|
title: content.title,
|
|
excerpt: this.highlight(content.body, query),
|
|
score
|
|
});
|
|
}
|
|
}
|
|
|
|
return results
|
|
.sort((a, b) => b.score - a.score)
|
|
.slice(0, 10);
|
|
}
|
|
|
|
highlight(text, query) {
|
|
const maxLength = 150;
|
|
const lowerText = text.toLowerCase();
|
|
const lowerQuery = query.toLowerCase();
|
|
const index = lowerText.indexOf(lowerQuery);
|
|
|
|
if (index === -1) {
|
|
return text.substring(0, maxLength) + (text.length > maxLength ? '...' : '');
|
|
}
|
|
|
|
const start = Math.max(0, index - 40);
|
|
const end = Math.min(text.length, index + query.length + 80);
|
|
let excerpt = text.substring(start, end);
|
|
|
|
if (start > 0) excerpt = '...' + excerpt;
|
|
if (end < text.length) excerpt += '...';
|
|
|
|
// Highlight matches
|
|
const regex = new RegExp('(' + query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi');
|
|
return excerpt.replace(regex, '<mark>$1</mark>');
|
|
}
|
|
}
|
|
|
|
// Initialize search
|
|
const indexData = JSON.parse(document.getElementById('search-index').textContent);
|
|
const search = new WikiSearch(indexData);
|
|
|
|
const searchInput = document.getElementById('searchInput');
|
|
const searchResults = document.getElementById('searchResults');
|
|
|
|
searchInput.addEventListener('input', function() {
|
|
const query = this.value.trim();
|
|
const results = search.search(query);
|
|
|
|
if (results.length === 0) {
|
|
searchResults.innerHTML = query.length >= 2
|
|
? '<div class="no-results">No results found</div>'
|
|
: '';
|
|
return;
|
|
}
|
|
|
|
searchResults.innerHTML = results.map(r => `
|
|
<a href="#${r.id}" class="search-result-item" data-tiddler="${r.id}">
|
|
<div class="result-title">${r.title}</div>
|
|
<div class="result-excerpt">${r.excerpt}</div>
|
|
</a>
|
|
`).join('');
|
|
});
|
|
|
|
// Clear search on result click
|
|
searchResults.addEventListener('click', function(e) {
|
|
const item = e.target.closest('.search-result-item');
|
|
if (item) {
|
|
searchInput.value = '';
|
|
searchResults.innerHTML = '';
|
|
|
|
// Expand target tiddler
|
|
const tiddlerId = item.dataset.tiddler;
|
|
const tiddler = document.getElementById(tiddlerId);
|
|
if (tiddler) {
|
|
tiddler.classList.remove('collapsed');
|
|
const toggle = tiddler.querySelector('.collapse-toggle');
|
|
if (toggle) toggle.textContent = '▼';
|
|
}
|
|
}
|
|
});
|
|
|
|
// ========== Collapse/Expand ==========
|
|
document.querySelectorAll('.collapse-toggle').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const tiddler = this.closest('.tiddler');
|
|
tiddler.classList.toggle('collapsed');
|
|
this.textContent = tiddler.classList.contains('collapsed') ? '▶' : '▼';
|
|
});
|
|
});
|
|
|
|
// Expand/Collapse All
|
|
document.getElementById('expandAll').addEventListener('click', function() {
|
|
document.querySelectorAll('.tiddler').forEach(t => {
|
|
t.classList.remove('collapsed');
|
|
const toggle = t.querySelector('.collapse-toggle');
|
|
if (toggle) toggle.textContent = '▼';
|
|
});
|
|
});
|
|
|
|
document.getElementById('collapseAll').addEventListener('click', function() {
|
|
document.querySelectorAll('.tiddler').forEach(t => {
|
|
t.classList.add('collapsed');
|
|
const toggle = t.querySelector('.collapse-toggle');
|
|
if (toggle) toggle.textContent = '▶';
|
|
});
|
|
});
|
|
|
|
// ========== Tag Filtering ==========
|
|
document.querySelectorAll('.wiki-tags .tag').forEach(tag => {
|
|
tag.addEventListener('click', function() {
|
|
const filter = this.dataset.tag;
|
|
|
|
// Update active state
|
|
document.querySelectorAll('.wiki-tags .tag').forEach(t => t.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
// Filter tiddlers
|
|
document.querySelectorAll('.tiddler').forEach(tiddler => {
|
|
if (filter === 'all') {
|
|
tiddler.style.display = '';
|
|
} else {
|
|
const tags = tiddler.dataset.tags || '';
|
|
tiddler.style.display = tags.includes(filter) ? '' : 'none';
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// ========== Theme Toggle ==========
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
const savedTheme = localStorage.getItem('wiki-theme');
|
|
|
|
if (savedTheme) {
|
|
document.body.dataset.theme = savedTheme;
|
|
}
|
|
|
|
themeToggle.addEventListener('click', function() {
|
|
const isDark = document.body.dataset.theme === 'dark';
|
|
document.body.dataset.theme = isDark ? 'light' : 'dark';
|
|
localStorage.setItem('wiki-theme', document.body.dataset.theme);
|
|
});
|
|
|
|
// ========== Sidebar Toggle (Mobile) ==========
|
|
document.getElementById('sidebarToggle').addEventListener('click', function() {
|
|
document.querySelector('.wiki-sidebar').classList.toggle('open');
|
|
});
|
|
|
|
// ========== Back to Top ==========
|
|
const backToTop = document.getElementById('backToTop');
|
|
|
|
window.addEventListener('scroll', function() {
|
|
backToTop.classList.toggle('visible', window.scrollY > 300);
|
|
});
|
|
|
|
backToTop.addEventListener('click', function() {
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
});
|
|
|
|
// ========== Print ==========
|
|
document.getElementById('printBtn').addEventListener('click', function() {
|
|
window.print();
|
|
});
|
|
|
|
// ========== TOC Navigation ==========
|
|
document.querySelectorAll('.wiki-toc a').forEach(link => {
|
|
link.addEventListener('click', function(e) {
|
|
const tiddlerId = this.getAttribute('href').substring(1);
|
|
const tiddler = document.getElementById(tiddlerId);
|
|
|
|
if (tiddler) {
|
|
// Expand if collapsed
|
|
tiddler.classList.remove('collapsed');
|
|
const toggle = tiddler.querySelector('.collapse-toggle');
|
|
if (toggle) toggle.textContent = '▼';
|
|
|
|
// Close sidebar on mobile
|
|
document.querySelector('.wiki-sidebar').classList.remove('open');
|
|
}
|
|
});
|
|
});
|
|
|
|
// ========== Code Block Copy ==========
|
|
document.querySelectorAll('pre').forEach(pre => {
|
|
const copyBtn = document.createElement('button');
|
|
copyBtn.className = 'copy-code-btn';
|
|
copyBtn.textContent = 'Copy';
|
|
copyBtn.addEventListener('click', function() {
|
|
const code = pre.querySelector('code');
|
|
navigator.clipboard.writeText(code.textContent).then(() => {
|
|
copyBtn.textContent = 'Copied!';
|
|
setTimeout(() => copyBtn.textContent = 'Copy', 2000);
|
|
});
|
|
});
|
|
pre.appendChild(copyBtn);
|
|
});
|
|
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|