Flatlogic Bot 02a6976a98 CodeAtlas
2026-06-27 08:14:56 +00:00

263 lines
11 KiB
JavaScript

const lessons = [
{
id: 'go-json-parse',
title: 'Parse JSON into a Go struct',
language: 'Go',
tags: ['json', 'struct', 'api'],
excerpt: 'Use encoding/json with exported struct fields and explicit error handling.',
summary: 'Go parses JSON by unmarshalling bytes into a struct. Field names must be exported, and struct tags let you map JSON keys to Go names.',
code: `type Payload struct {\n Name string \`json:"name"\`\n}\n\nvar p Payload\nif err := json.Unmarshal(body, &p); err != nil {\n return err\n}`
},
{
id: 'js-async-await',
title: 'Use async/await for readable JavaScript APIs',
language: 'JavaScript',
tags: ['async', 'fetch', 'promises'],
excerpt: 'Wrap fetch calls in async functions, await the response, then handle errors explicitly.',
summary: 'async/await keeps asynchronous JavaScript code close to synchronous control flow. Always check response.ok and catch failures near the calling boundary.',
code: `async function loadUser(id) {\n const res = await fetch(\`/api/users/\${id}\`);\n if (!res.ok) throw new Error('Request failed');\n return await res.json();\n}`
},
{
id: 'python-list-comprehension',
title: 'Filter and transform Python lists',
language: 'Python',
tags: ['lists', 'comprehension', 'clean code'],
excerpt: 'List comprehensions are concise when the expression and predicate stay simple.',
summary: 'Use a list comprehension when you can express mapping and filtering in one readable line. Prefer a normal loop when you need multiple steps or side effects.',
code: `names = ['Ada', 'Linus', 'Grace']\nshort_names = [name.lower() for name in names if len(name) <= 5]`
},
{
id: 'sql-joins',
title: 'Choose the right SQL join',
language: 'SQL',
tags: ['joins', 'select', 'relational'],
excerpt: 'INNER JOIN returns matching rows; LEFT JOIN keeps all rows from the left table.',
summary: 'Start with INNER JOIN when both records must exist. Use LEFT JOIN for optional related records, such as users without orders yet.',
code: `SELECT users.name, orders.total\nFROM users\nLEFT JOIN orders ON orders.user_id = users.id\nWHERE users.active = 1;`
},
{
id: 'php-pdo-prepared',
title: 'Run safe PHP PDO queries',
language: 'PHP',
tags: ['pdo', 'security', 'mysql'],
excerpt: 'Prepared statements prevent SQL injection and keep user input out of raw SQL strings.',
summary: 'Create a PDO statement, bind values, execute it, and fetch typed results. Never concatenate untrusted request input into SQL.',
code: `$stmt = $pdo->prepare('SELECT * FROM posts WHERE slug = :slug');\n$stmt->execute(['slug' => $slug]);\n$post = $stmt->fetch(PDO::FETCH_ASSOC);`
},
{
id: 'ts-generics',
title: 'Model reusable TypeScript functions with generics',
language: 'TypeScript',
tags: ['types', 'generics', 'safety'],
excerpt: 'Generics preserve specific types while keeping helpers reusable.',
summary: 'Use a generic type parameter when the function should work with many input types but return or store the same specific type information.',
code: `function first<T>(items: T[]): T | undefined {\n return items[0];\n}\n\nconst value = first<string>(['docs', 'code']);`
}
];
const els = {};
let activeResults = [];
let selectedId = null;
let toastInstance = null;
function $(selector) { return document.querySelector(selector); }
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, (char) => ({'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;'}[char]));
}
function scoreLesson(lesson, query, language) {
const q = query.trim().toLowerCase();
const haystack = `${lesson.title} ${lesson.language} ${lesson.tags.join(' ')} ${lesson.excerpt} ${lesson.summary}`.toLowerCase();
let score = 0;
if (language === 'all' || lesson.language === language) score += 25;
if (language !== 'all' && lesson.language !== language) return 0;
if (!q) return score;
const terms = q.split(/\s+/).filter(Boolean);
terms.forEach((term) => {
if (lesson.title.toLowerCase().includes(term)) score += 18;
if (lesson.tags.join(' ').toLowerCase().includes(term)) score += 12;
if (haystack.includes(term)) score += 6;
});
return score;
}
function searchLessons(query, language) {
return lessons
.map((lesson) => ({ ...lesson, score: scoreLesson(lesson, query, language) }))
.filter((lesson) => lesson.score > 0)
.sort((a, b) => b.score - a.score || a.title.localeCompare(b.title));
}
function renderResults(results) {
activeResults = results;
els.resultCount.textContent = results.length ? `${results.length} result${results.length === 1 ? '' : 's'} ranked by relevance.` : 'No matches yet. Try another language or broader topic.';
if (!results.length) {
const hasQuery = els.query && els.query.value.trim().length >= 2;
els.resultsList.innerHTML = hasQuery
? `<div class="empty-state py-5"><div class="empty-icon" aria-hidden="true">?</div><h2 class="h5">No results found</h2><p class="text-secondary mb-0">Try “JSON”, “async”, “join”, or choose All languages.</p></div>`
: `<div class="empty-state py-5"><div class="empty-icon" aria-hidden="true">/</div><h2 class="h5">Start with a query</h2><p class="text-secondary mb-0">Search a topic or choose a suggested chip to preview the knowledge workflow.</p></div>`;
renderEmptyDetail();
return;
}
els.resultsList.innerHTML = results.map((lesson, index) => `
<button class="result-item ${lesson.id === selectedId ? 'active' : ''}" type="button" data-id="${escapeHtml(lesson.id)}" aria-label="Open ${escapeHtml(lesson.title)}">
<div class="d-flex justify-content-between gap-3">
<div class="result-title">${escapeHtml(lesson.title)}</div>
<div class="score">#${index + 1}</div>
</div>
<p class="result-excerpt">${escapeHtml(lesson.excerpt)}</p>
<div>${[lesson.language, ...lesson.tags].map(tag => `<span class="badge-soft">${escapeHtml(tag)}</span>`).join('')}</div>
</button>
`).join('');
}
function renderEmptyDetail() {
selectedId = null;
els.detail.innerHTML = `<div class="empty-state"><div class="empty-icon" aria-hidden="true">⌘</div><h2 class="h4">Choose a result</h2><p class="text-secondary mb-0">Open a result to see a concise explanation, tags, and copy-ready code examples.</p></div>`;
}
function renderDetail(id) {
const lesson = lessons.find(item => item.id === id);
if (!lesson) return renderEmptyDetail();
selectedId = id;
els.detail.innerHTML = `
<div class="detail-meta">
<span class="badge-soft">${escapeHtml(lesson.language)}</span>
${lesson.tags.map(tag => `<span class="badge-soft">${escapeHtml(tag)}</span>`).join('')}
</div>
<h2 class="h3 mb-3">${escapeHtml(lesson.title)}</h2>
<p class="detail-summary">${escapeHtml(lesson.summary)}</p>
<h3 class="h6 mt-4">Code example</h3>
<pre class="code-block"><code>${escapeHtml(lesson.code)}</code></pre>
<div class="detail-actions">
<button type="button" class="btn btn-dark btn-sm" data-copy-code="${escapeHtml(lesson.id)}">Copy code</button>
<button type="button" class="btn btn-outline-secondary btn-sm" data-share-result="${escapeHtml(lesson.id)}">Share result</button>
</div>
`;
renderResults(activeResults);
updateUrl({ result: id });
}
function updateUrl(extra = {}) {
const params = new URLSearchParams(window.location.search);
const query = els.query.value.trim();
const language = els.language.value;
if (query) params.set('q', query); else params.delete('q');
if (language && language !== 'all') params.set('language', language); else params.delete('language');
if (extra.result) params.set('result', extra.result);
const next = `${window.location.pathname}${params.toString() ? `?${params}` : ''}#search`;
history.replaceState({}, '', next);
}
function showToast(message) {
els.toastMessage.textContent = message;
if (window.bootstrap && toastInstance) toastInstance.show();
}
function submitSearch(showNotification = true) {
const query = els.query.value.trim();
if (query.length < 2) {
els.form.classList.add('was-validated');
els.query.focus();
return;
}
els.form.classList.remove('was-validated');
selectedId = null;
const results = searchLessons(query, els.language.value);
renderResults(results);
if (results[0]) renderDetail(results[0].id);
updateUrl();
if (showNotification) showToast('Search complete. Results ranked locally for the MVP demo.');
}
async function copyText(text, message) {
try {
await navigator.clipboard.writeText(text);
showToast(message);
} catch (error) {
showToast('Copy failed. You can manually copy from the page.');
}
}
function initFromUrl() {
const params = new URLSearchParams(window.location.search);
const q = params.get('q');
const language = params.get('language');
const result = params.get('result');
if (q) els.query.value = q;
if (language && [...els.language.options].some(option => option.value === language || option.textContent === language)) els.language.value = language;
if (q && q.length >= 2) {
const results = searchLessons(q, els.language.value);
renderResults(results);
renderDetail(result && lessons.some(item => item.id === result) ? result : (results[0]?.id || null));
}
}
function bindEvents() {
els.form.addEventListener('submit', (event) => {
event.preventDefault();
submitSearch();
});
document.addEventListener('click', (event) => {
const resultButton = event.target.closest('[data-id]');
if (resultButton) {
renderDetail(resultButton.dataset.id);
document.getElementById('detail').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
return;
}
const chip = event.target.closest('.chip');
if (chip) {
els.query.value = chip.dataset.query || '';
els.language.value = chip.dataset.language || 'all';
submitSearch();
return;
}
const languageCard = event.target.closest('.language-card');
if (languageCard) {
els.language.value = languageCard.dataset.language;
els.query.value = languageCard.dataset.language;
submitSearch();
document.getElementById('search').scrollIntoView({ behavior: 'smooth' });
return;
}
const copyCode = event.target.closest('[data-copy-code]');
if (copyCode) {
const lesson = lessons.find(item => item.id === copyCode.dataset.copyCode);
if (lesson) copyText(lesson.code, 'Code example copied.');
return;
}
const shareResult = event.target.closest('[data-share-result]');
if (shareResult) {
updateUrl({ result: shareResult.dataset.shareResult });
copyText(window.location.href, 'Shareable result link copied.');
}
});
els.copySearchLink.addEventListener('click', () => {
updateUrl(selectedId ? { result: selectedId } : {});
copyText(window.location.href, 'Shareable search link copied.');
});
}
document.addEventListener('DOMContentLoaded', () => {
els.form = $('#searchForm');
els.query = $('#queryInput');
els.language = $('#languageFilter');
els.resultsList = $('#resultsList');
els.resultCount = $('#resultCount');
els.detail = $('#detail');
els.copySearchLink = $('#copySearchLink');
els.toastMessage = $('#toastMessage');
const toastEl = $('#appToast');
if (window.bootstrap && toastEl) toastInstance = new bootstrap.Toast(toastEl, { delay: 2800 });
renderResults([]);
bindEvents();
initFromUrl();
});