feat: add issue discovery view for managing discovery sessions and findings

- Implemented main render function for the issue discovery view.
- Added data loading functions to fetch discoveries, details, findings, and progress.
- Created rendering functions for discovery list and detail sections.
- Introduced filtering and searching capabilities for findings.
- Implemented actions for exporting and dismissing findings.
- Added polling mechanism to track discovery progress.
- Included utility functions for HTML escaping and cleanup.
This commit is contained in:
catlog22
2025-12-28 17:21:07 +08:00
parent a2c88ba885
commit 7aa1cda367
28 changed files with 7730 additions and 2 deletions

View File

@@ -0,0 +1,695 @@
/* ========================================
TiddlyWiki-Style Base CSS
Software Manual Skill
======================================== */
/* ========== CSS Variables ========== */
:root {
/* Light Theme */
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #e9ecef;
--text-primary: #212529;
--text-secondary: #495057;
--text-muted: #6c757d;
--border-color: #dee2e6;
--accent-color: #0d6efd;
--accent-hover: #0b5ed7;
--success-color: #198754;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #0dcaf0;
/* Layout */
--sidebar-width: 280px;
--header-height: 60px;
--content-max-width: 900px;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* Typography */
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-family-mono: 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--line-height: 1.6;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
/* Transitions */
--transition-fast: 150ms ease;
--transition-base: 300ms ease;
}
/* ========== Reset & Base ========== */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-family);
font-size: var(--font-size-base);
line-height: var(--line-height);
color: var(--text-primary);
background-color: var(--bg-secondary);
}
/* ========== Layout ========== */
.wiki-container {
display: flex;
min-height: 100vh;
}
/* ========== Sidebar ========== */
.wiki-sidebar {
position: fixed;
top: 0;
left: 0;
width: var(--sidebar-width);
height: 100vh;
background-color: var(--bg-primary);
border-right: 1px solid var(--border-color);
overflow-y: auto;
z-index: 100;
display: flex;
flex-direction: column;
transition: transform var(--transition-base);
}
/* Logo Area */
.wiki-logo {
padding: var(--spacing-lg);
text-align: center;
border-bottom: 1px solid var(--border-color);
}
.wiki-logo .logo-placeholder {
width: 60px;
height: 60px;
margin: 0 auto var(--spacing-sm);
background: linear-gradient(135deg, var(--accent-color), var(--info-color));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: var(--font-size-xl);
}
.wiki-logo h1 {
font-size: var(--font-size-lg);
font-weight: 600;
margin-bottom: var(--spacing-xs);
}
.wiki-logo .version {
font-size: var(--font-size-sm);
color: var(--text-muted);
}
/* Search */
.wiki-search {
padding: var(--spacing-md);
position: relative;
}
.wiki-search input {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: var(--font-size-sm);
background-color: var(--bg-secondary);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.wiki-search input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
}
.search-results {
position: absolute;
top: 100%;
left: var(--spacing-md);
right: var(--spacing-md);
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
box-shadow: var(--shadow-lg);
max-height: 400px;
overflow-y: auto;
z-index: 200;
}
.search-result-item {
display: block;
padding: var(--spacing-sm) var(--spacing-md);
text-decoration: none;
color: var(--text-primary);
border-bottom: 1px solid var(--border-color);
transition: background-color var(--transition-fast);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-item:hover {
background-color: var(--bg-secondary);
}
.result-title {
font-weight: 600;
margin-bottom: var(--spacing-xs);
}
.result-excerpt {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.result-excerpt mark {
background-color: var(--warning-color);
padding: 0 2px;
border-radius: 2px;
}
.no-results {
padding: var(--spacing-md);
text-align: center;
color: var(--text-muted);
}
/* Tags */
.wiki-tags {
padding: var(--spacing-md);
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
border-bottom: 1px solid var(--border-color);
}
.wiki-tags .tag {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-sm);
border: 1px solid var(--border-color);
border-radius: 20px;
background: var(--bg-secondary);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
.wiki-tags .tag:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
.wiki-tags .tag.active {
background-color: var(--accent-color);
border-color: var(--accent-color);
color: white;
}
/* Table of Contents */
.wiki-toc {
flex: 1;
padding: var(--spacing-md);
overflow-y: auto;
}
.wiki-toc h3 {
font-size: var(--font-size-sm);
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
margin-bottom: var(--spacing-md);
}
.wiki-toc ul {
list-style: none;
}
.wiki-toc li {
margin-bottom: var(--spacing-xs);
}
.wiki-toc a {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-sm);
color: var(--text-secondary);
text-decoration: none;
border-radius: 6px;
font-size: var(--font-size-sm);
transition: all var(--transition-fast);
}
.wiki-toc a:hover {
background-color: var(--bg-secondary);
color: var(--accent-color);
}
/* ========== Main Content ========== */
.wiki-content {
flex: 1;
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
.content-header {
position: sticky;
top: 0;
background-color: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-sm) var(--spacing-lg);
display: flex;
align-items: center;
justify-content: space-between;
z-index: 50;
}
.sidebar-toggle {
display: none;
flex-direction: column;
gap: 4px;
padding: var(--spacing-sm);
background: none;
border: none;
cursor: pointer;
}
.sidebar-toggle span {
display: block;
width: 20px;
height: 2px;
background-color: var(--text-primary);
transition: transform var(--transition-fast);
}
.header-actions {
display: flex;
gap: var(--spacing-sm);
}
.header-actions button {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-sm);
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-primary);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
.header-actions button:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
/* Tiddler Container */
.tiddler-container {
flex: 1;
max-width: var(--content-max-width);
margin: 0 auto;
padding: var(--spacing-lg);
width: 100%;
}
/* ========== Tiddler (Content Block) ========== */
.tiddler {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
margin-bottom: var(--spacing-lg);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--transition-fast);
}
.tiddler:hover {
box-shadow: var(--shadow-md);
}
.tiddler-header {
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.tiddler-title {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-xl);
font-weight: 600;
margin: 0;
}
.collapse-toggle {
background: none;
border: none;
font-size: var(--font-size-sm);
color: var(--text-muted);
cursor: pointer;
padding: var(--spacing-xs);
transition: transform var(--transition-fast);
}
.tiddler.collapsed .collapse-toggle {
transform: rotate(-90deg);
}
.tiddler-meta {
display: flex;
gap: var(--spacing-sm);
flex-wrap: wrap;
}
.difficulty-badge {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.75rem;
font-weight: 500;
border-radius: 4px;
text-transform: uppercase;
}
.difficulty-badge.beginner {
background-color: #d1fae5;
color: #065f46;
}
.difficulty-badge.intermediate {
background-color: #fef3c7;
color: #92400e;
}
.difficulty-badge.advanced {
background-color: #fee2e2;
color: #991b1b;
}
.tag-badge {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.75rem;
background-color: var(--bg-tertiary);
color: var(--text-secondary);
border-radius: 4px;
}
.tiddler-content {
padding: var(--spacing-lg);
}
.tiddler.collapsed .tiddler-content {
display: none;
}
/* ========== Content Typography ========== */
.tiddler-content h1,
.tiddler-content h2,
.tiddler-content h3,
.tiddler-content h4 {
margin-top: var(--spacing-lg);
margin-bottom: var(--spacing-md);
font-weight: 600;
}
.tiddler-content h1 { font-size: 1.75rem; }
.tiddler-content h2 { font-size: 1.5rem; }
.tiddler-content h3 { font-size: 1.25rem; }
.tiddler-content h4 { font-size: 1.125rem; }
.tiddler-content p {
margin-bottom: var(--spacing-md);
}
.tiddler-content ul,
.tiddler-content ol {
margin-bottom: var(--spacing-md);
padding-left: var(--spacing-lg);
}
.tiddler-content li {
margin-bottom: var(--spacing-xs);
}
.tiddler-content a {
color: var(--accent-color);
text-decoration: none;
}
.tiddler-content a:hover {
text-decoration: underline;
}
/* Code */
.tiddler-content code {
font-family: var(--font-family-mono);
font-size: 0.9em;
padding: 2px 6px;
background-color: var(--bg-tertiary);
border-radius: 4px;
}
.tiddler-content pre {
position: relative;
margin-bottom: var(--spacing-md);
padding: var(--spacing-md);
background-color: #1e1e1e;
border-radius: 8px;
overflow-x: auto;
}
.tiddler-content pre code {
padding: 0;
background: none;
color: #d4d4d4;
font-size: var(--font-size-sm);
}
.copy-code-btn {
position: absolute;
top: var(--spacing-sm);
right: var(--spacing-sm);
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.75rem;
background-color: var(--bg-tertiary);
border: none;
border-radius: 4px;
cursor: pointer;
opacity: 0;
transition: opacity var(--transition-fast);
}
.tiddler-content pre:hover .copy-code-btn {
opacity: 1;
}
/* Tables */
.tiddler-content table {
width: 100%;
margin-bottom: var(--spacing-md);
border-collapse: collapse;
}
.tiddler-content th,
.tiddler-content td {
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--border-color);
text-align: left;
}
.tiddler-content th {
background-color: var(--bg-secondary);
font-weight: 600;
}
.tiddler-content tr:nth-child(even) {
background-color: var(--bg-secondary);
}
/* Screenshots */
.screenshot {
margin: var(--spacing-lg) 0;
text-align: center;
}
.screenshot img {
max-width: 100%;
border: 1px solid var(--border-color);
border-radius: 8px;
box-shadow: var(--shadow-md);
}
.screenshot figcaption {
margin-top: var(--spacing-sm);
font-size: var(--font-size-sm);
color: var(--text-muted);
font-style: italic;
}
.screenshot-placeholder {
padding: var(--spacing-xl);
background-color: var(--bg-tertiary);
border: 2px dashed var(--border-color);
border-radius: 8px;
color: var(--text-muted);
text-align: center;
}
/* ========== Footer ========== */
.wiki-footer {
padding: var(--spacing-lg);
text-align: center;
color: var(--text-muted);
font-size: var(--font-size-sm);
border-top: 1px solid var(--border-color);
background-color: var(--bg-primary);
}
/* ========== Theme Toggle ========== */
.theme-toggle {
position: fixed;
bottom: var(--spacing-lg);
right: var(--spacing-lg);
width: 48px;
height: 48px;
border-radius: 50%;
border: none;
background-color: var(--bg-primary);
box-shadow: var(--shadow-lg);
cursor: pointer;
font-size: 1.5rem;
z-index: 100;
transition: transform var(--transition-fast);
}
.theme-toggle:hover {
transform: scale(1.1);
}
[data-theme="light"] .moon-icon { display: inline; }
[data-theme="light"] .sun-icon { display: none; }
[data-theme="dark"] .moon-icon { display: none; }
[data-theme="dark"] .sun-icon { display: inline; }
/* ========== Back to Top ========== */
.back-to-top {
position: fixed;
bottom: calc(var(--spacing-lg) + 60px);
right: var(--spacing-lg);
width: 40px;
height: 40px;
border-radius: 50%;
border: none;
background-color: var(--accent-color);
color: white;
font-size: 1.25rem;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: all var(--transition-fast);
z-index: 100;
}
.back-to-top.visible {
opacity: 1;
visibility: visible;
}
.back-to-top:hover {
background-color: var(--accent-hover);
}
/* ========== Responsive ========== */
@media (max-width: 1024px) {
.wiki-sidebar {
transform: translateX(-100%);
}
.wiki-sidebar.open {
transform: translateX(0);
}
.wiki-content {
margin-left: 0;
}
.sidebar-toggle {
display: flex;
}
}
@media (max-width: 640px) {
.tiddler-header {
flex-direction: column;
align-items: flex-start;
}
.header-actions {
display: none;
}
.wiki-tags {
overflow-x: auto;
flex-wrap: nowrap;
padding-bottom: var(--spacing-md);
}
}
/* ========== Print Styles ========== */
@media print {
.wiki-sidebar,
.theme-toggle,
.back-to-top,
.content-header,
.collapse-toggle,
.copy-code-btn {
display: none !important;
}
.wiki-content {
margin-left: 0;
}
.tiddler {
break-inside: avoid;
box-shadow: none;
border: 1px solid #ccc;
}
.tiddler.collapsed .tiddler-content {
display: block;
}
.tiddler-content pre {
background-color: #f5f5f5 !important;
color: #333 !important;
}
}

