feat: site search with a dialog in the header and a /api/busca endpoint
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d8323c653b
commit
7d0edf13c4
7 files changed
+1115
-5
No files matched your search
@@ -0,0 +1,104 @@
|
||||
import seriesData from "@/public/series.json";
|
||||
import frameworksData from "@/public/frameworks.json";
|
||||
import { categoriasGlossario } from "@/lib/glossarioData";
|
||||
import { errosCatalogo } from "@/lib/diagnosticoData";
|
||||
import { COMPONENTES_REGISTRY } from "@/lib/componentRegistryData";
|
||||
import { montarConhecimento } from "@/lib/searchTopics";
|
||||
import { readFileSync, existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* Índice de busca do site, montado no servidor a partir dos mesmos dados que
|
||||
* alimentam as páginas. Uma série, termo ou erro novo entra sozinho na busca.
|
||||
*
|
||||
* Cada entrada: { id, tipo, titulo, descricao, href, keywords }
|
||||
* `keywords` guarda texto extra pesquisável (não exibido).
|
||||
*/
|
||||
|
||||
const PAGINAS = [
|
||||
{ titulo: "Início", descricao: "Página inicial do ESPDocs", href: "/", keywords: "home inicio" },
|
||||
{ titulo: "Todas as Séries", descricao: "Pinouts, especificações e restrições de cada série ESP32", href: "/series", keywords: "chips pinout especificações" },
|
||||
{ titulo: "Comparador de Séries", descricao: "Compare até 4 modelos lado a lado", href: "/comparacao", keywords: "comparar comparação versus" },
|
||||
{ titulo: "Catálogo de Placas", descricao: "Placas de desenvolvimento, conectores e compras", href: "/catalogo", keywords: "placas dev boards comprar devkit" },
|
||||
{ titulo: "Seletor Inteligente", descricao: "Descubra o chip ideal para o seu projeto", href: "/seletor", keywords: "quiz escolher recomendar" },
|
||||
{ titulo: "Diagnóstico de Erros", descricao: "Solucione brownout, bootloop, WDT e falhas", href: "/diagnostico", keywords: "erro problema bug falha" },
|
||||
{ titulo: "Frameworks & SDKs", descricao: "ESP-IDF, Arduino, Rust, RainMaker e mais", href: "/frameworks", keywords: "sdk" },
|
||||
{ titulo: "Component Registry", descricao: "Catálogo de drivers e gerador de idf_component.yml", href: "/componentes", keywords: "bibliotecas drivers yml" },
|
||||
{ titulo: "Glossário Técnico", descricao: "Conceitos essenciais de hardware e software", href: "/glossario", keywords: "termos dicionário significado" },
|
||||
{ titulo: "O Livro", descricao: "Microcontroladores ESP32: conectividade, eficiência e desempenho", href: "/livro", keywords: "livro book capa comprar" },
|
||||
{ titulo: "Sobre o Projeto", descricao: "História, autor e Ciência Embarcada", href: "/sobre", keywords: "autor contato" },
|
||||
];
|
||||
|
||||
const cortar = (texto = "", max = 140) => (texto.length > max ? `${texto.slice(0, max - 1).trimEnd()}…` : texto);
|
||||
|
||||
export function construirIndiceBusca() {
|
||||
const itens = [];
|
||||
|
||||
for (const p of PAGINAS) {
|
||||
itens.push({ tipo: "pagina", ...p, keywords: p.keywords });
|
||||
}
|
||||
|
||||
for (const [chave, s] of Object.entries(seriesData)) {
|
||||
itens.push({
|
||||
tipo: "serie",
|
||||
titulo: chave,
|
||||
descricao: cortar(`${s.nome_completo ?? ""} — ${s.descricao ?? ""}`),
|
||||
href: `/series/${chave}`,
|
||||
keywords: [s.arquitetura, s.wifi, s.bluetooth, s.frequencia, s.nucleos].filter(Boolean).join(" "),
|
||||
});
|
||||
}
|
||||
|
||||
for (const [chave, f] of Object.entries(frameworksData)) {
|
||||
itens.push({
|
||||
tipo: "framework",
|
||||
titulo: f.nome ?? chave,
|
||||
descricao: cortar(`${f.nome_completo ?? ""} — ${f.descricao ?? ""}`),
|
||||
href: `/frameworks/${encodeURIComponent(chave)}`,
|
||||
keywords: [f.linguagem, f.tipo, f.funcao_principal].filter(Boolean).join(" "),
|
||||
});
|
||||
}
|
||||
|
||||
for (const cat of categoriasGlossario) {
|
||||
for (const t of cat.termos) {
|
||||
const slug = t.termo.toLowerCase().replace(/[\s()/²]+/g, "-");
|
||||
itens.push({
|
||||
tipo: "glossario",
|
||||
titulo: t.termo,
|
||||
descricao: cortar(t.definicao),
|
||||
href: `/glossario#${slug}`,
|
||||
keywords: [cat.nome, t.exemplo].filter(Boolean).join(" "),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const e of errosCatalogo) {
|
||||
itens.push({
|
||||
tipo: "erro",
|
||||
titulo: e.titulo,
|
||||
descricao: cortar(e.resumo),
|
||||
href: `/diagnostico#${e.id}`,
|
||||
keywords: [e.logSerial, ...(e.sintomas ?? [])].filter(Boolean).join(" "),
|
||||
});
|
||||
}
|
||||
|
||||
for (const c of COMPONENTES_REGISTRY) {
|
||||
itens.push({
|
||||
tipo: "componente",
|
||||
titulo: c.titulo,
|
||||
descricao: cortar(c.descricao),
|
||||
href: "/componentes",
|
||||
keywords: [c.name, c.namespace, ...(c.tags ?? [])].filter(Boolean).join(" "),
|
||||
});
|
||||
}
|
||||
|
||||
const conexoes = {};
|
||||
for (const chave of Object.keys(seriesData)) {
|
||||
const arquivo = path.join(process.cwd(), "public", "conexoes", `${chave}.json`);
|
||||
if (existsSync(arquivo)) conexoes[chave] = JSON.parse(readFileSync(arquivo, "utf8"));
|
||||
}
|
||||
|
||||
return {
|
||||
itens: itens.map((item, i) => ({ id: i, ...item })),
|
||||
series: montarConhecimento(seriesData, conexoes),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user