Skip to content

Commit 614d1ac

Browse files
feat: add link to authors on app page
1 parent 53c83a3 commit 614d1ac

2 files changed

Lines changed: 136 additions & 23 deletions

File tree

site/script.js

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,19 @@ class LilkaRepository {
3535
// Handle authors page
3636
if (type === 'authors') {
3737
this.currentType = type;
38+
const authorParam = params.get('author');
3839
await this.showAuthors();
3940
this.updateActiveTab('authors');
41+
if (authorParam) {
42+
const sectionId = `author-${this.authorSlug(authorParam)}`;
43+
const section = document.getElementById(sectionId);
44+
if (section) {
45+
section.classList.remove('collapsed');
46+
section.scrollIntoView({behavior: 'smooth', block: 'start'});
47+
section.classList.add('author-highlight');
48+
setTimeout(() => section.classList.remove('author-highlight'), 2000);
49+
}
50+
}
4051
return;
4152
}
4253

@@ -60,9 +71,12 @@ class LilkaRepository {
6071
document.querySelector(`[data-type="${type}"]`).classList.add('active');
6172

6273
// Show/hide appropriate content
63-
document.getElementById('content').style.display = (type === 'docs' || type === 'authors') ? 'none' : 'block';
64-
document.getElementById('docs').style.display = type === 'docs' ? 'block' : 'none';
65-
document.getElementById('authors').style.display = type === 'authors' ? 'block' : 'none';
74+
document.getElementById('content').style.display =
75+
(type === 'docs' || type === 'authors') ? 'none' : 'block';
76+
document.getElementById('docs').style.display =
77+
type === 'docs' ? 'block' : 'none';
78+
document.getElementById('authors').style.display =
79+
type === 'authors' ? 'block' : 'none';
6680
}
6781

6882
async openDirectItem(type, itemName) {
@@ -269,15 +283,17 @@ class LilkaRepository {
269283
});
270284

271285
// Collapsible author sections
272-
authorsContainer.querySelectorAll('.author-section-header').forEach(header => {
273-
header.addEventListener('click', () => {
274-
const section = header.closest('.author-section');
275-
section.classList.toggle('collapsed');
276-
});
277-
});
286+
authorsContainer.querySelectorAll('.author-section-header')
287+
.forEach(header => {
288+
header.addEventListener('click', () => {
289+
const section = header.closest('.author-section');
290+
section.classList.toggle('collapsed');
291+
});
292+
});
278293
} catch (error) {
279294
authorsContainer.innerHTML =
280-
`<p style="color: var(--error);">Failed to load authors: ${error.message}</p>`;
295+
`<p style="color: var(--error);">Failed to load authors: ${
296+
error.message}</p>`;
281297
}
282298
}
283299

