+

@@ -188,7 +195,11 @@ (function () { 'use strict'; var CHANNELS = ['stable', 'edge']; - var state = { apps: [], cat: 'all', q: '' }; + function readFocus() { + var h = (window.location.hash || '').replace(/^#/, ''); + return /^[a-z0-9][a-z0-9_]{0,31}$/.test(h) ? h : ''; + } + var state = { apps: [], cat: 'all', q: '', focus: readFocus() }; var grid = document.getElementById('grid'); var cats = document.getElementById('cats'); var empty = document.getElementById('empty'); @@ -254,15 +265,31 @@ function render() { var q = state.q.toLowerCase(); + var focus = state.focus; // # deep-link → show just that app var shown = state.apps.filter(function (a) { + if (focus) return a.slug === focus; if (state.cat !== 'all' && a.category !== state.cat) return false; if (q && (a.title + ' ' + a.description + ' ' + a.long_description + ' ' + a.slug).toLowerCase().indexOf(q) < 0) return false; return true; }); + var fb = document.getElementById('focusbar'); + if (focus) { + var one = state.apps.filter(function (a) { return a.slug === focus; })[0]; + fb.innerHTML = 'Linked from the marketplace: ' + esc(one ? one.title : focus) + '' + + ''; + fb.hidden = false; + } else { fb.hidden = true; fb.innerHTML = ''; } grid.innerHTML = shown.map(card).join(''); grid.style.display = shown.length ? '' : 'none'; empty.hidden = shown.length > 0; - if (state.q || state.cat !== 'all') { empty.textContent = 'Nothing matches your filter.'; } + if (focus && !shown.length) empty.textContent = 'That app isn’t on this channel.'; + else if (state.q || state.cat !== 'all') empty.textContent = 'Nothing matches your filter.'; + } + + function clearFocus() { + if (!state.focus) return; + state.focus = ''; + if (window.location.hash) { try { history.replaceState(null, '', window.location.pathname + window.location.search); } catch (_) { window.location.hash = ''; } } } function setStatus(idx, ch, signed) { @@ -308,8 +335,12 @@ cats.addEventListener('click', function (e) { var el = e.target.closest('[data-cat]'); if (!el) return; - state.cat = el.getAttribute('data-cat'); renderCats(); render(); + clearFocus(); state.cat = el.getAttribute('data-cat'); renderCats(); render(); }); + document.getElementById('focusbar').addEventListener('click', function (e) { + if (e.target.closest('#showall')) { clearFocus(); render(); } + }); + window.addEventListener('hashchange', function () { state.focus = readFocus(); render(); }); grid.addEventListener('click', function (e) { var b = e.target.closest('button[data-slug]'); if (!b) return; var txt = 'libreportal app add ' + b.getAttribute('data-slug'); @@ -317,7 +348,7 @@ if (navigator.clipboard) navigator.clipboard.writeText(txt).then(done, function () { b.textContent = txt; }); else done(); }); - document.getElementById('q').addEventListener('input', function (e) { state.q = e.target.value; render(); }); + document.getElementById('q').addEventListener('input', function (e) { clearFocus(); state.q = e.target.value; render(); }); load(); })(); diff --git a/scripts/webui/data/generators/apps/webui_registry_scan.sh b/scripts/webui/data/generators/apps/webui_registry_scan.sh index ca2c7c0..bf3edd1 100644 --- a/scripts/webui/data/generators/apps/webui_registry_scan.sh +++ b/scripts/webui/data/generators/apps/webui_registry_scan.sh @@ -92,10 +92,12 @@ webuiRegistryCatalogScan() { local tmp; tmp="$(mktemp)" printf '%s' "$index" | jq \ --arg now "$now" --arg signed "$signed" --arg serial "${serial:-0}" \ + --arg src_base "$base" --arg src_channel "$(lpReleaseChannel)" \ --argjson defined "$defined" --argjson installed "$installed" --argjson icons "$icons_map" ' { generated_at: $now, signed: ($signed=="true"), serial: ($serial|tonumber? // 0), + source: { base: $src_base, channel: $src_channel }, apps: [ .artifacts[]? | select(.type=="app" and .payload.kind=="bundle") | (.applies_when.app // "") as $slug | select($slug != "") | { id,