View File

@@ -0,0 +1,278 @@
/* ========================================
TiddlyWiki-Style Dark Theme
Software Manual Skill
======================================== */
[data-theme="dark"] {
/* Dark Theme Colors */
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-tertiary: #0f3460;
--text-primary: #eaeaea;
--text-secondary: #b8b8b8;
--text-muted: #888888;
--border-color: #2d3748;
--accent-color: #4dabf7;
--accent-hover: #339af0;
--success-color: #51cf66;
--warning-color: #ffd43b;
--danger-color: #ff6b6b;
--info-color: #22b8cf;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5);
}
/* Dark theme specific overrides */
[data-theme="dark"] .wiki-logo .logo-placeholder {
background: linear-gradient(135deg, var(--accent-color), #6741d9);
}
[data-theme="dark"] .wiki-search input {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-primary);
}
[data-theme="dark"] .wiki-search input::placeholder {
color: var(--text-muted);
}
[data-theme="dark"] .search-results {
background-color: var(--bg-secondary);
border-color: var(--border-color);
}
[data-theme="dark"] .search-result-item {
border-color: var(--border-color);
}
[data-theme="dark"] .search-result-item:hover {
background-color: var(--bg-tertiary);
}
[data-theme="dark"] .result-excerpt mark {
background-color: rgba(255, 212, 59, 0.3);
color: var(--warning-color);
}
[data-theme="dark"] .wiki-tags .tag {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-secondary);
}
[data-theme="dark"] .wiki-tags .tag:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
[data-theme="dark"] .wiki-tags .tag.active {
background-color: var(--accent-color);
border-color: var(--accent-color);
color: #1a1a2e;
}
[data-theme="dark"] .wiki-toc a:hover {
background-color: var(--bg-tertiary);
}
[data-theme="dark"] .content-header {
background-color: var(--bg-primary);
border-color: var(--border-color);
}
[data-theme="dark"] .sidebar-toggle span {
background-color: var(--text-primary);
}
[data-theme="dark"] .header-actions button {
background-color: var(--bg-secondary);
border-color: var(--border-color);
color: var(--text-secondary);
}
[data-theme="dark"] .header-actions button:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
[data-theme="dark"] .tiddler {
background-color: var(--bg-primary);
border-color: var(--border-color);
}
[data-theme="dark"] .tiddler-header {
border-color: var(--border-color);
}
[data-theme="dark"] .difficulty-badge.beginner {
background-color: rgba(81, 207, 102, 0.2);
color: var(--success-color);
}
[data-theme="dark"] .difficulty-badge.intermediate {
background-color: rgba(255, 212, 59, 0.2);
color: var(--warning-color);
}
[data-theme="dark"] .difficulty-badge.advanced {
background-color: rgba(255, 107, 107, 0.2);
color: var(--danger-color);
}
[data-theme="dark"] .tag-badge {
background-color: var(--bg-tertiary);
color: var(--text-secondary);
}
[data-theme="dark"] .tiddler-content code {
background-color: var(--bg-tertiary);
color: var(--accent-color);
}
[data-theme="dark"] .tiddler-content pre {
background-color: #0d1117;
border: 1px solid var(--border-color);
}
[data-theme="dark"] .tiddler-content pre code {
color: #e6e6e6;
}
[data-theme="dark"] .copy-code-btn {
background-color: var(--bg-tertiary);
color: var(--text-secondary);
}
[data-theme="dark"] .tiddler-content th {
background-color: var(--bg-tertiary);
}
[data-theme="dark"] .tiddler-content tr:nth-child(even) {
background-color: var(--bg-secondary);
}
[data-theme="dark"] .tiddler-content th,
[data-theme="dark"] .tiddler-content td {
border-color: var(--border-color);
}
[data-theme="dark"] .screenshot img {
border-color: var(--border-color);
}
[data-theme="dark"] .screenshot-placeholder {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
}
[data-theme="dark"] .wiki-footer {
background-color: var(--bg-primary);
border-color: var(--border-color);
}
[data-theme="dark"] .theme-toggle {
background-color: var(--bg-secondary);
color: var(--warning-color);
}
[data-theme="dark"] .back-to-top {
background-color: var(--accent-color);
}
[data-theme="dark"] .back-to-top:hover {
background-color: var(--accent-hover);
}
/* Scrollbar styling for dark theme */
[data-theme="dark"] ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
[data-theme="dark"] ::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
[data-theme="dark"] ::-webkit-scrollbar-thumb {
background: var(--bg-tertiary);
border-radius: 4px;
}
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
background: var(--border-color);
}
/* Selection color */
[data-theme="dark"] ::selection {
background-color: rgba(77, 171, 247, 0.3);
color: var(--text-primary);
}
/* Focus styles for accessibility */
[data-theme="dark"] :focus {
outline-color: var(--accent-color);
}
[data-theme="dark"] .wiki-search input:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(77, 171, 247, 0.2);
}
/* Link colors */
[data-theme="dark"] .tiddler-content a {
color: var(--accent-color);
}
[data-theme="dark"] .tiddler-content a:hover {
color: var(--accent-hover);
}
/* Blockquote styling */
[data-theme="dark"] .tiddler-content blockquote {
border-left: 4px solid var(--accent-color);
background-color: var(--bg-tertiary);
padding: var(--spacing-md);
margin: var(--spacing-md) 0;
color: var(--text-secondary);
}
/* Horizontal rule */
[data-theme="dark"] .tiddler-content hr {
border: none;
border-top: 1px solid var(--border-color);
margin: var(--spacing-lg) 0;
}
/* Alert/Note boxes */
[data-theme="dark"] .note,
[data-theme="dark"] .warning,
[data-theme="dark"] .tip,
[data-theme="dark"] .danger {
padding: var(--spacing-md);
border-radius: 6px;
margin: var(--spacing-md) 0;
}
[data-theme="dark"] .note {
background-color: rgba(34, 184, 207, 0.1);
border-left: 4px solid var(--info-color);
}
[data-theme="dark"] .warning {
background-color: rgba(255, 212, 59, 0.1);
border-left: 4px solid var(--warning-color);
}
[data-theme="dark"] .tip {
background-color: rgba(81, 207, 102, 0.1);
border-left: 4px solid var(--success-color);
}
[data-theme="dark"] .danger {
background-color: rgba(255, 107, 107, 0.1);
border-left: 4px solid var(--danger-color);
}

View File

@@ -0,0 +1,332 @@
<!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 -->
<nav class="wiki-tags" aria-label="Filter by category">
<button class="tag active" data-tag="all">All</button>
<button class="tag" data-tag="getting-started">Getting Started</button>
<button class="tag" data-tag="ui-guide">UI Guide</button>
<button class="tag" data-tag="api">API</button>
<button class="tag" data-tag="config">Configuration</button>
<button class="tag" data-tag="troubleshooting">Troubleshooting</button>
<button class="tag" data-tag="examples">Examples</button>
</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">&#9728;</span>
<span class="moon-icon">&#9790;</span>
</button>
<!-- Back to Top Button -->
<button class="back-to-top" id="backToTop" aria-label="Back to top">&#8593;</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>