From 4a77e26689ae2f2fc7c5084895a9754b29389d2d Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Thu, 22 Jan 2026 17:45:41 -0500 Subject: [PATCH 01/15] try again --- js/index.js | 116 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 92 insertions(+), 24 deletions(-) diff --git a/js/index.js b/js/index.js index 5c1369f..addb244 100644 --- a/js/index.js +++ b/js/index.js @@ -211,9 +211,8 @@ if (Const.APP_DATES_CSV) httpGet(Const.APP_DATES_CSV).then(csv=>{ appSortInfo[key].created = parseDate(l[1]); appSortInfo[key].modified = parseDate(l[2]); }); - document.querySelector(".sort-nav").classList.remove("hidden"); - document.querySelector(".sort-nav label[sortid='created']").classList.remove("hidden"); - document.querySelector(".sort-nav label[sortid='modified']").classList.remove("hidden"); + document.querySelector(".sort-nav a[sortid='created']").parentElement.classList.remove("hidden"); + document.querySelector(".sort-nav a[sortid='modified']").parentElement.classList.remove("hidden"); }).catch(err=>{ console.log("No recent.csv - app sort disabled"); }); @@ -240,9 +239,8 @@ if (Const.APP_USAGE_JSON) httpGet(Const.APP_USAGE_JSON).then(jsonTxt=>{ if (json.app[key] > appCounts.installs) appCounts.installs = json.app[key]; appSortInfo[key].installs = json.app[key]; }); - document.querySelector(".sort-nav").classList.remove("hidden"); - document.querySelector(".sort-nav label[sortid='installs']").classList.remove("hidden"); - document.querySelector(".sort-nav label[sortid='favourites']").classList.remove("hidden"); + document.querySelector(".sort-nav a[sortid='installs']").parentElement.classList.remove("hidden"); + document.querySelector(".sort-nav a[sortid='favourites']").parentElement.classList.remove("hidden"); // actually set to sort on favourites if (activeSort != "favourites") { activeSort = "favourites"; @@ -644,8 +642,9 @@ function getAppHTML(app, appInstalled, forInterface) { // =========================================== Library -// Can't use chip.attributes.filterid.value here because Safari/Apple's WebView doesn't handle it -let chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip => chip.getAttribute("filterid")); +// Initialize filter and sort dropdown references +let filterNav = document.querySelector('.filter-nav'); +let sortNav = document.querySelector('.sort-nav'); /* Filter types: @@ -664,9 +663,23 @@ let libraryShowAll = false; // perist whether user chose to view all apps // Update the sort state to match the current sort value function refreshSort(){ let sortContainer = document.querySelector("#librarycontainer .sort-nav"); - sortContainer.querySelector('.active').classList.remove('active'); - if(activeSort) sortContainer.querySelector('.chip[sortid="'+activeSort+'"]').classList.add('active'); - else sortContainer.querySelector('.chip[sortid]').classList.add('active'); + let sortToggle = sortContainer.querySelector('.dropdown-toggle span'); + let sortAnchors = sortContainer.querySelectorAll('.menu-item a'); + + // Find the currently selected sort and update label + let activeAnchor = Array.from(sortAnchors).find(a => + a.getAttribute('sortid') === (activeSort || '') + ); + + if (activeAnchor && sortToggle) { + sortToggle.innerHTML = ''; + sortToggle.innerHTML += ''; + if (activeSort === '') { + sortToggle.innerHTML += `None`; + } else { + sortToggle.innerHTML += activeAnchor.textContent; + } + } } function handlefavouriteClick(icon,app,button){ const favAnimMS = 500; // duration of favourite animation in ms @@ -720,13 +733,21 @@ function refreshLibrary(options) { searchValue = ""; searchChip = searchValue; } - // Update the 'chips' to match the current window location - let filtersContainer = document.querySelector("#librarycontainer .filter-nav"); - filtersContainer.querySelector('.active').classList.remove('active'); - if(searchChip) { - let hashFilter = filtersContainer.querySelector('.chip[filterid="'+searchChip+'"]'); - if (hashFilter) hashFilter.classList.add('active'); - } else filtersContainer.querySelector('.chip[filterid]').classList.add('active'); +let filtersContainer = document.querySelector("#librarycontainer .filter-nav"); +let filterToggle = filtersContainer.querySelector('.dropdown-toggle span'); +let filterAnchors = filtersContainer.querySelectorAll('.menu-item a'); + +// Find the currently selected filter and update label +let activeFilterAnchor = Array.from(filterAnchors).find(a => + a.getAttribute('dt') === (searchChip || '') +); + +if (activeFilterAnchor && filterToggle) { + filterToggle.innerHTML = ''; + filterToggle.innerHTML += ''; + filterToggle.innerHTML += activeFilterAnchor.textContent; + +} // update the search box value if (!options.dontChangeSearchBox) { if (searchType === "full") @@ -1363,19 +1384,21 @@ connectMyDeviceBtn.addEventListener("click", () => { }); Comms.watchConnectionChange(handleConnectionChange); -// Handle the 'chips' let filtersContainer = document.querySelector("#librarycontainer .filter-nav"); filtersContainer.addEventListener('click', ({ target }) => { - if (target.classList.contains('active')) return; - let filterName = target.getAttribute('filterid') || ''; - // Update window URL + // Only handle anchor clicks in menu items + if (target.tagName !== 'A' || !target.hasAttribute('dt')) return; + + let filterName = target.getAttribute('dt') || ''; window.history.replaceState(null, null, "?c=" + filterName); refreshLibrary(); }); let sortContainer = document.querySelector("#librarycontainer .sort-nav"); sortContainer.addEventListener('click', ({ target }) => { - if (target.classList.contains('active')) return; + // Only handle anchor clicks in menu items + if (target.tagName !== 'A' || !target.hasAttribute('sortid')) return; + activeSort = target.getAttribute('sortid') || ''; refreshSort(); refreshLibrary(); @@ -1428,6 +1451,51 @@ settingsCheckbox("settings-autoReload", "autoReload"); settingsCheckbox("settings-nopacket", "noPackets"); loadSettings(); + +function autoAlignMenu(dropdown) { + const menu = dropdown.querySelector('.menu'); + if (!menu) return; + + // Ensure we can measure even if hidden + const prevVis = menu.style.visibility; + const prevDisp = menu.style.display; + const cs = getComputedStyle(menu); + if (cs.display === 'none') { + menu.style.visibility = 'hidden'; + menu.style.display = 'block'; + } + + // Reset to left before measuring + menu.classList.remove('align-right'); + + const rect = menu.getBoundingClientRect(); + const overflowRight = rect.right > (window.innerWidth - 8); // 8px margin + if (overflowRight) menu.classList.add('align-right'); + + // Restore styles + menu.style.visibility = prevVis || ''; + menu.style.display = prevDisp || ''; +} + +// Flip on open +document.addEventListener('click', (e) => { + const toggle = e.target.closest('.dropdown-toggle'); + if (!toggle) return; + const dropdown = toggle.closest('.dropdown'); + if (!dropdown) return; + + // Let the framework open the menu, then align + requestAnimationFrame(() => autoAlignMenu(dropdown)); +}); + +// Keep alignment on resize +window.addEventListener('resize', () => { + document.querySelectorAll('.dropdown .menu').forEach(menu => { + const dropdown = menu.closest('.dropdown'); + if (dropdown) autoAlignMenu(dropdown); + }); +}); + let btn; btn = document.getElementById("defaultsettings"); @@ -1551,4 +1619,4 @@ if (btn) btn.addEventListener("click",event=>{ document.querySelector(".editor__canvas").style.display = "inherit"; Comms.on("data",x=>Espruino.Core.Terminal.outputDataHandler(x)) Espruino.Core.Terminal.setInputDataHandler(function(d) { Comms.write(d); }) -}); +}); \ No newline at end of file From 72d2a9cbf283e361be7d0fd7d7d4d1d2fcbf7af7 Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Mon, 2 Feb 2026 22:09:48 -0500 Subject: [PATCH 02/15] Implement app shuffling for 'Explore' mode Added shuffling of apps in 'Explore' mode when not sorted by relevance. --- js/index.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/js/index.js b/js/index.js index addb244..5f517cb 100644 --- a/js/index.js +++ b/js/index.js @@ -821,14 +821,25 @@ if (activeFilterAnchor && filterToggle) { }).map(a => a.app); } // if not otherwise sorted, use 'sort by' option - if (!sortedByRelevance) - visibleApps.sort(appSorter); + if (!sortedByRelevance) { + if (activeSort === 'explore') { + // Shuffle apps for "Explore" mode using Fisher-Yates + for (let i = visibleApps.length - 1; i > 0; i--) { + let j = Math.floor(Math.random() * (i + 1)); + let t = visibleApps[i]; visibleApps[i] = visibleApps[j]; visibleApps[j] = t; + } + } else { + visibleApps.sort(appSorter); + } + } if (activeSort && !sortedByRelevance) { // only sort if not searching (searching already sorts) if (["created","modified","installs","favourites"].includes(activeSort)) { visibleApps = visibleApps.sort((a,b) => ((appSortInfo[b.id]||{})[activeSort]||0) - ((appSortInfo[a.id]||{})[activeSort]||0)); + } else if (activeSort === 'explore') { + // nothing to do - shuffled above } else throw new Error("Unknown sort type "+activeSort); } @@ -1619,4 +1630,4 @@ if (btn) btn.addEventListener("click",event=>{ document.querySelector(".editor__canvas").style.display = "inherit"; Comms.on("data",x=>Espruino.Core.Terminal.outputDataHandler(x)) Espruino.Core.Terminal.setInputDataHandler(function(d) { Comms.write(d); }) -}); \ No newline at end of file +}); From cf221656010669af9d89ce0b568f341814261835 Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Wed, 4 Feb 2026 12:38:12 -0500 Subject: [PATCH 03/15] Change sorting mode from 'explore' to 'random' --- js/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/index.js b/js/index.js index 5f517cb..d24dffa 100644 --- a/js/index.js +++ b/js/index.js @@ -822,7 +822,7 @@ if (activeFilterAnchor && filterToggle) { } // if not otherwise sorted, use 'sort by' option if (!sortedByRelevance) { - if (activeSort === 'explore') { + if (activeSort === 'random') { // Shuffle apps for "Explore" mode using Fisher-Yates for (let i = visibleApps.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); @@ -838,7 +838,7 @@ if (activeFilterAnchor && filterToggle) { visibleApps = visibleApps.sort((a,b) => ((appSortInfo[b.id]||{})[activeSort]||0) - ((appSortInfo[a.id]||{})[activeSort]||0)); - } else if (activeSort === 'explore') { + } else if (activeSort === 'random') { // nothing to do - shuffled above } else throw new Error("Unknown sort type "+activeSort); } From 885e79ca43323e1e75e5db1718338a45661adcfd Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Wed, 4 Feb 2026 12:39:08 -0500 Subject: [PATCH 04/15] Refactor searchType condition for clarity and remove chips value --- js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index d24dffa..8531936 100644 --- a/js/index.js +++ b/js/index.js @@ -728,7 +728,7 @@ function refreshLibrary(options) { searchChip = searchParams.get("c").toLowerCase(); } } - if (searchType === "hash" && chips.indexOf(searchValue)>=0) { + if (searchType === "hash") { searchType = ""; searchValue = ""; searchChip = searchValue; From a966deb47908a88ab11063c4fbb0a459f5a77fe7 Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Wed, 4 Feb 2026 12:41:27 -0500 Subject: [PATCH 05/15] Refactor filter label update logic --- js/index.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/js/index.js b/js/index.js index 8531936..9309c95 100644 --- a/js/index.js +++ b/js/index.js @@ -733,21 +733,21 @@ function refreshLibrary(options) { searchValue = ""; searchChip = searchValue; } -let filtersContainer = document.querySelector("#librarycontainer .filter-nav"); -let filterToggle = filtersContainer.querySelector('.dropdown-toggle span'); -let filterAnchors = filtersContainer.querySelectorAll('.menu-item a'); - -// Find the currently selected filter and update label -let activeFilterAnchor = Array.from(filterAnchors).find(a => - a.getAttribute('dt') === (searchChip || '') -); - -if (activeFilterAnchor && filterToggle) { - filterToggle.innerHTML = ''; - filterToggle.innerHTML += ''; - filterToggle.innerHTML += activeFilterAnchor.textContent; - -} + let filtersContainer = document.querySelector("#librarycontainer .filter-nav"); + let filterToggle = filtersContainer.querySelector('.dropdown-toggle span'); + let filterAnchors = filtersContainer.querySelectorAll('.menu-item a'); + + // Find the currently selected filter and update label + let activeFilterAnchor = Array.from(filterAnchors).find(a => + a.getAttribute('dt') === (searchChip || '') + ); + + if (activeFilterAnchor && filterToggle) { + filterToggle.innerHTML = ''; + filterToggle.innerHTML += ''; + filterToggle.innerHTML += activeFilterAnchor.textContent; + + } // update the search box value if (!options.dontChangeSearchBox) { if (searchType === "full") From 7de670f49b6cef19465295941b0189b80a13cd49 Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Wed, 4 Feb 2026 12:54:14 -0500 Subject: [PATCH 06/15] Update js/index.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- js/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index 9309c95..ab9e5eb 100644 --- a/js/index.js +++ b/js/index.js @@ -729,9 +729,10 @@ function refreshLibrary(options) { } } if (searchType === "hash") { + // Treat URL hash as a chip/filter identifier + searchChip = searchValue; searchType = ""; searchValue = ""; - searchChip = searchValue; } let filtersContainer = document.querySelector("#librarycontainer .filter-nav"); let filterToggle = filtersContainer.querySelector('.dropdown-toggle span'); From 2086ff22aec8ab9424a30bbb287bda9f8ceae51e Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Wed, 4 Feb 2026 12:54:39 -0500 Subject: [PATCH 07/15] Update js/index.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- js/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/js/index.js b/js/index.js index ab9e5eb..e8f1165 100644 --- a/js/index.js +++ b/js/index.js @@ -642,10 +642,6 @@ function getAppHTML(app, appInstalled, forInterface) { // =========================================== Library -// Initialize filter and sort dropdown references -let filterNav = document.querySelector('.filter-nav'); -let sortNav = document.querySelector('.sort-nav'); - /* Filter types: .../BangleApps/#blue shows apps having "blue" in app.id or app.tag --> searchType:hash From e74e487e5415e334f74876ade377fc2703f38a9b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 5 Feb 2026 10:12:22 +0000 Subject: [PATCH 08/15] remove un-needed += --- js/index.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/index.js b/js/index.js index e8f1165..b4bccff 100644 --- a/js/index.js +++ b/js/index.js @@ -668,8 +668,7 @@ function refreshSort(){ ); if (activeAnchor && sortToggle) { - sortToggle.innerHTML = ''; - sortToggle.innerHTML += ''; + sortToggle.innerHTML = ''; if (activeSort === '') { sortToggle.innerHTML += `None`; } else { @@ -740,10 +739,8 @@ function refreshLibrary(options) { ); if (activeFilterAnchor && filterToggle) { - filterToggle.innerHTML = ''; - filterToggle.innerHTML += ''; - filterToggle.innerHTML += activeFilterAnchor.textContent; - + filterToggle.innerHTML = ''; + filterToggle.innerHTML += activeFilterAnchor.textContent; } // update the search box value if (!options.dontChangeSearchBox) { From 3225ddf2ffb42f100dfb50b06b15f67e96b2a71b Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Mon, 25 May 2026 21:30:14 -0400 Subject: [PATCH 09/15] hide sort dropdown items when there's no data for it Add functionality to hide sort dropdowns with no data. --- js/index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index 4fbfee8..76b41a0 100644 --- a/js/index.js +++ b/js/index.js @@ -1508,7 +1508,17 @@ function autoAlignMenu(dropdown) { menu.style.visibility = prevVis || ''; menu.style.display = prevDisp || ''; } - +// Make sure sort dropdown hides any with no data for. +if (Const.APP_DATES_CSV) httpGet(Const.APP_DATES_CSV).then(csv=>{ + // ... + csv.split("\n").forEach(line=>{ + // parse lines, build appSortInfo + }); + document.querySelector("#newSort").parentElement.classList.remove("hidden"); + document.querySelector("#changedSort").parentElement.classList.remove("hidden"); +}).catch(err=>{ + console.log("No recent.csv - app sort disabled"); +}); // Flip on open document.addEventListener('click', (e) => { const toggle = e.target.closest('.dropdown-toggle'); From 97e90b1f163b3e05537d3eade0e53b9bc851b225 Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Tue, 2 Jun 2026 13:50:13 -0400 Subject: [PATCH 10/15] Update index.js --- js/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index 76b41a0..2ac5ab8 100644 --- a/js/index.js +++ b/js/index.js @@ -1651,7 +1651,8 @@ if (btn) btn.addEventListener("click",event=>{ } })); }); - +if (document.querySelectorAll(".chip").length) + console.error("This EspruinoAppLoaderCore expects app types in a drop-down, not chips. See https://github.com/espruino/BangleApps/pull/4150/changes"); // Open terminal button if (Espruino.Core.Terminal) Espruino.Core.Terminal.OVERRIDE_CONTENTS = "Click here and type to communicate with Bangle.js"; From 8dfa78d745d8f2b5d41e3be341e6ff4d869cf23d Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Tue, 2 Jun 2026 14:05:52 -0400 Subject: [PATCH 11/15] Move visibility to where the chip visibility was --- js/index.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/js/index.js b/js/index.js index 2ac5ab8..81c9d48 100644 --- a/js/index.js +++ b/js/index.js @@ -240,8 +240,8 @@ if (Const.APP_USAGE_JSON) httpGet(Const.APP_USAGE_JSON).then(jsonTxt=>{ if (json.app[key] > appCounts.installs) appCounts.installs = json.app[key]; appSortInfo[key].installs = json.app[key]; }); - document.querySelector(".sort-nav a[sortid='installs']").parentElement.classList.remove("hidden"); - document.querySelector(".sort-nav a[sortid='favourites']").parentElement.classList.remove("hidden"); + document.querySelector("#newSort").parentElement.classList.remove("hidden"); + document.querySelector("#changedSort").parentElement.classList.remove("hidden"); // actually set to sort on favourites if (activeSort != "favourites") { activeSort = "favourites"; @@ -1508,17 +1508,7 @@ function autoAlignMenu(dropdown) { menu.style.visibility = prevVis || ''; menu.style.display = prevDisp || ''; } -// Make sure sort dropdown hides any with no data for. -if (Const.APP_DATES_CSV) httpGet(Const.APP_DATES_CSV).then(csv=>{ - // ... - csv.split("\n").forEach(line=>{ - // parse lines, build appSortInfo - }); - document.querySelector("#newSort").parentElement.classList.remove("hidden"); - document.querySelector("#changedSort").parentElement.classList.remove("hidden"); -}).catch(err=>{ - console.log("No recent.csv - app sort disabled"); -}); + // Flip on open document.addEventListener('click', (e) => { const toggle = e.target.closest('.dropdown-toggle'); @@ -1651,8 +1641,7 @@ if (btn) btn.addEventListener("click",event=>{ } })); }); -if (document.querySelectorAll(".chip").length) - console.error("This EspruinoAppLoaderCore expects app types in a drop-down, not chips. See https://github.com/espruino/BangleApps/pull/4150/changes"); + // Open terminal button if (Espruino.Core.Terminal) Espruino.Core.Terminal.OVERRIDE_CONTENTS = "Click here and type to communicate with Bangle.js"; From 299c92beeb643407a44bb7bcc2ee0c0945e8daf4 Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Fri, 5 Jun 2026 07:35:40 -0400 Subject: [PATCH 12/15] Merge changes into file --- js/index.js | 163 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 122 insertions(+), 41 deletions(-) diff --git a/js/index.js b/js/index.js index 81c9d48..1fe172d 100644 --- a/js/index.js +++ b/js/index.js @@ -240,8 +240,9 @@ if (Const.APP_USAGE_JSON) httpGet(Const.APP_USAGE_JSON).then(jsonTxt=>{ if (json.app[key] > appCounts.installs) appCounts.installs = json.app[key]; appSortInfo[key].installs = json.app[key]; }); - document.querySelector("#newSort").parentElement.classList.remove("hidden"); - document.querySelector("#changedSort").parentElement.classList.remove("hidden"); + document.querySelector(".sort-nav").classList.remove("hidden"); + document.querySelector(".sort-nav label[sortid='installs']").classList.remove("hidden"); + document.querySelector(".sort-nav label[sortid='favourites']").classList.remove("hidden"); // actually set to sort on favourites if (activeSort != "favourites") { activeSort = "favourites"; @@ -253,31 +254,61 @@ if (Const.APP_USAGE_JSON) httpGet(Const.APP_USAGE_JSON).then(jsonTxt=>{ }); // =========================================== Top Navigation -function showChangeLog(appid, installedVersion) { + +function getChangeLogText(appid, installedVersion) { let app = appNameToApp(appid); function show(contents) { let shouldEscapeHtml = true; - if (contents && installedVersion) { - let lines = contents.split("\n"); - for(let i = 0; i < lines.length; i++) { - let line = lines[i]; - if (line.startsWith(installedVersion)) { - line = '' + line; - lines[i] = line; + if (contents) { + const lines = contents.split("\n"); + const entries = []; + let entry = []; + + lines.forEach(line => { + let cleanLine = line.trimEnd(); + let parts = cleanLine.split(":"); + let token = parts[0].trim(); + let isHeader = parts.length > 1 && /[0-9]/.test(token); + + if (isHeader && entry.length) { + entries.push(entry); + entry = []; } - } - contents = lines.join("
"); + entry.push(cleanLine); + }); + + if (entry.length) entries.push(entry); + entries.reverse(); + + contents = entries.map(entryLines => { + while (entryLines.length && !entryLines[0].trim()) entryLines.shift(); + while (entryLines.length && !entryLines[entryLines.length - 1].trim()) entryLines.pop(); + if (!entryLines.length) return ""; + + let header = entryLines[0]; + let parts = header.split(":"); + if (parts.length > 1) { + let token = parts[0].trim(); + let body = ":" + parts.slice(1).join(":"); + let installedText = installedVersion && installedVersion + "" == token ? " (installed)" : ""; + if (/[0-9]/.test(token)) header = `${token}${installedText}${body}`; + } + + entryLines[0] = header; + return entryLines.join("
"); + }).join("
"); shouldEscapeHtml = false; } - showPrompt(app.name+" ChangeLog",contents,{ok:true}, shouldEscapeHtml).catch(()=>{}); if (installedVersion) { let elem = document.getElementById(installedVersion); if (elem) elem.scrollIntoView(); } + return contents; } - httpGet(`apps/${appid}/ChangeLog`). + return httpGet(`apps/${appid}/ChangeLog`). then(show).catch(()=>show("No Change Log available")); } + function showReadme(event, appid) { if (event) event.preventDefault(); let app = appNameToApp(appid); @@ -290,6 +321,18 @@ function showReadme(event, appid) { } httpGet(appPath+app.readme).then(show).catch(()=>show("Failed to load README.")); } +function showAppInfo(appid, installedVersion) { + let app = appNameToApp(appid); + let infoTxt=getAppInfo(app,true); + let changelogText; + getChangeLogText(appid, installedVersion).then(contents => { + changelogText = contents; + const infoPart = infoTxt.length>0 ? marked(infoTxt.join("
")) : ""; + const changelogPart = changelogText ? changelogText.replace(/\n/g, "
") : ""; + const changeLogHeading = changelogPart ? "
ChangeLog:
" : ""; + showPrompt(app.name + " App Information", infoPart + changeLogHeading + changelogPart, {ok: true,}, false).catch(() => {}); + }); +} function getAppDescription(app) { let appPath = `apps/${app.id}/`; let markedOptions = { baseUrl : appPath }; @@ -567,30 +610,32 @@ function getAppfavourites(app){ } return appFavourites; } - - -function getAppHTML(app, appInstalled, forInterface) { - let version = getVersionInfo(app, appInstalled); - let versionInfo = version.text; - let versionTitle = ''; - let appFavourites; +function getAppInfo(app, expanded){ + // expanded is for prompt, so it shows md formatting and author + let infoTxt = []; + function bold(txt){ + if(expanded) return `**${txt}**`; + return txt; + } if (app.id in appSortInfo) { - let infoTxt = []; + let info = appSortInfo[app.id]; - if ("object"==typeof info.modified) - infoTxt.push(`Last update: ${(info.modified.toLocaleDateString())}`); if (info.installs){ let percent=(info.installs / appCounts.installs * 100).toFixed(0); let percentText=percent<1?"Less than 1% of all users":percent+"% of all Bangle.js users"; - infoTxt.push(`${info.installs} reported installs (${percentText})`); + infoTxt.push(`${bold(`${info.installs} reported installs`)} (${percentText})`); } if (info.favourites) { - appFavourites = getAppfavourites(app); - let percent=(appFavourites / info.installs * 100).toFixed(0); - let percentText=percent>100?"More than 100% of installs":percent+"% of installs"; - if(!info.installs||info.installs<1) {infoTxt.push(`${appFavourites} users favourited`);} - else {infoTxt.push(`${appFavourites} users favourited (${percentText})`);} + let appFavourites = getAppfavourites(app); + if(info.installs&&info.installs>1){ + let percent=(appFavourites / info.installs * 100).toFixed(0); + let percentText=percent>100?"More than 100% of installs":percent+"% of installs"; + infoTxt.push(`${bold(`${appFavourites} users favourited`)} (${percentText})`); + }else{ + infoTxt.push(bold(`${appFavourites} users favourited`)); + } } + if(expanded)infoTxt.push(`${bold("App ID:")} ${app.id}`); if (app.supports) { const devices = { BANGLEJS:"Bangle.js 1", @@ -599,11 +644,28 @@ function getAppHTML(app, appInstalled, forInterface) { BANGLEJS3_COMPAT:"Bangle.js 3 (compatibility mode)" }; if (app.supports.every(s => s in devices)) - infoTxt.push(`Supports ${app.supports.map(d => devices[d]).join(", ")}`); + infoTxt.push(`${bold("Supports:")} ${app.supports.map(d => devices[d]).join(", ")}`); } - if (infoTxt.length) - versionTitle = `title="${infoTxt.join("\n")}"`; + if ("object"==typeof info.created && expanded) + infoTxt.push(`${bold("Created:")} ${(info.created.toLocaleDateString())}`); + if ("object"==typeof info.modified) + infoTxt.push(`${bold("Last updated:")} ${(info.modified.toLocaleDateString())}`); + if(app.author&&expanded) infoTxt.push(`${bold("Author:")} ${app.author}`); } + return infoTxt; +} + +function getAppHTML(app, appInstalled, forInterface) { + let version = getVersionInfo(app, appInstalled); + let versionInfo = version.text; + let versionTitle = ''; + let appFavourites; + appFavourites = getAppfavourites(app); + let infoTxt= getAppInfo(app,false) + if (infoTxt.length) versionTitle = `title="${infoTxt.join("\n")}"`; + + + if (versionInfo) versionInfo = ` (${versionInfo})`; let appurl = window.location.origin + window.location.pathname + "?id=" + encodeURIComponent(app.id); let readme = `Read more...`; @@ -616,7 +678,7 @@ function getAppHTML(app, appInstalled, forInterface) { let txt = (n > 999) ? Math.round(n/100)/10+"k" : n; return `${txt}`; }; - + let html = `
${escapeHtml(app.name)}
@@ -680,7 +742,7 @@ function refreshSort(){ if (activeAnchor && sortToggle) { sortToggle.innerHTML = ''; - if (activeSort === '') { + if (activeSort === ''||!activeSort) { sortToggle.innerHTML += `None`; } else { sortToggle.innerHTML += activeAnchor.textContent; @@ -1482,6 +1544,7 @@ settingsCheckbox("settings-alwaysAllowEmulator", "alwaysAllowEmulator"); settingsCheckbox("settings-autoReload", "autoReload"); settingsCheckbox("settings-nopacket", "noPackets"); loadSettings(); +refreshSort(); function autoAlignMenu(dropdown) { @@ -1509,15 +1572,33 @@ function autoAlignMenu(dropdown) { menu.style.display = prevDisp || ''; } -// Flip on open +function closeDropdowns() { + document.querySelectorAll('.dropdown.active').forEach(dropdown => { + dropdown.classList.remove('active'); + const toggle = dropdown.querySelector('.dropdown-toggle'); + if (toggle && document.activeElement === toggle) { + requestAnimationFrame(() => toggle.blur()); + } + }); +} + +// Toggle dropdowns on header click document.addEventListener('click', (e) => { const toggle = e.target.closest('.dropdown-toggle'); - if (!toggle) return; - const dropdown = toggle.closest('.dropdown'); - if (!dropdown) return; + if (toggle) { + e.preventDefault(); + const dropdown = toggle.closest('.dropdown'); + if (!dropdown) return; + const shouldOpen = !dropdown.classList.contains('active'); + closeDropdowns(); + if (shouldOpen) { + dropdown.classList.add('active'); + requestAnimationFrame(() => autoAlignMenu(dropdown)); + } + return; + } - // Let the framework open the menu, then align - requestAnimationFrame(() => autoAlignMenu(dropdown)); + closeDropdowns(); }); // Keep alignment on resize From faea018ebfdd9ce3ece8e1d0746cce535dba2ba8 Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Fri, 5 Jun 2026 08:53:10 -0400 Subject: [PATCH 13/15] fix sort not working with search Refactor search result filtering and sorting logic to improve clarity and maintainability. --- js/index.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/js/index.js b/js/index.js index 1fe172d..5a9c243 100644 --- a/js/index.js +++ b/js/index.js @@ -846,8 +846,8 @@ function refreshLibrary(options) { } // Now do our search, put the values in searchResult if (searchValue) { + sortedByRelevance = true; if (searchType === "hash") { - sortedByRelevance = true; searchResult = visibleApps.map(app => ({ app : app, relevance : @@ -861,7 +861,6 @@ function refreshLibrary(options) { relevance: (app.id.toLowerCase() == searchValue) ? 1 : 0 })); } else if (searchType === "full" && searchValue) { - sortedByRelevance = true; searchResult = visibleApps.map(app => ({ app:app, relevance: @@ -875,17 +874,24 @@ function refreshLibrary(options) { } else { console.warn("Unknown search type "+searchType, searchValue); } - // Now finally, filter, sort based on relevance and set the search result - visibleApps = searchResult.filter(a => a.relevance>0).sort((a,b) => { - // sort by relevance and sort order - let sort = (b.relevance-(0|b.sortorder)) - (a.relevance-(0|a.sortorder)); - if (sort) return sort; - // if relevance is the same, sort by extraSort (eg created, modified, installs, favourites) - if (["created","modified","installs","favourites"].includes(activeSort)) - return ((appSortInfo[b.app.id]||{})[activeSort]||0) - - ((appSortInfo[a.app.id]||{})[activeSort]||0); - return 0; - }).map(a => a.app); + // Now finally, filter and sort the search result. + let searchMatches = searchResult.filter(a => a.relevance>0); + if (activeSort && ["created","modified","installs","favourites"].includes(activeSort)) { + searchMatches.sort((a,b) => { + let sort = ((appSortInfo[b.app.id]||{})[activeSort]||0) - + ((appSortInfo[a.app.id]||{})[activeSort]||0); + if (sort) return sort; + return (b.relevance-(0|b.sortorder)) - (a.relevance-(0|a.sortorder)); + }); + } else { + searchMatches.sort((a,b) => { + // sort by relevance and sort order + let sort = (b.relevance-(0|b.sortorder)) - (a.relevance-(0|a.sortorder)); + if (sort) return sort; + return 0; + }); + } + visibleApps = searchMatches.map(a => a.app); } // if not otherwise sorted, use 'sort by' option if (!sortedByRelevance) { From 4c3fb0e392c17ff903434803f16400c1191dbb7d Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Fri, 5 Jun 2026 10:29:57 -0400 Subject: [PATCH 14/15] Revert "fix sort not working with search" This reverts commit faea018ebfdd9ce3ece8e1d0746cce535dba2ba8. --- js/index.js | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/js/index.js b/js/index.js index 5a9c243..1fe172d 100644 --- a/js/index.js +++ b/js/index.js @@ -846,8 +846,8 @@ function refreshLibrary(options) { } // Now do our search, put the values in searchResult if (searchValue) { - sortedByRelevance = true; if (searchType === "hash") { + sortedByRelevance = true; searchResult = visibleApps.map(app => ({ app : app, relevance : @@ -861,6 +861,7 @@ function refreshLibrary(options) { relevance: (app.id.toLowerCase() == searchValue) ? 1 : 0 })); } else if (searchType === "full" && searchValue) { + sortedByRelevance = true; searchResult = visibleApps.map(app => ({ app:app, relevance: @@ -874,24 +875,17 @@ function refreshLibrary(options) { } else { console.warn("Unknown search type "+searchType, searchValue); } - // Now finally, filter and sort the search result. - let searchMatches = searchResult.filter(a => a.relevance>0); - if (activeSort && ["created","modified","installs","favourites"].includes(activeSort)) { - searchMatches.sort((a,b) => { - let sort = ((appSortInfo[b.app.id]||{})[activeSort]||0) - - ((appSortInfo[a.app.id]||{})[activeSort]||0); - if (sort) return sort; - return (b.relevance-(0|b.sortorder)) - (a.relevance-(0|a.sortorder)); - }); - } else { - searchMatches.sort((a,b) => { - // sort by relevance and sort order - let sort = (b.relevance-(0|b.sortorder)) - (a.relevance-(0|a.sortorder)); - if (sort) return sort; - return 0; - }); - } - visibleApps = searchMatches.map(a => a.app); + // Now finally, filter, sort based on relevance and set the search result + visibleApps = searchResult.filter(a => a.relevance>0).sort((a,b) => { + // sort by relevance and sort order + let sort = (b.relevance-(0|b.sortorder)) - (a.relevance-(0|a.sortorder)); + if (sort) return sort; + // if relevance is the same, sort by extraSort (eg created, modified, installs, favourites) + if (["created","modified","installs","favourites"].includes(activeSort)) + return ((appSortInfo[b.app.id]||{})[activeSort]||0) - + ((appSortInfo[a.app.id]||{})[activeSort]||0); + return 0; + }).map(a => a.app); } // if not otherwise sorted, use 'sort by' option if (!sortedByRelevance) { From 389b915c002ac3bf7b7dc290a2157e592b849296 Mon Sep 17 00:00:00 2001 From: RKBoss6 Date: Fri, 5 Jun 2026 10:50:48 -0400 Subject: [PATCH 15/15] Make favourites the default sort --- js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/index.js b/js/index.js index 1fe172d..42a3ff0 100644 --- a/js/index.js +++ b/js/index.js @@ -727,7 +727,7 @@ function getAppHTML(app, appInstalled, forInterface) { */ -let activeSort = ''; +let activeSort = 'favourites'; let libraryShowAll = false; // perist whether user chose to view all apps // Update the sort state to match the current sort value function refreshSort(){