Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { remarkReadingTime } from './src/plugins/remark-reading-time.mjs';
export default defineConfig({
site: 'https://spicetify.app',
trailingSlash: 'never',
// `<ClientRouter />` defaults to prefetch-all links, which spams fetches and can make
// the tab title / URL bar look like they’re constantly updating. Opt out; navigations
// are still client-side on click.
prefetch: false,
build: {
format: 'file',
},
Expand Down
68 changes: 40 additions & 28 deletions src/components/Navbar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -151,37 +151,49 @@ const isBlogActive = currentPath.startsWith('/blog');
</header>

<script>
function setupThemeToggle() {
const toggle = document.getElementById('theme-toggle');
toggle?.addEventListener('click', () => {
const current = document.documentElement.dataset.theme;
const next = current === 'dark' ? 'light' : 'dark';
if (window.__setTheme) {
window.__setTheme(next);
} else {
document.documentElement.dataset.theme = next;
}
localStorage.setItem('theme', next);
});
}
let navListenersAbort = new AbortController();

function setupNavbarInteractions() {
navListenersAbort.abort();
navListenersAbort = new AbortController();
const { signal } = navListenersAbort;

const themeToggle = document.getElementById('theme-toggle');
themeToggle?.addEventListener(
'click',
() => {
const current = document.documentElement.dataset.theme;
const next = current === 'dark' ? 'light' : 'dark';
if (window.__setTheme) {
window.__setTheme(next);
} else {
document.documentElement.dataset.theme = next;
}
localStorage.setItem('theme', next);
},
{ signal },
);

function setupMobileMenu() {
const toggle = document.querySelector('.mobile-menu-toggle');
toggle?.addEventListener('click', () => {
const sidebar = document.querySelector('.docs-sidebar');
const mobileNav = document.querySelector('.mobile-nav');
const expanded = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', String(!expanded));
if (sidebar) {
sidebar.classList.toggle('open');
} else if (mobileNav) {
mobileNav.classList.toggle('open');
}
});
const menuToggle = document.querySelector('.mobile-menu-toggle');
menuToggle?.addEventListener(
'click',
() => {
const sidebar = document.querySelector('.docs-sidebar');
const mobileNav = document.querySelector('.mobile-nav');
const expanded = menuToggle.getAttribute('aria-expanded') === 'true';
menuToggle.setAttribute('aria-expanded', String(!expanded));
if (sidebar) {
sidebar.classList.toggle('open');
} else if (mobileNav) {
mobileNav.classList.toggle('open');
}
},
{ signal },
);
}

setupThemeToggle();
setupMobileMenu();
setupNavbarInteractions();
document.addEventListener('astro:page-load', setupNavbarInteractions);
</script>

<style>
Expand Down
168 changes: 100 additions & 68 deletions src/components/SearchModal.astro
Original file line number Diff line number Diff line change
Expand Up @@ -29,77 +29,102 @@
</div>

<script is:inline>
function setupSearch() {
const modal = document.getElementById('search-modal');
const input = document.getElementById('search-input');
const resultsContainer = document.getElementById('search-results');
const backdrop = modal?.querySelector('.search-backdrop');
let pagefind = null;
let pagefindLoading = null;
let debounceTimer;
let searchCounter = 0;
/* One-time document listeners: survives Astro client navigations (body swap)
where this inline script would not run again. */
(function () {
if (window.__spicetifySearchInit) return;
window.__spicetifySearchInit = true;

var pagefind = null;
var pagefindLoading = null;
var debounceTimer;
var searchCounter = 0;

function getEls() {
return {
modal: document.getElementById('search-modal'),
input: document.getElementById('search-input'),
resultsContainer: document.getElementById('search-results'),
};
}

async function loadPagefind() {
if (pagefind) return pagefind;
if (pagefindLoading) return pagefindLoading;
pagefindLoading = import(/* @vite-ignore */ '/pagefind/pagefind.js')
.then(function(pf) { return pf.init().then(function() { pagefind = pf; return pf; }); })
.catch(function() { pagefindLoading = null; return null; });
.then(function (pf) {
return pf.init().then(function () {
pagefind = pf;
return pf;
});
})
.catch(function () {
pagefindLoading = null;
return null;
});
return pagefindLoading;
}

function openModal() {
if (!modal) return;
modal.setAttribute('aria-hidden', 'false');
var els = getEls();
if (!els.modal) return;
els.modal.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
input?.focus();
els.input?.focus();
loadPagefind();
}

function closeModal() {
if (!modal) return;
modal.setAttribute('aria-hidden', 'true');
var els = getEls();
if (!els.modal) return;
els.modal.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
clearTimeout(debounceTimer);
searchCounter++;
if (input) input.value = '';
if (resultsContainer) resultsContainer.innerHTML = '';
if (els.input) els.input.value = '';
if (els.resultsContainer) els.resultsContainer.innerHTML = '';
}

function isOpen() {
return modal?.getAttribute('aria-hidden') === 'false';
return getEls().modal?.getAttribute('aria-hidden') === 'false';
}

async function performSearch(query) {
if (!resultsContainer) return;
var els = getEls();
if (!els.resultsContainer) return;
if (!query.trim()) {
resultsContainer.innerHTML = '<div class="search-empty">Start typing to search...</div>';
els.resultsContainer.innerHTML =
'<div class="search-empty">Start typing to search...</div>';
return;
}

var thisSearch = ++searchCounter;

var pf = await loadPagefind();
if (!pf) {
resultsContainer.innerHTML = '<div class="search-empty">Search unavailable</div>';
els.resultsContainer.innerHTML =
'<div class="search-empty">Search unavailable</div>';
return;
}

var search = await pf.search(query);
if (thisSearch !== searchCounter) return;

if (search.results.length === 0) {
resultsContainer.innerHTML = '<div class="search-empty">No results found</div>';
els.resultsContainer.innerHTML =
'<div class="search-empty">No results found</div>';
return;
}

var items = await Promise.all(
search.results.slice(0, 10).map(function(r) { return r.data(); }),
search.results.slice(0, 10).map(function (r) {
return r.data();
}),
);
if (thisSearch !== searchCounter) return;

resultsContainer.innerHTML = '';
items.forEach(function(item) {
els.resultsContainer.innerHTML = '';
items.forEach(function (item) {
var url = item.url.replace(/\.html$/, '');
var link = document.createElement('a');
link.href = url;
Expand All @@ -116,70 +141,77 @@
excerptEl.innerHTML = item.excerpt;
link.appendChild(excerptEl);

resultsContainer.appendChild(link);
els.resultsContainer.appendChild(link);
});
}

document.addEventListener('keydown', (e) => {
document.addEventListener('keydown', function (e) {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
if (isOpen()) {
closeModal();
} else {
openModal();
}
if (isOpen()) closeModal();
else openModal();
}
if (e.key === 'Escape' && isOpen()) closeModal();
});

document.addEventListener('click', function (e) {
var t = e.target;
if (t && t.closest && t.closest('[data-search-trigger]')) {
e.preventDefault();
openModal();
return;
}
if (e.key === 'Escape' && isOpen()) {
if (t && t.closest && t.closest('.search-backdrop')) {
closeModal();
}
});

backdrop?.addEventListener('click', closeModal);

input?.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
performSearch(input.value);
}, 200);
document.addEventListener('input', function (e) {
if (e.target && e.target.id === 'search-input') {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
performSearch(e.target.value);
}, 200);
}
});

input?.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
document.addEventListener('keydown', function (e) {
var els = getEls();
if (e.target === els.input && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
e.preventDefault();
const results = resultsContainer?.querySelectorAll('.search-result');
var results = els.resultsContainer?.querySelectorAll('.search-result');
if (!results?.length) return;
const focused = resultsContainer?.querySelector('.search-result:focus');
let index = focused ? Array.from(results).indexOf(focused) : -1;
var focused = els.resultsContainer?.querySelector('.search-result:focus');
var index = focused ? Array.from(results).indexOf(focused) : -1;
if (e.key === 'ArrowDown') index = Math.min(index + 1, results.length - 1);
else index = Math.max(index - 1, 0);
results[index].focus();
return;
}
});

resultsContainer?.addEventListener('keydown', (e) => {
const target = e.target;
if (e.key === 'Enter' && target.classList.contains('search-result')) {
target.click();
}
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
const results = resultsContainer.querySelectorAll('.search-result');
const index = Array.from(results).indexOf(target);
if (e.key === 'ArrowDown' && index < results.length - 1) {
results[index + 1].focus();
} else if (e.key === 'ArrowUp') {
if (index === 0) input?.focus();
else results[index - 1].focus();
if (
e.target &&
e.target.classList &&
e.target.classList.contains('search-result')
) {
if (e.key === 'Enter') {
e.target.click();
}
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
if (!els.resultsContainer) return;
var res = els.resultsContainer.querySelectorAll('.search-result');
var idx = Array.from(res).indexOf(e.target);
if (e.key === 'ArrowDown' && idx < res.length - 1) {
res[idx + 1].focus();
} else if (e.key === 'ArrowUp') {
if (idx === 0) els.input?.focus();
else res[idx - 1].focus();
}
}
}
});

document.querySelectorAll('[data-search-trigger]').forEach((btn) => {
btn.addEventListener('click', openModal);
});
}

setupSearch();
})();
</script>

<style>
Expand Down
1 change: 1 addition & 0 deletions src/components/Tabs.astro
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,5 @@ const { groupId } = Astro.props;
}

initTabs();
document.addEventListener('astro:page-load', initTabs);
</script>
9 changes: 9 additions & 0 deletions src/components/homepage/InstallSection.astro
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ type Props = {};

<script is:inline>
(function () {
function bindInstallSection() {
var root = document.querySelector('.install-section');
if (!root || root.dataset.interactiveInit) return;
root.dataset.interactiveInit = '1';

function setupInstallTabs() {
var tabs = Array.from(document.querySelectorAll('.install-tab'));
var panels = document.querySelectorAll('.install-panel');
Expand Down Expand Up @@ -164,6 +169,10 @@ type Props = {};

setupInstallTabs();
setupCopyButton();
}

document.addEventListener('astro:page-load', bindInstallSection);
bindInstallSection();
})();
</script>

Expand Down
Loading