snapport 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -27
- package/dist/index.d.ts +1 -0
- package/dist/snapport.js +242 -0
- package/dist/snapport.umd.cjs +43 -0
- package/package.json +8 -7
- package/dist/snap-port.js +0 -195
- package/dist/snap-port.umd.cjs +0 -43
- /package/dist/{snap-port.css → snapport.css} +0 -0
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://github.com/guilhermegodoydev/snapport/blob/main/preview.png" width="
|
|
2
|
+
<img src="https://github.com/guilhermegodoydev/snapport/blob/main/preview.png" width="120" height="120" style="border-radius: 50%" alt="Logo" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">Snapport</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
8
|
+
<strong>Gere automaticamente a seção de projetos do seu portfólio a partir dos seus repositórios do GitHub.</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -15,42 +15,107 @@
|
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
17
|
<p align="center">
|
|
18
|
-
<a href="https://guilhermegodoydev.github.io/snapport"><strong>
|
|
19
|
-
<br /><br />
|
|
20
|
-
<a href="https://github.com/guilhermegodoydev/snapport/issues">Reportar Bug</a>
|
|
21
|
-
·
|
|
22
|
-
<a href="https://github.com/guilhermegodoydev/snapport/issues">Sugestão de Feature</a>
|
|
18
|
+
<a href="https://guilhermegodoydev.github.io/snapport"><strong>Ver Documentação »</strong></a>
|
|
23
19
|
</p>
|
|
24
20
|
|
|
25
21
|
---
|
|
26
22
|
|
|
27
|
-
## 💡
|
|
23
|
+
## 💡 O problema
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
Manter a seção de projetos do portfólio atualizada manualmente é repetitivo:
|
|
30
26
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
27
|
+
- você cria um novo projeto
|
|
28
|
+
- atualiza o GitHub
|
|
29
|
+
- esquece de atualizar o portfólio
|
|
30
|
+
- seu portfólio fica desatualizado
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ✨ O que o Snapport faz
|
|
35
|
+
|
|
36
|
+
O Snapport automatiza a geração da seção de projetos do seu portfólio com base nos seus repositórios do GitHub.
|
|
37
|
+
|
|
38
|
+
Você define onde os projetos serão renderizados e a lib cuida da sincronização.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🔥 Antes vs Depois
|
|
43
|
+
|
|
44
|
+
### ❌ Antes (manual)
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<div>
|
|
48
|
+
<h2>Projetos</h2>
|
|
49
|
+
<ul>
|
|
50
|
+
<li>Projeto A</li>
|
|
51
|
+
<li>Projeto B</li>
|
|
52
|
+
</ul>
|
|
53
|
+
</div>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### ✅ Depois (automático)
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import { initPortfolio } from 'snapport';
|
|
60
|
+
|
|
61
|
+
initPortfolio('seu-usuario-github', {
|
|
62
|
+
tag: 'port',
|
|
63
|
+
searchContainer: 'meu-search',
|
|
64
|
+
filtersContainer: 'meu-filters',
|
|
65
|
+
projectsContainer: 'meu-projects'
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 🚀 Início rápido
|
|
72
|
+
|
|
73
|
+
### Via npm
|
|
74
|
+
|
|
75
|
+
Instale o pacote:
|
|
36
76
|
|
|
37
77
|
```bash
|
|
38
78
|
npm install snapport
|
|
39
79
|
```
|
|
40
80
|
|
|
41
|
-
|
|
81
|
+
No seu arquivo JavaScript/TypeScript, importe o JS e o CSS:
|
|
82
|
+
|
|
83
|
+
```js
|
|
42
84
|
import { initPortfolio } from 'snapport';
|
|
85
|
+
import 'snapport/dist/style.css';
|
|
43
86
|
|
|
44
|
-
initPortfolio('seu-usuario', {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
87
|
+
initPortfolio('seu-usuario-github', {
|
|
88
|
+
tag: 'port',
|
|
89
|
+
searchContainer: 'meu-search',
|
|
90
|
+
filtersContainer: 'meu-filters',
|
|
91
|
+
projectsContainer: 'meu-projects'
|
|
48
92
|
});
|
|
49
93
|
```
|
|
50
94
|
|
|
51
|
-
|
|
95
|
+
### Via CDN (vanilla JS)
|
|
96
|
+
|
|
97
|
+
No seu HTML, inclua o CSS no `<head>` e o script no `<body>`:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<!-- No seu <head> -->
|
|
101
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/snapport/dist/style.css"/>
|
|
102
|
+
|
|
103
|
+
<!-- No final do <body> -->
|
|
104
|
+
<script type="module">
|
|
105
|
+
import { initPortfolio } from 'https://cdn.jsdelivr.net/npm/snapport/dist/snapport.js';
|
|
106
|
+
|
|
107
|
+
initPortfolio('seu-usuario-github', {
|
|
108
|
+
tag: 'port',
|
|
109
|
+
searchContainer: 'meu-search',
|
|
110
|
+
filtersContainer: 'meu-filters',
|
|
111
|
+
projectsContainer: 'meu-projects'
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
114
|
+
```
|
|
52
115
|
|
|
53
|
-
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🎨 Personalização
|
|
54
119
|
|
|
55
120
|
```css
|
|
56
121
|
:root {
|
|
@@ -60,17 +125,24 @@ O Snapport é totalmente customizável via **CSS Variables**. Adapte as cores ao
|
|
|
60
125
|
}
|
|
61
126
|
```
|
|
62
127
|
|
|
63
|
-
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## ⚙️ Características
|
|
64
131
|
|
|
65
|
-
|
|
66
|
-
|
|
132
|
+
- Zero dependências em runtime
|
|
133
|
+
- Cache local (2h)
|
|
134
|
+
- Funciona com qualquer framework
|
|
135
|
+
- Baseado em GitHub Topics
|
|
136
|
+
- Leve e direto ao ponto
|
|
67
137
|
|
|
68
138
|
---
|
|
69
139
|
|
|
70
|
-
##
|
|
140
|
+
## 🛠 Tecnologias suportadas
|
|
71
141
|
|
|
72
|
-
|
|
142
|
+
React • TypeScript • Node.js • Docker • Tailwind • Python • etc
|
|
73
143
|
|
|
74
|
-
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 🤝 Contribuição
|
|
75
147
|
|
|
76
|
-
|
|
148
|
+
Veja a documentação: [CONTRIBUIR](https://guilhermegodoydev.github.io/snapport/projeto/contribuir.html)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare function getPortProjects(username: string, tag?: string, options?: {
|
|
2
2
|
forceRefresh?: boolean;
|
|
3
|
+
cache?: boolean;
|
|
3
4
|
}): Promise<SanitizedRepo[]>;
|
|
4
5
|
|
|
5
6
|
export declare function initPortfolio(username: string, config?: PortfolioConfig): Promise<PortfolioResponse>;
|
package/dist/snapport.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
const C = {
|
|
2
|
+
react: { name: "React", icon: "react", color: "61DAFB" },
|
|
3
|
+
vue: { name: "Vue.js", icon: "vuedotjs", color: "4FC08D" },
|
|
4
|
+
angular: { name: "Angular", icon: "angular", color: "DD0031" },
|
|
5
|
+
nextjs: { name: "Next.js", icon: "nextdotjs", color: "000000" },
|
|
6
|
+
typescript: { name: "TypeScript", icon: "typescript", color: "3178C6" },
|
|
7
|
+
javascript: { name: "JavaScript", icon: "javascript", color: "F7DF1E" },
|
|
8
|
+
html5: { name: "HTML5", icon: "html5", color: "E34F26" },
|
|
9
|
+
css3: { name: "CSS3", icon: "css3", color: "1572B6" },
|
|
10
|
+
sass: { name: "Sass", icon: "sass", color: "CC6699" },
|
|
11
|
+
"tailwind-css": { name: "Tailwind", icon: "tailwindcss", color: "06B6D4" },
|
|
12
|
+
bootstrap: { name: "Bootstrap", icon: "bootstrap", color: "7952B3" },
|
|
13
|
+
"styled-components": { name: "Styled Components", icon: "styledcomponents", color: "DB7093" },
|
|
14
|
+
figma: { name: "Figma", icon: "figma", color: "F24E1E" },
|
|
15
|
+
nodejs: { name: "Node.js", icon: "nodedotjs", color: "339933" },
|
|
16
|
+
express: { name: "Express", icon: "express", color: "000000" },
|
|
17
|
+
nestjs: { name: "NestJS", icon: "nestjs", color: "E0234E" },
|
|
18
|
+
python: { name: "Python", icon: "python", color: "3776AB" },
|
|
19
|
+
java: { name: "Java", icon: "openjdk", color: "007396" },
|
|
20
|
+
php: { name: "PHP", icon: "php", color: "777BB4" },
|
|
21
|
+
postgresql: { name: "PostgreSQL", icon: "postgresql", color: "4169E1" },
|
|
22
|
+
mongodb: { name: "MongoDB", icon: "mongodb", color: "47A248" },
|
|
23
|
+
mysql: { name: "MySQL", icon: "mysql", color: "4479A1" },
|
|
24
|
+
firebase: { name: "Firebase", icon: "firebase", color: "FFCA28" },
|
|
25
|
+
prisma: { name: "Prisma", icon: "prisma", color: "2D3748" },
|
|
26
|
+
docker: { name: "Docker", icon: "docker", color: "2496ED" },
|
|
27
|
+
git: { name: "Git", icon: "git", color: "F05032" },
|
|
28
|
+
jest: { name: "Jest", icon: "jest", color: "C21325" },
|
|
29
|
+
"github-actions": { name: "GitHub Actions", icon: "githubactions", color: "2088FF" }
|
|
30
|
+
}, j = 7200 * 1e3, w = (t = []) => t.filter(
|
|
31
|
+
(e) => !!C[e?.toLowerCase().trim()]
|
|
32
|
+
), S = (t) => {
|
|
33
|
+
const e = Array.isArray(t.topics) ? t.topics : [];
|
|
34
|
+
return {
|
|
35
|
+
id: t.id,
|
|
36
|
+
name: t.name ?? "Projeto sem nome",
|
|
37
|
+
description: t.description ?? "Sem descrição disponível",
|
|
38
|
+
htmlUrl: t.html_url,
|
|
39
|
+
topics: e,
|
|
40
|
+
stacks: w(e),
|
|
41
|
+
deployUrl: t.homepage ?? null
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
function $(t) {
|
|
45
|
+
try {
|
|
46
|
+
Object.keys(localStorage).forEach((e) => {
|
|
47
|
+
e.startsWith("gh_projects_") && e !== `gh_projects_${t}` && localStorage.removeItem(e);
|
|
48
|
+
});
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.warn("Erro ao limpar caches antigos:", e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function b(t, e = "port", r = {}) {
|
|
54
|
+
if (!t)
|
|
55
|
+
return console.error("[Snapport]: Username é obrigatório."), [];
|
|
56
|
+
const o = `gh_projects_${t}`, c = r.cache !== !1, n = r.forceRefresh === !0, a = c && !n;
|
|
57
|
+
try {
|
|
58
|
+
if ($(t), a) {
|
|
59
|
+
const y = localStorage.getItem(o);
|
|
60
|
+
if (y)
|
|
61
|
+
try {
|
|
62
|
+
const d = JSON.parse(y);
|
|
63
|
+
if (d && typeof d == "object" && Array.isArray(d.data) && typeof d.timestamp == "number" && Date.now() - d.timestamp < j)
|
|
64
|
+
return d.data;
|
|
65
|
+
} catch {
|
|
66
|
+
localStorage.removeItem(o);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const i = encodeURIComponent(`user:${t} topic:${e}`), s = await fetch(`https://api.github.com/search/repositories?q=${i}&sort=updated&order=desc`);
|
|
70
|
+
if (!s.ok)
|
|
71
|
+
throw new Error(`GitHub API error: ${s.status}`);
|
|
72
|
+
const l = ((await s.json()).items || []).map(S), p = {
|
|
73
|
+
data: l,
|
|
74
|
+
timestamp: Date.now()
|
|
75
|
+
};
|
|
76
|
+
return c && localStorage.setItem(o, JSON.stringify(p)), l;
|
|
77
|
+
} catch (i) {
|
|
78
|
+
console.error(`[Snapport]: Erro ao buscar dados de ${t}:`, i);
|
|
79
|
+
const s = localStorage.getItem(o);
|
|
80
|
+
if (s)
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(s).data;
|
|
83
|
+
} catch {
|
|
84
|
+
localStorage.removeItem(o);
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function u(t, e = 5e3) {
|
|
90
|
+
return new Promise((r, o) => {
|
|
91
|
+
const c = Date.now(), n = () => {
|
|
92
|
+
const a = document.getElementById(t);
|
|
93
|
+
if (a) return r(a);
|
|
94
|
+
if (Date.now() - c > e)
|
|
95
|
+
return o(`Snapport: container "${t}" não encontrado`);
|
|
96
|
+
requestAnimationFrame(n);
|
|
97
|
+
};
|
|
98
|
+
n();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function g(t) {
|
|
102
|
+
return Array.isArray(t.topics) ? t.topics : [];
|
|
103
|
+
}
|
|
104
|
+
const m = (t) => {
|
|
105
|
+
if (typeof t == "string") {
|
|
106
|
+
const e = document.getElementById(t);
|
|
107
|
+
return e || console.warn(
|
|
108
|
+
`[Snapport] Container "${t}" não encontrado. Verifique se o elemento existe no DOM antes de inicializar.`
|
|
109
|
+
), e;
|
|
110
|
+
}
|
|
111
|
+
return t;
|
|
112
|
+
}, v = (t, e = null) => {
|
|
113
|
+
if (!e)
|
|
114
|
+
return `
|
|
115
|
+
<button class="ghp-filter-btn active" data-topic="all">
|
|
116
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
117
|
+
<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/>
|
|
118
|
+
<rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>
|
|
119
|
+
</svg>
|
|
120
|
+
<p>Todos</p>
|
|
121
|
+
</button>
|
|
122
|
+
`;
|
|
123
|
+
const r = `https://cdn.simpleicons.org/${e.icon}/${e.color}`;
|
|
124
|
+
return `
|
|
125
|
+
<button class="ghp-filter-btn" data-topic="${t}" style="--tech-color: #${e.color}">
|
|
126
|
+
<img src="${r}" alt="${e.name}" loading="lazy">
|
|
127
|
+
<p>${e.name}</p>
|
|
128
|
+
</button>
|
|
129
|
+
`;
|
|
130
|
+
}, E = (t, e) => {
|
|
131
|
+
const r = `https://raw.githubusercontent.com/${e}/${t.name}/main/preview.png`, o = `https://opengraph.githubassets.com/${e}/${t.name}`, c = `https://placehold.co/640x360?text=${encodeURIComponent(t.name)}`, n = t.deployUrl ? `<a href="${t.deployUrl}" target="_blank" rel="noopener noreferrer">Acessar</a>` : "", a = t.htmlUrl ? `<a href="${t.htmlUrl}" target="_blank" rel="noopener noreferrer">Github</a>` : "";
|
|
132
|
+
return `
|
|
133
|
+
<div class="ghp-project-card">
|
|
134
|
+
<div class="ghp-img-container">
|
|
135
|
+
<img
|
|
136
|
+
src="${r}"
|
|
137
|
+
alt="Preview do projeto ${t.name}"
|
|
138
|
+
class="ghp-card-img"
|
|
139
|
+
loading="lazy"
|
|
140
|
+
onerror="if (this.src.includes('preview.png')) { this.src='${o}'; } else { this.onerror=null; this.src='${c}'; }"
|
|
141
|
+
>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="ghp-card-content">
|
|
144
|
+
<div>
|
|
145
|
+
<h3>${t.name}</h3>
|
|
146
|
+
<p title="${t.description}">${t.description}</p>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="ghp-card-links">
|
|
149
|
+
${a}
|
|
150
|
+
${n}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
`;
|
|
155
|
+
};
|
|
156
|
+
function x(t, e = 6) {
|
|
157
|
+
const r = m(t);
|
|
158
|
+
r && (r.classList.toggle("ghp-projects-grid", !0), r.innerHTML = Array(e).fill('<div class="ghp-project-card ghp-skeleton ghp-skeleton-card"></div>').join(""));
|
|
159
|
+
}
|
|
160
|
+
function B(t, e) {
|
|
161
|
+
const r = m(e);
|
|
162
|
+
if (!r) return;
|
|
163
|
+
const o = t.flatMap((a) => g(a)), n = [...new Set(o)].reduce((a, i) => {
|
|
164
|
+
const s = C[i];
|
|
165
|
+
return s ? a + v(i, s) : a;
|
|
166
|
+
}, v("all"));
|
|
167
|
+
r.innerHTML = `<div class="ghp-filters-content">${n}</div>`;
|
|
168
|
+
}
|
|
169
|
+
function f(t, e, r = "", o) {
|
|
170
|
+
const c = m(e);
|
|
171
|
+
if (c) {
|
|
172
|
+
if (c.className = "ghp-projects-grid", c.style.minHeight = "auto", t.length === 0) {
|
|
173
|
+
c.innerHTML = '<p class="ghp-empty-msg">Nenhum projeto encontrado.</p>';
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
c.innerHTML = t.map((n) => o ? o(n) : E(n, r)).join("");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function F(t) {
|
|
180
|
+
const e = m(t);
|
|
181
|
+
e && (e.innerHTML = `
|
|
182
|
+
<div class="ghp-search-container">
|
|
183
|
+
<svg class="ghp-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
184
|
+
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
185
|
+
</svg>
|
|
186
|
+
<input type="text" id="gh-port-search" class="ghp-search-input" placeholder="Buscar projeto..." autocomplete="off">
|
|
187
|
+
</div>
|
|
188
|
+
`);
|
|
189
|
+
}
|
|
190
|
+
function k(t, e, r, o, c) {
|
|
191
|
+
const n = document.getElementById(t);
|
|
192
|
+
if (!n) {
|
|
193
|
+
console.warn(
|
|
194
|
+
`[Snapport] setupFilters falhou: container "${t}" não encontrado. Possíveis causas: renderFilters não foi chamado ou DOM ainda não está pronto.`
|
|
195
|
+
);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
n.addEventListener("click", (a) => {
|
|
199
|
+
const s = a.target.closest(".ghp-filter-btn");
|
|
200
|
+
if (!s) return;
|
|
201
|
+
const h = s.dataset.topic, l = h === "all" ? e : e.filter((p) => g(p).includes(h));
|
|
202
|
+
f(l, r, o, c), n.querySelectorAll(".ghp-filter-btn").forEach((p) => p.classList.remove("active")), s.classList.add("active");
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function A(t, e, r, o) {
|
|
206
|
+
(() => {
|
|
207
|
+
const n = document.getElementById("gh-port-search");
|
|
208
|
+
return n ? (n.addEventListener("input", (a) => {
|
|
209
|
+
const s = a.target.value.toLowerCase(), h = t.filter(
|
|
210
|
+
(l) => l.name.toLowerCase().includes(s) || (l.description || "").toLowerCase().includes(s) || g(l).some((p) => p.toLowerCase().includes(s))
|
|
211
|
+
);
|
|
212
|
+
f(h, e, r, o);
|
|
213
|
+
}), !0) : !1;
|
|
214
|
+
})() || console.warn("[Snapport] Search input ainda não existe no DOM");
|
|
215
|
+
}
|
|
216
|
+
async function L(t, e = {
|
|
217
|
+
searchContainer: "search-cont",
|
|
218
|
+
filtersContainer: "filters-cont",
|
|
219
|
+
projectsContainer: "projects-cont"
|
|
220
|
+
}) {
|
|
221
|
+
try {
|
|
222
|
+
const r = e.tag || "port";
|
|
223
|
+
await Promise.all([
|
|
224
|
+
u(e.searchContainer),
|
|
225
|
+
u(e.filtersContainer),
|
|
226
|
+
u(e.projectsContainer)
|
|
227
|
+
]), F(e.searchContainer), x(e.projectsContainer, 6);
|
|
228
|
+
const o = await b(t, r);
|
|
229
|
+
return B(o, e.filtersContainer), f(o, e.projectsContainer, t, e.customCardTemplate), k(e.filtersContainer, o, e.projectsContainer, t, e.customCardTemplate), A(o, e.projectsContainer, t, e.customCardTemplate), { projects: o, status: "success" };
|
|
230
|
+
} catch (r) {
|
|
231
|
+
console.error("Erro ao inicializar portfólio:", r);
|
|
232
|
+
const o = document.getElementById(e.projectsContainer);
|
|
233
|
+
return o && (o.innerHTML = '<p class="ghp-empty-msg">Erro ao carregar projetos.</p>'), { status: "error", message: r.message };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export {
|
|
237
|
+
b as getPortProjects,
|
|
238
|
+
L as initPortfolio,
|
|
239
|
+
B as renderFilters,
|
|
240
|
+
f as renderProjects,
|
|
241
|
+
F as renderSearchBar
|
|
242
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
(function(i,h){typeof exports=="object"&&typeof module<"u"?h(exports):typeof define=="function"&&define.amd?define(["exports"],h):(i=typeof globalThis<"u"?globalThis:i||self,h(i.SnapPort={}))})(this,(function(i){"use strict";const h={react:{name:"React",icon:"react",color:"61DAFB"},vue:{name:"Vue.js",icon:"vuedotjs",color:"4FC08D"},angular:{name:"Angular",icon:"angular",color:"DD0031"},nextjs:{name:"Next.js",icon:"nextdotjs",color:"000000"},typescript:{name:"TypeScript",icon:"typescript",color:"3178C6"},javascript:{name:"JavaScript",icon:"javascript",color:"F7DF1E"},html5:{name:"HTML5",icon:"html5",color:"E34F26"},css3:{name:"CSS3",icon:"css3",color:"1572B6"},sass:{name:"Sass",icon:"sass",color:"CC6699"},"tailwind-css":{name:"Tailwind",icon:"tailwindcss",color:"06B6D4"},bootstrap:{name:"Bootstrap",icon:"bootstrap",color:"7952B3"},"styled-components":{name:"Styled Components",icon:"styledcomponents",color:"DB7093"},figma:{name:"Figma",icon:"figma",color:"F24E1E"},nodejs:{name:"Node.js",icon:"nodedotjs",color:"339933"},express:{name:"Express",icon:"express",color:"000000"},nestjs:{name:"NestJS",icon:"nestjs",color:"E0234E"},python:{name:"Python",icon:"python",color:"3776AB"},java:{name:"Java",icon:"openjdk",color:"007396"},php:{name:"PHP",icon:"php",color:"777BB4"},postgresql:{name:"PostgreSQL",icon:"postgresql",color:"4169E1"},mongodb:{name:"MongoDB",icon:"mongodb",color:"47A248"},mysql:{name:"MySQL",icon:"mysql",color:"4479A1"},firebase:{name:"Firebase",icon:"firebase",color:"FFCA28"},prisma:{name:"Prisma",icon:"prisma",color:"2D3748"},docker:{name:"Docker",icon:"docker",color:"2496ED"},git:{name:"Git",icon:"git",color:"F05032"},jest:{name:"Jest",icon:"jest",color:"C21325"},"github-actions":{name:"GitHub Actions",icon:"githubactions",color:"2088FF"}},b=7200*1e3,E=(e=[])=>e.filter(t=>!!h[t?.toLowerCase().trim()]),B=e=>{const t=Array.isArray(e.topics)?e.topics:[];return{id:e.id,name:e.name??"Projeto sem nome",description:e.description??"Sem descrição disponível",htmlUrl:e.html_url,topics:t,stacks:E(t),deployUrl:e.homepage??null}};function F(e){try{Object.keys(localStorage).forEach(t=>{t.startsWith("gh_projects_")&&t!==`gh_projects_${e}`&&localStorage.removeItem(t)})}catch(t){console.warn("Erro ao limpar caches antigos:",t)}}async function j(e,t="port",r={}){if(!e)return console.error("[Snapport]: Username é obrigatório."),[];const o=`gh_projects_${e}`,c=r.cache!==!1,n=r.forceRefresh===!0,a=c&&!n;try{if(F(e),a){const $=localStorage.getItem(o);if($)try{const m=JSON.parse($);if(m&&typeof m=="object"&&Array.isArray(m.data)&&typeof m.timestamp=="number"&&Date.now()-m.timestamp<b)return m.data}catch{localStorage.removeItem(o)}}const l=encodeURIComponent(`user:${e} topic:${t}`),s=await fetch(`https://api.github.com/search/repositories?q=${l}&sort=updated&order=desc`);if(!s.ok)throw new Error(`GitHub API error: ${s.status}`);const p=((await s.json()).items||[]).map(B),d={data:p,timestamp:Date.now()};return c&&localStorage.setItem(o,JSON.stringify(d)),p}catch(l){console.error(`[Snapport]: Erro ao buscar dados de ${e}:`,l);const s=localStorage.getItem(o);if(s)try{return JSON.parse(s).data}catch{localStorage.removeItem(o)}return[]}}function y(e,t=5e3){return new Promise((r,o)=>{const c=Date.now(),n=()=>{const a=document.getElementById(e);if(a)return r(a);if(Date.now()-c>t)return o(`Snapport: container "${e}" não encontrado`);requestAnimationFrame(n)};n()})}function v(e){return Array.isArray(e.topics)?e.topics:[]}const g=e=>{if(typeof e=="string"){const t=document.getElementById(e);return t||console.warn(`[Snapport] Container "${e}" não encontrado. Verifique se o elemento existe no DOM antes de inicializar.`),t}return e},C=(e,t=null)=>{if(!t)return`
|
|
2
|
+
<button class="ghp-filter-btn active" data-topic="all">
|
|
3
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
4
|
+
<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/>
|
|
5
|
+
<rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>
|
|
6
|
+
</svg>
|
|
7
|
+
<p>Todos</p>
|
|
8
|
+
</button>
|
|
9
|
+
`;const r=`https://cdn.simpleicons.org/${t.icon}/${t.color}`;return`
|
|
10
|
+
<button class="ghp-filter-btn" data-topic="${e}" style="--tech-color: #${t.color}">
|
|
11
|
+
<img src="${r}" alt="${t.name}" loading="lazy">
|
|
12
|
+
<p>${t.name}</p>
|
|
13
|
+
</button>
|
|
14
|
+
`},P=(e,t)=>{const r=`https://raw.githubusercontent.com/${t}/${e.name}/main/preview.png`,o=`https://opengraph.githubassets.com/${t}/${e.name}`,c=`https://placehold.co/640x360?text=${encodeURIComponent(e.name)}`,n=e.deployUrl?`<a href="${e.deployUrl}" target="_blank" rel="noopener noreferrer">Acessar</a>`:"",a=e.htmlUrl?`<a href="${e.htmlUrl}" target="_blank" rel="noopener noreferrer">Github</a>`:"";return`
|
|
15
|
+
<div class="ghp-project-card">
|
|
16
|
+
<div class="ghp-img-container">
|
|
17
|
+
<img
|
|
18
|
+
src="${r}"
|
|
19
|
+
alt="Preview do projeto ${e.name}"
|
|
20
|
+
class="ghp-card-img"
|
|
21
|
+
loading="lazy"
|
|
22
|
+
onerror="if (this.src.includes('preview.png')) { this.src='${o}'; } else { this.onerror=null; this.src='${c}'; }"
|
|
23
|
+
>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="ghp-card-content">
|
|
26
|
+
<div>
|
|
27
|
+
<h3>${e.name}</h3>
|
|
28
|
+
<p title="${e.description}">${e.description}</p>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="ghp-card-links">
|
|
31
|
+
${a}
|
|
32
|
+
${n}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
`};function T(e,t=6){const r=g(e);r&&(r.classList.toggle("ghp-projects-grid",!0),r.innerHTML=Array(t).fill('<div class="ghp-project-card ghp-skeleton ghp-skeleton-card"></div>').join(""))}function w(e,t){const r=g(t);if(!r)return;const o=e.flatMap(a=>v(a)),n=[...new Set(o)].reduce((a,l)=>{const s=h[l];return s?a+C(l,s):a},C("all"));r.innerHTML=`<div class="ghp-filters-content">${n}</div>`}function f(e,t,r="",o){const c=g(t);if(c){if(c.className="ghp-projects-grid",c.style.minHeight="auto",e.length===0){c.innerHTML='<p class="ghp-empty-msg">Nenhum projeto encontrado.</p>';return}c.innerHTML=e.map(n=>o?o(n):P(n,r)).join("")}}function S(e){const t=g(e);t&&(t.innerHTML=`
|
|
37
|
+
<div class="ghp-search-container">
|
|
38
|
+
<svg class="ghp-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
39
|
+
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
40
|
+
</svg>
|
|
41
|
+
<input type="text" id="gh-port-search" class="ghp-search-input" placeholder="Buscar projeto..." autocomplete="off">
|
|
42
|
+
</div>
|
|
43
|
+
`)}function x(e,t,r,o,c){const n=document.getElementById(e);if(!n){console.warn(`[Snapport] setupFilters falhou: container "${e}" não encontrado. Possíveis causas: renderFilters não foi chamado ou DOM ainda não está pronto.`);return}n.addEventListener("click",a=>{const s=a.target.closest(".ghp-filter-btn");if(!s)return;const u=s.dataset.topic,p=u==="all"?t:t.filter(d=>v(d).includes(u));f(p,r,o,c),n.querySelectorAll(".ghp-filter-btn").forEach(d=>d.classList.remove("active")),s.classList.add("active")})}function k(e,t,r,o){(()=>{const n=document.getElementById("gh-port-search");return n?(n.addEventListener("input",a=>{const s=a.target.value.toLowerCase(),u=e.filter(p=>p.name.toLowerCase().includes(s)||(p.description||"").toLowerCase().includes(s)||v(p).some(d=>d.toLowerCase().includes(s)));f(u,t,r,o)}),!0):!1})()||console.warn("[Snapport] Search input ainda não existe no DOM")}async function A(e,t={searchContainer:"search-cont",filtersContainer:"filters-cont",projectsContainer:"projects-cont"}){try{const r=t.tag||"port";await Promise.all([y(t.searchContainer),y(t.filtersContainer),y(t.projectsContainer)]),S(t.searchContainer),T(t.projectsContainer,6);const o=await j(e,r);return w(o,t.filtersContainer),f(o,t.projectsContainer,e,t.customCardTemplate),x(t.filtersContainer,o,t.projectsContainer,e,t.customCardTemplate),k(o,t.projectsContainer,e,t.customCardTemplate),{projects:o,status:"success"}}catch(r){console.error("Erro ao inicializar portfólio:",r);const o=document.getElementById(t.projectsContainer);return o&&(o.innerHTML='<p class="ghp-empty-msg">Erro ao carregar projetos.</p>'),{status:"error",message:r.message}}}i.getPortProjects=j,i.initPortfolio=A,i.renderFilters=w,i.renderProjects=f,i.renderSearchBar=S,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snapport",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"portfolio",
|
|
7
7
|
"github-api",
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
|
-
"main": "./dist/
|
|
20
|
-
"module": "./dist/
|
|
19
|
+
"main": "./dist/snapport.umd.cjs",
|
|
20
|
+
"module": "./dist/snapport.js",
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
|
-
"import": "./dist/
|
|
26
|
-
"require": "./dist/
|
|
25
|
+
"import": "./dist/snapport.js",
|
|
26
|
+
"require": "./dist/snapport.umd.cjs"
|
|
27
27
|
},
|
|
28
|
-
"./style.css": "./dist/
|
|
28
|
+
"./style.css": "./dist/snapport.css"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"dev": "vite playground",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"docs:dev": "vitepress dev docs",
|
|
35
35
|
"docs:build": "vitepress build docs",
|
|
36
36
|
"docs:preview": "vitepress preview docs",
|
|
37
|
-
"docs:deploy": "npm run docs:build && gh-pages -d docs/.vitepress/dist"
|
|
37
|
+
"docs:deploy": "npm run docs:build && gh-pages -d docs/.vitepress/dist",
|
|
38
|
+
"prepublishOnly": "npm run build"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/node": "^25.6.0",
|
package/dist/snap-port.js
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
const f = {
|
|
2
|
-
react: { name: "React", icon: "react", color: "61DAFB" },
|
|
3
|
-
vue: { name: "Vue.js", icon: "vuedotjs", color: "4FC08D" },
|
|
4
|
-
angular: { name: "Angular", icon: "angular", color: "DD0031" },
|
|
5
|
-
nextjs: { name: "Next.js", icon: "nextdotjs", color: "000000" },
|
|
6
|
-
typescript: { name: "TypeScript", icon: "typescript", color: "3178C6" },
|
|
7
|
-
javascript: { name: "JavaScript", icon: "javascript", color: "F7DF1E" },
|
|
8
|
-
html5: { name: "HTML5", icon: "html5", color: "E34F26" },
|
|
9
|
-
css3: { name: "CSS3", icon: "css3", color: "1572B6" },
|
|
10
|
-
sass: { name: "Sass", icon: "sass", color: "CC6699" },
|
|
11
|
-
"tailwind-css": { name: "Tailwind", icon: "tailwindcss", color: "06B6D4" },
|
|
12
|
-
bootstrap: { name: "Bootstrap", icon: "bootstrap", color: "7952B3" },
|
|
13
|
-
"styled-components": { name: "Styled Components", icon: "styledcomponents", color: "DB7093" },
|
|
14
|
-
figma: { name: "Figma", icon: "figma", color: "F24E1E" },
|
|
15
|
-
nodejs: { name: "Node.js", icon: "nodedotjs", color: "339933" },
|
|
16
|
-
express: { name: "Express", icon: "express", color: "000000" },
|
|
17
|
-
nestjs: { name: "NestJS", icon: "nestjs", color: "E0234E" },
|
|
18
|
-
python: { name: "Python", icon: "python", color: "3776AB" },
|
|
19
|
-
java: { name: "Java", icon: "openjdk", color: "007396" },
|
|
20
|
-
php: { name: "PHP", icon: "php", color: "777BB4" },
|
|
21
|
-
postgresql: { name: "PostgreSQL", icon: "postgresql", color: "4169E1" },
|
|
22
|
-
mongodb: { name: "MongoDB", icon: "mongodb", color: "47A248" },
|
|
23
|
-
mysql: { name: "MySQL", icon: "mysql", color: "4479A1" },
|
|
24
|
-
firebase: { name: "Firebase", icon: "firebase", color: "FFCA28" },
|
|
25
|
-
prisma: { name: "Prisma", icon: "prisma", color: "2D3748" },
|
|
26
|
-
docker: { name: "Docker", icon: "docker", color: "2496ED" },
|
|
27
|
-
git: { name: "Git", icon: "git", color: "F05032" },
|
|
28
|
-
jest: { name: "Jest", icon: "jest", color: "C21325" },
|
|
29
|
-
"github-actions": { name: "GitHub Actions", icon: "githubactions", color: "2088FF" }
|
|
30
|
-
}, u = typeof process < "u" && process.env.NODE_ENV !== "production" || typeof import.meta < "u" && import.meta.env.MODE !== "production", v = 7200 * 1e3, C = (t) => t ? t.filter((o) => !!f[o.toLocaleLowerCase().trim()]) : [], j = (t) => ({
|
|
31
|
-
id: t.id,
|
|
32
|
-
name: t.name ?? "Projeto sem nome",
|
|
33
|
-
description: t.description ?? "Sem descrição disponível",
|
|
34
|
-
htmlUrl: t.html_url,
|
|
35
|
-
topics: Array.isArray(t.topics) ? t.topics : [],
|
|
36
|
-
stacks: Array.isArray(t.topics) ? C(t.topics) : [],
|
|
37
|
-
deployUrl: t.homepage ?? null
|
|
38
|
-
});
|
|
39
|
-
function w(t) {
|
|
40
|
-
try {
|
|
41
|
-
Object.keys(localStorage).forEach((e) => {
|
|
42
|
-
e.startsWith("gh_projects_") && !e.includes(t) && localStorage.removeItem(e);
|
|
43
|
-
});
|
|
44
|
-
} catch (e) {
|
|
45
|
-
console.warn("Erro ao limpar caches antigos:", e);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async function $(t, e = "port", o = {}) {
|
|
49
|
-
if (!t)
|
|
50
|
-
return console.error("GitHubPortfolio: Username é obrigatório."), [];
|
|
51
|
-
const r = `gh_projects_${t}`, c = u || o.forceRefresh;
|
|
52
|
-
try {
|
|
53
|
-
if (w(t), c)
|
|
54
|
-
u && console.info(`[GitHubPortfolio]: Dev mode detectado. Cache ignorado para ${t}.`);
|
|
55
|
-
else {
|
|
56
|
-
const p = localStorage.getItem(r);
|
|
57
|
-
if (p) {
|
|
58
|
-
const { data: d, timestamp: y } = JSON.parse(p);
|
|
59
|
-
if (Date.now() - y < v)
|
|
60
|
-
return d;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const n = encodeURIComponent(`user:${t} topic:${e}`), s = await fetch(`https://api.github.com/search/repositories?q=${n}&sort=updated&order=desc`);
|
|
64
|
-
if (!s.ok)
|
|
65
|
-
throw new Error(`GitHub API error: ${s.status}`);
|
|
66
|
-
const a = ((await s.json()).items || []).map(j), l = {
|
|
67
|
-
data: a,
|
|
68
|
-
timestamp: Date.now()
|
|
69
|
-
};
|
|
70
|
-
return localStorage.setItem(r, JSON.stringify(l)), a;
|
|
71
|
-
} catch (n) {
|
|
72
|
-
console.error(`GitHubPortfolio: Erro ao buscar dados de ${t}:`, n);
|
|
73
|
-
const s = localStorage.getItem(r);
|
|
74
|
-
return s ? (console.warn("GitHubPortfolio: Usando cache expirado devido a erro de rede."), JSON.parse(s).data) : [];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const h = (t) => typeof t == "string" ? document.getElementById(t) : t, g = (t, e = null) => {
|
|
78
|
-
if (!e)
|
|
79
|
-
return `
|
|
80
|
-
<button class="ghp-filter-btn active" data-topic="all">
|
|
81
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
82
|
-
<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/>
|
|
83
|
-
<rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>
|
|
84
|
-
</svg>
|
|
85
|
-
<p>Todos</p>
|
|
86
|
-
</button>
|
|
87
|
-
`;
|
|
88
|
-
const o = `https://cdn.simpleicons.org/${e.icon}/${e.color}`;
|
|
89
|
-
return `
|
|
90
|
-
<button class="ghp-filter-btn" data-topic="${t}" style="--tech-color: #${e.color}">
|
|
91
|
-
<img src="${o}" alt="${e.name}" loading="lazy">
|
|
92
|
-
<p>${e.name}</p>
|
|
93
|
-
</button>
|
|
94
|
-
`;
|
|
95
|
-
}, b = (t, e) => {
|
|
96
|
-
const o = `https://raw.githubusercontent.com/${e}/${t.name}/main/preview.png`, r = `https://opengraph.githubassets.com/${e}/${t.name}`, c = `https://placehold.co/640x360?text=${encodeURIComponent(t.name)}`, n = t.deployUrl ? `<a href="${t.deployUrl}" target="_blank" rel="noopener noreferrer">Acessar</a>` : "", s = t.htmlUrl ? `<a href="${t.htmlUrl}" target="_blank" rel="noopener noreferrer">Github</a>` : "";
|
|
97
|
-
return `
|
|
98
|
-
<div class="ghp-project-card">
|
|
99
|
-
<div class="ghp-img-container">
|
|
100
|
-
<img
|
|
101
|
-
src="${o}"
|
|
102
|
-
alt="Preview do projeto ${t.name}"
|
|
103
|
-
class="ghp-card-img"
|
|
104
|
-
loading="lazy"
|
|
105
|
-
onerror="if (this.src.includes('preview.png')) { this.src='${r}'; } else { this.onerror=null; this.src='${c}'; }"
|
|
106
|
-
>
|
|
107
|
-
</div>
|
|
108
|
-
<div class="ghp-card-content">
|
|
109
|
-
<div>
|
|
110
|
-
<h3>${t.name}</h3>
|
|
111
|
-
<p title="${t.description}">${t.description}</p>
|
|
112
|
-
</div>
|
|
113
|
-
<div class="ghp-card-links">
|
|
114
|
-
${s}
|
|
115
|
-
${n}
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
`;
|
|
120
|
-
};
|
|
121
|
-
function E(t, e = 6) {
|
|
122
|
-
const o = h(t);
|
|
123
|
-
o && (o.className = "ghp-projects-grid", o.innerHTML = Array(e).fill('<div class="ghp-project-card ghp-skeleton ghp-skeleton-card"></div>').join(""));
|
|
124
|
-
}
|
|
125
|
-
function S(t, e) {
|
|
126
|
-
const o = h(e);
|
|
127
|
-
if (!o) return;
|
|
128
|
-
const r = t.flatMap((s) => s.topics || []), n = [...new Set(r)].reduce((s, i) => {
|
|
129
|
-
const a = f[i];
|
|
130
|
-
return a ? s + g(i, a) : s;
|
|
131
|
-
}, g("all"));
|
|
132
|
-
o.innerHTML = `<div class="ghp-filters-content">${n}</div>`;
|
|
133
|
-
}
|
|
134
|
-
function m(t, e, o = "", r) {
|
|
135
|
-
const c = h(e);
|
|
136
|
-
if (c) {
|
|
137
|
-
if (c.className = "ghp-projects-grid", c.style.minHeight = "auto", t.length === 0) {
|
|
138
|
-
c.innerHTML = '<p class="ghp-empty-msg">Nenhum projeto encontrado.</p>';
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
c.innerHTML = t.map((n) => r ? r(n) : b(n, o)).join("");
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function x(t) {
|
|
145
|
-
const e = h(t);
|
|
146
|
-
e && (e.innerHTML = `
|
|
147
|
-
<div class="ghp-search-container">
|
|
148
|
-
<svg class="ghp-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
149
|
-
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
150
|
-
</svg>
|
|
151
|
-
<input type="text" id="gh-port-search" class="ghp-search-input" placeholder="Buscar projeto..." autocomplete="off">
|
|
152
|
-
</div>
|
|
153
|
-
`);
|
|
154
|
-
}
|
|
155
|
-
function k(t, e, o, r, c) {
|
|
156
|
-
const n = document.getElementById(t);
|
|
157
|
-
n && n.addEventListener("click", (s) => {
|
|
158
|
-
const a = s.target.closest(".ghp-filter-btn");
|
|
159
|
-
if (!a) return;
|
|
160
|
-
const l = a.dataset.topic, p = l === "all" ? e : e.filter((d) => d.topics.includes(l));
|
|
161
|
-
m(p, o, r, c), n.querySelectorAll(".ghp-filter-btn").forEach((d) => d.classList.remove("active")), a.classList.add("active");
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
function B(t, e, o, r) {
|
|
165
|
-
const c = document.getElementById("gh-port-search");
|
|
166
|
-
c && c.addEventListener("input", (n) => {
|
|
167
|
-
const i = n.target.value.toLowerCase(), a = t.filter(
|
|
168
|
-
(l) => l.name.toLowerCase().includes(i) || (l.description || "").toLowerCase().includes(i) || l.topics.some((p) => p.toLowerCase().includes(i))
|
|
169
|
-
);
|
|
170
|
-
m(a, e, o, r);
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
async function L(t, e = {
|
|
174
|
-
searchContainer: "search-cont",
|
|
175
|
-
filtersContainer: "filters-cont",
|
|
176
|
-
projectsContainer: "projects-cont"
|
|
177
|
-
}) {
|
|
178
|
-
try {
|
|
179
|
-
const o = e.tag || "port";
|
|
180
|
-
x(e.searchContainer), E(e.projectsContainer, 6);
|
|
181
|
-
const r = await $(t, o);
|
|
182
|
-
return S(r, e.filtersContainer), m(r, e.projectsContainer, t, e.customCardTemplate), k(e.filtersContainer, r, e.projectsContainer, t, e.customCardTemplate), B(r, e.projectsContainer, t, e.customCardTemplate), { projects: r, status: "success" };
|
|
183
|
-
} catch (o) {
|
|
184
|
-
console.error("Erro ao inicializar portfólio:", o);
|
|
185
|
-
const r = document.getElementById(e.projectsContainer);
|
|
186
|
-
return r && (r.innerHTML = '<p class="ghp-empty-msg">Erro ao carregar projetos.</p>'), { status: "error", message: o.message };
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
export {
|
|
190
|
-
$ as getPortProjects,
|
|
191
|
-
L as initPortfolio,
|
|
192
|
-
S as renderFilters,
|
|
193
|
-
m as renderProjects,
|
|
194
|
-
x as renderSearchBar
|
|
195
|
-
};
|
package/dist/snap-port.umd.cjs
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
(function(a,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(a=typeof globalThis<"u"?globalThis:a||self,p(a.SnapPort={}))})(this,(function(a){"use strict";var p=typeof document<"u"?document.currentScript:null;const f={react:{name:"React",icon:"react",color:"61DAFB"},vue:{name:"Vue.js",icon:"vuedotjs",color:"4FC08D"},angular:{name:"Angular",icon:"angular",color:"DD0031"},nextjs:{name:"Next.js",icon:"nextdotjs",color:"000000"},typescript:{name:"TypeScript",icon:"typescript",color:"3178C6"},javascript:{name:"JavaScript",icon:"javascript",color:"F7DF1E"},html5:{name:"HTML5",icon:"html5",color:"E34F26"},css3:{name:"CSS3",icon:"css3",color:"1572B6"},sass:{name:"Sass",icon:"sass",color:"CC6699"},"tailwind-css":{name:"Tailwind",icon:"tailwindcss",color:"06B6D4"},bootstrap:{name:"Bootstrap",icon:"bootstrap",color:"7952B3"},"styled-components":{name:"Styled Components",icon:"styledcomponents",color:"DB7093"},figma:{name:"Figma",icon:"figma",color:"F24E1E"},nodejs:{name:"Node.js",icon:"nodedotjs",color:"339933"},express:{name:"Express",icon:"express",color:"000000"},nestjs:{name:"NestJS",icon:"nestjs",color:"E0234E"},python:{name:"Python",icon:"python",color:"3776AB"},java:{name:"Java",icon:"openjdk",color:"007396"},php:{name:"PHP",icon:"php",color:"777BB4"},postgresql:{name:"PostgreSQL",icon:"postgresql",color:"4169E1"},mongodb:{name:"MongoDB",icon:"mongodb",color:"47A248"},mysql:{name:"MySQL",icon:"mysql",color:"4479A1"},firebase:{name:"Firebase",icon:"firebase",color:"FFCA28"},prisma:{name:"Prisma",icon:"prisma",color:"2D3748"},docker:{name:"Docker",icon:"docker",color:"2496ED"},git:{name:"Git",icon:"git",color:"F05032"},jest:{name:"Jest",icon:"jest",color:"C21325"},"github-actions":{name:"GitHub Actions",icon:"githubactions",color:"2088FF"}},y=typeof process<"u"&&process.env.NODE_ENV!=="production"||typeof{url:typeof document>"u"&&typeof location>"u"?require("url").pathToFileURL(__filename).href:typeof document>"u"?location.href:p&&p.tagName.toUpperCase()==="SCRIPT"&&p.src||new URL("snap-port.umd.cjs",document.baseURI).href}<"u"&&(void 0).MODE!=="production",w=7200*1e3,S=e=>e?e.filter(o=>!!f[o.toLocaleLowerCase().trim()]):[],$=e=>({id:e.id,name:e.name??"Projeto sem nome",description:e.description??"Sem descrição disponível",htmlUrl:e.html_url,topics:Array.isArray(e.topics)?e.topics:[],stacks:Array.isArray(e.topics)?S(e.topics):[],deployUrl:e.homepage??null});function E(e){try{Object.keys(localStorage).forEach(t=>{t.startsWith("gh_projects_")&&!t.includes(e)&&localStorage.removeItem(t)})}catch(t){console.warn("Erro ao limpar caches antigos:",t)}}async function v(e,t="port",o={}){if(!e)return console.error("GitHubPortfolio: Username é obrigatório."),[];const r=`gh_projects_${e}`,s=y||o.forceRefresh;try{if(E(e),s)y&&console.info(`[GitHubPortfolio]: Dev mode detectado. Cache ignorado para ${e}.`);else{const u=localStorage.getItem(r);if(u){const{data:h,timestamp:x}=JSON.parse(u);if(Date.now()-x<w)return h}}const n=encodeURIComponent(`user:${e} topic:${t}`),c=await fetch(`https://api.github.com/search/repositories?q=${n}&sort=updated&order=desc`);if(!c.ok)throw new Error(`GitHub API error: ${c.status}`);const i=((await c.json()).items||[]).map($),d={data:i,timestamp:Date.now()};return localStorage.setItem(r,JSON.stringify(d)),i}catch(n){console.error(`GitHubPortfolio: Erro ao buscar dados de ${e}:`,n);const c=localStorage.getItem(r);return c?(console.warn("GitHubPortfolio: Usando cache expirado devido a erro de rede."),JSON.parse(c).data):[]}}const m=e=>typeof e=="string"?document.getElementById(e):e,C=(e,t=null)=>{if(!t)return`
|
|
2
|
-
<button class="ghp-filter-btn active" data-topic="all">
|
|
3
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
4
|
-
<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/>
|
|
5
|
-
<rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>
|
|
6
|
-
</svg>
|
|
7
|
-
<p>Todos</p>
|
|
8
|
-
</button>
|
|
9
|
-
`;const o=`https://cdn.simpleicons.org/${t.icon}/${t.color}`;return`
|
|
10
|
-
<button class="ghp-filter-btn" data-topic="${e}" style="--tech-color: #${t.color}">
|
|
11
|
-
<img src="${o}" alt="${t.name}" loading="lazy">
|
|
12
|
-
<p>${t.name}</p>
|
|
13
|
-
</button>
|
|
14
|
-
`},P=(e,t)=>{const o=`https://raw.githubusercontent.com/${t}/${e.name}/main/preview.png`,r=`https://opengraph.githubassets.com/${t}/${e.name}`,s=`https://placehold.co/640x360?text=${encodeURIComponent(e.name)}`,n=e.deployUrl?`<a href="${e.deployUrl}" target="_blank" rel="noopener noreferrer">Acessar</a>`:"",c=e.htmlUrl?`<a href="${e.htmlUrl}" target="_blank" rel="noopener noreferrer">Github</a>`:"";return`
|
|
15
|
-
<div class="ghp-project-card">
|
|
16
|
-
<div class="ghp-img-container">
|
|
17
|
-
<img
|
|
18
|
-
src="${o}"
|
|
19
|
-
alt="Preview do projeto ${e.name}"
|
|
20
|
-
class="ghp-card-img"
|
|
21
|
-
loading="lazy"
|
|
22
|
-
onerror="if (this.src.includes('preview.png')) { this.src='${r}'; } else { this.onerror=null; this.src='${s}'; }"
|
|
23
|
-
>
|
|
24
|
-
</div>
|
|
25
|
-
<div class="ghp-card-content">
|
|
26
|
-
<div>
|
|
27
|
-
<h3>${e.name}</h3>
|
|
28
|
-
<p title="${e.description}">${e.description}</p>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="ghp-card-links">
|
|
31
|
-
${c}
|
|
32
|
-
${n}
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
`};function T(e,t=6){const o=m(e);o&&(o.className="ghp-projects-grid",o.innerHTML=Array(t).fill('<div class="ghp-project-card ghp-skeleton ghp-skeleton-card"></div>').join(""))}function j(e,t){const o=m(t);if(!o)return;const r=e.flatMap(c=>c.topics||[]),n=[...new Set(r)].reduce((c,l)=>{const i=f[l];return i?c+C(l,i):c},C("all"));o.innerHTML=`<div class="ghp-filters-content">${n}</div>`}function g(e,t,o="",r){const s=m(t);if(s){if(s.className="ghp-projects-grid",s.style.minHeight="auto",e.length===0){s.innerHTML='<p class="ghp-empty-msg">Nenhum projeto encontrado.</p>';return}s.innerHTML=e.map(n=>r?r(n):P(n,o)).join("")}}function b(e){const t=m(e);t&&(t.innerHTML=`
|
|
37
|
-
<div class="ghp-search-container">
|
|
38
|
-
<svg class="ghp-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
39
|
-
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
40
|
-
</svg>
|
|
41
|
-
<input type="text" id="gh-port-search" class="ghp-search-input" placeholder="Buscar projeto..." autocomplete="off">
|
|
42
|
-
</div>
|
|
43
|
-
`)}function k(e,t,o,r,s){const n=document.getElementById(e);n&&n.addEventListener("click",c=>{const i=c.target.closest(".ghp-filter-btn");if(!i)return;const d=i.dataset.topic,u=d==="all"?t:t.filter(h=>h.topics.includes(d));g(u,o,r,s),n.querySelectorAll(".ghp-filter-btn").forEach(h=>h.classList.remove("active")),i.classList.add("active")})}function B(e,t,o,r){const s=document.getElementById("gh-port-search");s&&s.addEventListener("input",n=>{const l=n.target.value.toLowerCase(),i=e.filter(d=>d.name.toLowerCase().includes(l)||(d.description||"").toLowerCase().includes(l)||d.topics.some(u=>u.toLowerCase().includes(l)));g(i,t,o,r)})}async function L(e,t={searchContainer:"search-cont",filtersContainer:"filters-cont",projectsContainer:"projects-cont"}){try{const o=t.tag||"port";b(t.searchContainer),T(t.projectsContainer,6);const r=await v(e,o);return j(r,t.filtersContainer),g(r,t.projectsContainer,e,t.customCardTemplate),k(t.filtersContainer,r,t.projectsContainer,e,t.customCardTemplate),B(r,t.projectsContainer,e,t.customCardTemplate),{projects:r,status:"success"}}catch(o){console.error("Erro ao inicializar portfólio:",o);const r=document.getElementById(t.projectsContainer);return r&&(r.innerHTML='<p class="ghp-empty-msg">Erro ao carregar projetos.</p>'),{status:"error",message:o.message}}}a.getPortProjects=v,a.initPortfolio=L,a.renderFilters=j,a.renderProjects=g,a.renderSearchBar=b,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
|
|
File without changes
|