This commit is contained in:
2025-11-16 18:01:30 +01:00
commit 858003cb0b
26 changed files with 4712 additions and 0 deletions

461
static/index.js Normal file
View File

@@ -0,0 +1,461 @@
// Theme management
const themeToggle = document.getElementById('themeToggle');
const html = document.documentElement;
// Load saved theme
const savedTheme = localStorage.getItem('theme') || 'light';
html.setAttribute('data-theme', savedTheme);
themeToggle.addEventListener('click', () => {
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
// Sidebar reference (no collapse functionality)
const sidebar = document.getElementById('sidebar');
// Sidebar resizing
const resizeHandle = document.getElementById('resizeHandle');
let isResizing = false;
let startX = 0;
let startWidth = 0;
resizeHandle.addEventListener('mousedown', (e) => {
isResizing = true;
startX = e.clientX;
startWidth = parseInt(window.getComputedStyle(sidebar).width, 10);
document.addEventListener('mousemove', handleResize);
document.addEventListener('mouseup', stopResize);
e.preventDefault();
});
function handleResize(e) {
if (!isResizing) return;
const width = startWidth + e.clientX - startX;
const minWidth = 200;
const maxWidth = 600;
if (width >= minWidth && width <= maxWidth) {
sidebar.style.width = `${width}px`;
document.documentElement.style.setProperty('--sidebar-width', `${width}px`);
}
}
function stopResize() {
isResizing = false;
document.removeEventListener('mousemove', handleResize);
document.removeEventListener('mouseup', stopResize);
}
// Tab switching
const navTabs = document.querySelectorAll('.nav-tab');
const docsTab = document.getElementById('docsTab');
const courseTab = document.getElementById('courseTab');
navTabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabName = tab.dataset.tab;
// Update active tab
navTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Show/hide tab content
if (tabName === 'docs') {
docsTab.style.display = 'block';
courseTab.style.display = 'none';
} else {
docsTab.style.display = 'none';
courseTab.style.display = 'block';
loadCourseContent();
}
});
});
// API interaction
const objectInput = document.getElementById('objectInput');
const langSelect = document.getElementById('langSelect');
const searchBtn = document.getElementById('searchBtn');
const resultsContainer = document.getElementById('results');
const moduleListContainer = document.getElementById('moduleList');
const courseListContainer = document.getElementById('courseList');
// Current state
let currentObject = '';
let currentLang = '';
// Load module list
async function loadModuleList() {
try {
const response = await fetch('/modules');
const data = await response.json();
const list = document.createElement('div');
list.className = 'module-items';
// Add builtins section
if (data.builtins && data.builtins.length > 0) {
const builtinsSection = document.createElement('div');
builtinsSection.className = 'module-section';
const builtinsTitle = document.createElement('h4');
builtinsTitle.textContent = 'Builtins';
builtinsTitle.className = 'module-section-title';
builtinsSection.appendChild(builtinsTitle);
const builtinsList = document.createElement('div');
builtinsList.className = 'module-items-list';
data.builtins.slice(0, 20).forEach(item => {
const btn = createModuleButton(item.full_name || `builtins.${item.name}`, item.name);
builtinsList.appendChild(btn);
});
builtinsSection.appendChild(builtinsList);
list.appendChild(builtinsSection);
}
// Add modules section
if (data.modules && data.modules.length > 0) {
const modulesSection = document.createElement('div');
modulesSection.className = 'module-section';
const modulesTitle = document.createElement('h4');
modulesTitle.textContent = 'Standard Library';
modulesTitle.className = 'module-section-title';
modulesSection.appendChild(modulesTitle);
const modulesList = document.createElement('div');
modulesList.className = 'module-items-list';
data.modules.forEach(item => {
const btn = createModuleButton(item.name, item.name);
modulesList.appendChild(btn);
});
modulesSection.appendChild(modulesList);
list.appendChild(modulesSection);
}
moduleListContainer.innerHTML = '';
moduleListContainer.appendChild(list);
} catch (error) {
moduleListContainer.innerHTML = `<div class="error-message">Error loading modules: ${error.message}</div>`;
}
}
function createModuleButton(fullName, displayName) {
const btn = document.createElement('button');
btn.className = 'module-item';
btn.textContent = displayName;
btn.title = fullName;
btn.addEventListener('click', () => {
objectInput.value = fullName;
currentObject = fullName;
fetchDocumentation();
});
return btn;
}
// Load modules on page load
loadModuleList();
// Auto-translate when language changes
langSelect.addEventListener('change', () => {
if (currentObject) {
currentLang = langSelect.value;
fetchDocumentation();
}
});
async function fetchDocumentation() {
const objectName = objectInput.value.trim();
const targetLang = langSelect.value;
if (!objectName) {
resultsContainer.innerHTML = '<div class="error-message">Please enter a Python object name.</div>';
return;
}
currentObject = objectName;
currentLang = targetLang;
searchBtn.disabled = true;
searchBtn.textContent = 'Loading...';
resultsContainer.innerHTML = '<div class="loading">Loading documentation...</div>';
try {
const params = new URLSearchParams({ object: objectName });
if (targetLang) {
params.append('lang', targetLang);
}
const response = await fetch(`/docs?${params}`);
const data = await response.json();
if (data.error) {
resultsContainer.innerHTML = `<div class="error-message">Error: ${data.error}</div>`;
return;
}
displayResults(data, targetLang);
} catch (error) {
resultsContainer.innerHTML = `<div class="error-message">Network error: ${error.message}</div>`;
} finally {
searchBtn.disabled = false;
searchBtn.textContent = 'Get Documentation';
}
}
function displayResults(data, targetLang) {
// Python docs style - no card, clean layout
const wrapper = document.createElement('div');
// Header with title
const header = document.createElement('div');
header.className = 'doc-header';
const title = document.createElement('h1');
title.className = 'doc-title';
title.textContent = data.object_name;
const meta = document.createElement('div');
meta.className = 'doc-meta';
meta.innerHTML = `
<span class="type-badge">${data.object_type || 'unknown'}</span>
${data.cached ? '<span class="doc-badge">Cached</span>' : ''}
`;
header.appendChild(title);
header.appendChild(meta);
wrapper.appendChild(header);
// Signature
if (data.signature) {
const signature = document.createElement('div');
signature.className = 'doc-signature';
signature.textContent = data.signature;
wrapper.appendChild(signature);
}
// Main documentation content
const docText = data.translated || data.original;
if (docText) {
const docSection = document.createElement('section');
docSection.className = 'doc-section';
const docTextEl = document.createElement('div');
docTextEl.className = 'doc-text';
docTextEl.textContent = docText;
docSection.appendChild(docTextEl);
wrapper.appendChild(docSection);
}
// Show original if translation exists (collapsible)
if (data.translated && data.original) {
const originalSection = document.createElement('details');
originalSection.className = 'doc-section';
originalSection.innerHTML = `
<summary style="cursor: pointer; font-weight: 500; margin-bottom: 0.5rem; color: var(--text-secondary);">
Original Documentation (English)
</summary>
<div class="doc-text" style="margin-top: 0.5rem;">${data.original}</div>
`;
wrapper.appendChild(originalSection);
}
if (!data.original && !data.translated) {
const noDoc = document.createElement('div');
noDoc.className = 'doc-text';
noDoc.textContent = 'No documentation available for this object.';
wrapper.appendChild(noDoc);
}
resultsContainer.innerHTML = '';
resultsContainer.appendChild(wrapper);
}
// Load course content
async function loadCourseContent() {
if (courseListContainer.querySelector('.course-loaded')) {
return; // Already loaded
}
try {
const response = await fetch('/course');
const data = await response.json();
const list = document.createElement('div');
list.className = 'module-items';
list.classList.add('course-loaded');
if (data.sections && data.sections.length > 0) {
data.sections.forEach(section => {
const sectionDiv = document.createElement('div');
sectionDiv.className = 'module-section';
const sectionTitle = document.createElement('h4');
sectionTitle.textContent = section.title;
sectionTitle.className = 'module-section-title';
sectionDiv.appendChild(sectionTitle);
const itemsList = document.createElement('div');
itemsList.className = 'module-items-list';
// Create clickable navigation item
const navBtn = document.createElement('button');
navBtn.className = 'module-item';
navBtn.textContent = section.title;
navBtn.addEventListener('click', () => {
// Scroll to section in main content
const sectionId = section.id || section.title.toLowerCase().replace(/\s+/g, '-');
const sectionElement = document.getElementById(`section-${sectionId}`);
if (sectionElement) {
sectionElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else {
// Try to find by heading ID
const headingId = section.title.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
const headingElement = document.getElementById(headingId);
if (headingElement) {
headingElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
});
itemsList.appendChild(navBtn);
// Add subsections if any
if (section.subsections && section.subsections.length > 0) {
section.subsections.forEach(subsection => {
const subBtn = document.createElement('button');
subBtn.className = 'module-item';
subBtn.style.paddingLeft = '1.5rem';
subBtn.textContent = subsection.title;
subBtn.addEventListener('click', () => {
const subId = subsection.id || subsection.title.toLowerCase().replace(/\s+/g, '-');
const subElement = document.getElementById(`subsection-${subId}`);
if (subElement) {
subElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
itemsList.appendChild(subBtn);
});
}
sectionDiv.appendChild(itemsList);
list.appendChild(sectionDiv);
});
}
courseListContainer.innerHTML = '';
courseListContainer.appendChild(list);
// Also display course content in main area
displayCourseContent(data);
} catch (error) {
courseListContainer.innerHTML = `<div class="error-message">Error loading course: ${error.message}</div>`;
}
}
function displayCourseContent(courseData) {
const wrapper = document.createElement('div');
wrapper.className = 'course-content';
const title = document.createElement('h1');
title.textContent = courseData.title || 'Python Course';
wrapper.appendChild(title);
if (courseData.sections && courseData.sections.length > 0) {
courseData.sections.forEach(section => {
const sectionDiv = document.createElement('section');
sectionDiv.className = 'course-section';
sectionDiv.id = `section-${section.id || section.title.toLowerCase().replace(/\s+/g, '-')}`;
// Parse and render markdown
if (section.markdown) {
// Configure marked options
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(code, { language: lang }).value;
} catch (err) {
console.error('Highlight error:', err);
}
}
return hljs.highlightAuto(code).value;
},
breaks: true,
gfm: true
});
// Convert markdown to HTML
const htmlContent = marked.parse(section.markdown);
// Create a container for the markdown content
const contentDiv = document.createElement('div');
contentDiv.className = 'markdown-content';
contentDiv.innerHTML = htmlContent;
// Add IDs to headings for navigation
contentDiv.querySelectorAll('h1, h2, h3, h4').forEach((heading) => {
const text = heading.textContent.trim();
const id = text.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
heading.id = id;
});
// Highlight code blocks
contentDiv.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
sectionDiv.appendChild(contentDiv);
} else if (section.content && section.content.length > 0) {
// Fallback to old format
section.content.forEach(item => {
if (item.startsWith('```')) {
const codeDiv = document.createElement('pre');
codeDiv.className = 'doc-signature';
codeDiv.textContent = item.replace(/```python\n?/g, '').replace(/```/g, '').trim();
sectionDiv.appendChild(codeDiv);
} else {
const itemDiv = document.createElement('p');
itemDiv.className = 'doc-text';
itemDiv.textContent = item;
sectionDiv.appendChild(itemDiv);
}
});
}
wrapper.appendChild(sectionDiv);
});
}
resultsContainer.innerHTML = '';
resultsContainer.appendChild(wrapper);
}
function getLanguageName(langCode) {
const langMap = {
'de': 'German',
'fr': 'French',
'es': 'Spanish',
'it': 'Italian',
'pt': 'Portuguese',
'ru': 'Russian'
};
return langMap[langCode] || langCode;
}
searchBtn.addEventListener('click', fetchDocumentation);
objectInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
fetchDocumentation();
}
});