(tbody)
| 1768 | // textContent — titles/URLs are untrusted (YouTube/SoundCloud) so never |
| 1769 | // interpolate them into innerHTML. |
| 1770 | function renderLibraryRows(tbody) { |
| 1771 | tbody.textContent = ""; |
| 1772 | const trashIds = new Set(getTrashFolder()?.items || []); |
| 1773 | // Only out-of-sync (audio missing) tracks — this table sits next to Resync. |
| 1774 | const entries = Object.entries(tracks) |
| 1775 | .filter(([id, t]) => !trashIds.has(id) && t.status === "unavailable") |
| 1776 | .sort((a, b) => (b[1].createdAt || 0) - (a[1].createdAt || 0)); |
| 1777 | |
| 1778 | if (!entries.length) { |
| 1779 | const tr = document.createElement("tr"); |
| 1780 | const td = document.createElement("td"); |
| 1781 | td.colSpan = 3; |
| 1782 | td.className = "library-editor-empty"; |
| 1783 | td.textContent = "All tracks are in sync."; |
| 1784 | tr.appendChild(td); |
| 1785 | tbody.appendChild(tr); |
| 1786 | return; |
| 1787 | } |
| 1788 | |
| 1789 | for (const [id, t] of entries) { |
| 1790 | const tr = document.createElement("tr"); |
| 1791 | tr.dataset.id = id; |
| 1792 | if (t.status === "unavailable") tr.className = "unavailable"; |
| 1793 | |
| 1794 | const name = document.createElement("td"); |
| 1795 | name.className = "le-name"; |
| 1796 | name.textContent = t.title || "—"; |
| 1797 | name.title = t.title || ""; |
| 1798 | if (t.status === "unavailable") { |
| 1799 | const badge = document.createElement("span"); |
| 1800 | badge.className = "le-badge"; |
| 1801 | badge.textContent = "unavailable"; |
| 1802 | name.appendChild(badge); |
| 1803 | } |
| 1804 | |
| 1805 | const source = document.createElement("td"); |
| 1806 | source.className = "le-source"; |
| 1807 | source.textContent = deriveSource(t.sourceUrl); |
| 1808 | |
| 1809 | const loc = document.createElement("td"); |
| 1810 | loc.className = "le-loc"; |
| 1811 | const locText = libraryLocation(t.sourceUrl); |
| 1812 | loc.textContent = locText; |
| 1813 | loc.title = locText; |
| 1814 | |
| 1815 | tr.append(name, source, loc); |
| 1816 | tbody.appendChild(tr); |
| 1817 | } |
| 1818 | } |
| 1819 | |
| 1820 | // "Make StemDeck available on your network" toggle. The backend always binds |
| 1821 | // all interfaces and gates LAN access on a runtime flag (GET/POST /api/settings) |
no test coverage detected