initial
This commit is contained in:
461
static/index.js
Normal file
461
static/index.js
Normal 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();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user