@@ -290,28 +306,38 @@ class LilkaRepository {
290306
const apps = items.filter(i => i.type === 'apps');
291307
const mods = items.filter(i => i.type === 'mods');
292308
const badge = [];
293-
if (apps.length) badge.push(`${apps.length} app${apps.length > 1 ? 's' : ''}`);
294-
if (mods.length) badge.push(`${mods.length} mod${mods.length > 1 ? 's' : ''}`);
309+
if (apps.length)
310+
badge.push(`${apps.length} app${apps.length > 1 ? 's' : ''}`);
311+
if (mods.length)
312+
badge.push(`${mods.length} mod${mods.length > 1 ? 's' : ''}`);
295313

296-
html += `<div class="author-section">`;
314+
const sectionId = `author-${this.authorSlug(author)}`;
315+
html += `<div class="author-section" id="${sectionId}">`;
297316
html += `<div class="author-section-header">`;
298-
html += `<span class="author-section-name">${this.escapeHtml(author)}</span>`;
317+
html +=
318+
`<span class="author-section-name">${this.escapeHtml(author)}</span>`;
299319
html += `<span class="author-section-badge">${badge.join(', ')}</span>`;
300320
html += `<span class="author-section-toggle">&#9660;</span>`;
301321
html += `</div>`;
302322
html += `<div class="author-section-body">`;
303323
html += `<div class="author-items-grid">`;
304324

305325
for (const item of items) {
306-
const iconPath = item.icon ? `${item.type}/${item.path}/static/${item.icon}` : '';
326+
const iconPath =
327+
item.icon ? `${item.type}/${item.path}/static/${item.icon}` : '';
307328
const typeLabel = item.type === 'apps' ? 'App' : 'Mod';
308-
html += `<div class="author-item-card" data-item-type="${item.type}" data-item-path="${this.escapeHtml(item.path)}">`;
329+
html += `<div class="author-item-card" data-item-type="${
330+
item.type}" data-item-path="${this.escapeHtml(item.path)}">`;
309331
if (item.icon) {
310-
html += `<img src="${iconPath}" alt="${this.escapeHtml(item.name)}" class="icon" onerror="this.style.display='none'">`;
332+
html += `<img src="${iconPath}" alt="${
333+
this.escapeHtml(
334+
item.name)}" class="icon" onerror="this.style.display='none'">`;
311335
}
312336
html += `<h3>${this.escapeHtml(item.name)}</h3>`;
313-
html += `<span class="author-item-type type-${item.type}">${typeLabel}</span>`;
314-
html += `<div class="short-desc">${this.escapeHtml(item.short_description)}</div>`;
337+
html += `<span class="author-item-type type-${item.type}">${
338+
typeLabel}</span>`;
339+
html += `<div class="short-desc">${
340+
this.escapeHtml(item.short_description)}</div>`;
315341
html += `</div>`;
316342
}
317343

@@ -423,6 +449,7 @@ class LilkaRepository {
423449
const iconPath =
424450
`${this.currentType}/${manifestName}/static/${manifest.icon}`;
425451

452+
const authorId = this.authorSlug(manifest.author);
426453
card.innerHTML = `
427454
${
428455
manifest.icon ?
@@ -431,11 +458,25 @@ class LilkaRepository {
431458
.name}" class="icon" onerror="this.style.display='none'">` :
432459
''}
433460
<h3>${this.escapeHtml(manifest.name)}</h3>
434-
<div class="author">${this.escapeHtml(manifest.author)}</div>
461+
<div class="author"><a href="?type=authors&author=${
462+
encodeURIComponent(
463+
manifest.author)}" class="author-link" data-author="${
464+
this.escapeHtml(
465+
manifest.author)}">${this.escapeHtml(manifest.author)}</a></div>
435466
<div class="short-desc">${
436467
this.escapeHtml(manifest.short_description)}</div>
437468
`;
438469

470+
// Author link click — navigate to authors page
471+
const authorLink = card.querySelector('.author-link');
472+
if (authorLink) {
473+
authorLink.addEventListener('click', (e) => {
474+
e.preventDefault();
475+
e.stopPropagation();
476+
this.navigateToAuthor(manifest.author);
477+
});
478+
}
479+
439480
card.addEventListener('click', () => {
440481
// Track card clicks to view details
441482
if (window.umami) {
@@ -564,8 +605,11 @@ class LilkaRepository {
564605
filesSection += `
565606
<div class="modal-section">
566607
<h3>📦 Package ZIP</h3>
567-
<p><strong>File:</strong> ${this.escapeHtml(manifest.package)}</p>
568-
<a href="${packageDownloadPath}" download class="download-btn" data-umami-event="download-package-zip" data-umami-event-item="${this.escapeHtml(manifest.name)}">⬇️ Download ZIP</a>
608+
<p><strong>File:</strong> ${
609+
this.escapeHtml(manifest.package)}</p>
610+
<a href="${
611+
packageDownloadPath}" download class="download-btn" data-umami-event="download-package-zip" data-umami-event-item="${
612+
this.escapeHtml(manifest.name)}">⬇️ Download ZIP</a>
569613
</div>
570614
`;
571615
}
@@ -629,7 +673,11 @@ class LilkaRepository {
629673
modalBody.innerHTML = `
630674
<div class="modal-header">
631675
<h2>${this.escapeHtml(manifest.name)}</h2>
632-
<div class="author">${this.escapeHtml(manifest.author)}</div>
676+
<div class="author"><a href="?type=authors&author=${
677+
encodeURIComponent(
678+
manifest.author)}" class="author-link" data-author="${
679+
this.escapeHtml(
680+
manifest.author)}">${this.escapeHtml(manifest.author)}</a></div>
633681
</div>
634682
${
635683
manifest.icon ?
@@ -680,6 +728,16 @@ class LilkaRepository {
680728
this.openLightbox(index);
681729
});
682730
});
731+
732+
// Add click handler for author link in modal
733+
const modalAuthorLink = modalBody.querySelector('.author-link');
734+
if (modalAuthorLink) {
735+
modalAuthorLink.addEventListener('click', (e) => {
736+
e.preventDefault();
737+
e.stopPropagation();
738+
this.navigateToAuthor(modalAuthorLink.dataset.author);
739+
});
740+
}
683741
}, 100);
684742
}
685743

@@ -839,6 +897,36 @@ class LilkaRepository {
839897
div.textContent = text;
840898
return div.innerHTML;
841899
}
900+
901+
authorSlug(author) {
902+
return (author || '').replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
903+
}
904+
905+
async navigateToAuthor(author) {
906+
// Close modal if open
907+
document.getElementById('modal').style.display = 'none';
908+
909+
// Load authors page
910+
await this.showAuthors();
911+
912+
// Scroll to the author section & highlight it
913+
const sectionId = `author-${this.authorSlug(author)}`;
914+
const section = document.getElementById(sectionId);
915+
if (section) {
916+
// Make sure it's not collapsed
917+
section.classList.remove('collapsed');
918+
section.scrollIntoView({behavior: 'smooth', block: 'start'});
919+
section.classList.add('author-highlight');
920+
setTimeout(() => section.classList.remove('author-highlight'), 2000);
921+
}
922+
923+
// Update URL
924+
const params = new URLSearchParams();
925+
params.set('type', 'authors');
926+
params.set('author', author);
927+
window.history.pushState(
928+
{type: 'authors', author}, '', `?${params.toString()}`);
929+
}
842930
}
843931

844932
// Initialize the app when DOM is ready

site/styles.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,31 @@ footer {
822822
color: var(--success);
823823
}
824824

825+
/* Author link on cards & modal */
826+
.author-link {
827+
color: var(--primary-color);
828+
text-decoration: none;
829+
cursor: pointer;
830+
transition: color 0.2s ease, text-decoration 0.2s ease;
831+
}
832+
833+
.author-link:hover {
834+
text-decoration: underline;
835+
color: var(--secondary-color);
836+
}
837+
838+
/* Highlight animation when scrolling to author */
839+
.author-highlight {
840+
animation: authorPulse 2s ease;
841+
}
842+
843+
@keyframes authorPulse {
844+
0% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.5); }
845+
30% { box-shadow: 0 0 0 6px rgba(99, 102, 241, 0.3); }
846+
70% { box-shadow: 0 0 0 6px rgba(99, 102, 241, 0.15); }
847+
100% { box-shadow: none; }
848+
}
849+
825850
@media (max-width: 768px) {
826851
.author-items-grid {
827852
grid-template-columns: 1fr;

0 commit comments

Comments
 (0)