Rewild Your Web
web browser dweb
16
fork

Configure Feed

Select the types of activity you want to include in your feed.

1// SPDX-License-Identifier: AGPL-3.0-or-later 2 3// DOM elements 4const bookmarksGrid = document.getElementById("bookmarks-grid"); 5const resultsArea = document.getElementById("results-area"); 6const resultsList = document.getElementById("results-list"); 7const searchInput = document.getElementById("search-input"); 8const widgetsArea = document.getElementById("widgets-area"); 9 10// Load widgets from JSON 11async function loadWidgets() { 12 try { 13 const response = await fetch("resources/widgets.json"); 14 const widgets = await response.json(); 15 renderWidgets(widgets); 16 } catch (e) { 17 console.log("[Homescreen] No widgets configured"); 18 } 19} 20 21// Render widgets as iframes 22function renderWidgets(widgets) { 23 for (const widget of widgets) { 24 const iframe = document.createElement("iframe"); 25 iframe.className = "widget-frame"; 26 iframe.src = widget.url; 27 iframe.style.width = widget.width || "100%"; 28 iframe.style.height = widget.height || "100px"; 29 widgetsArea.appendChild(iframe); 30 } 31} 32 33// Load bookmarks from JSON 34async function loadBookmarks() { 35 try { 36 const response = await fetch("resources/bookmarks.json"); 37 const bookmarks = await response.json(); 38 renderBookmarks(bookmarks); 39 } catch (e) { 40 console.error("[Homescreen] Failed to load bookmarks:", e); 41 } 42} 43 44// Render bookmarks to the grid 45function renderBookmarks(bookmarks) { 46 bookmarksGrid.innerHTML = ""; 47 48 for (const bookmark of bookmarks) { 49 const item = document.createElement("div"); 50 item.className = "bookmark-item"; 51 item.innerHTML = ` 52 <div class="bookmark-icon"> 53 <img src="resources/${bookmark.icon}" alt="" onerror="this.style.display='none'" /> 54 </div> 55 <span class="bookmark-title">${bookmark.title}</span> 56 `; 57 // TODO: replace with <a target="_blank"> 58 item.addEventListener("click", () => { 59 navigate(bookmark.url); 60 }); 61 bookmarksGrid.appendChild(item); 62 } 63} 64 65// Reset search UI to initial state 66function resetSearchState() { 67 searchInput.value = ""; 68 searchInput.blur(); 69 document.body.classList.remove("searching"); 70 controller.clear(); 71} 72 73// Navigate to a URL 74function navigate(url) { 75 resetSearchState(); 76 window.open(url, "_blank"); 77} 78 79class Controller { 80 constructor() { 81 this.inner = null; 82 } 83 84 async ensureController() { 85 if (this.inner) { 86 return; 87 } 88 89 let mod = await import("//shared.localhost:8888/search/controller.js"); 90 this.inner = new mod.SearchController({ 91 onNavigate: navigate, 92 onResultsChanged: renderResults, 93 debounceDelay: 150, 94 }); 95 } 96 97 async handleResultClick(result) { 98 await this.ensureController(); 99 this.inner.handleResultClick(result); 100 } 101 102 async handleSubmit(data) { 103 await this.ensureController(); 104 this.inner.handleSubmit(data); 105 } 106 107 async query(query) { 108 await this.ensureController(); 109 this.inner.query(query); 110 } 111 112 async clear() { 113 await this.ensureController(); 114 this.inner.clear(); 115 } 116} 117 118// Initialize search controller 119const controller = new Controller(); 120 121// Render search results (adapted from new_view.js) 122function renderResults(results, groups) { 123 resultsList.innerHTML = ""; 124 125 if (results.length === 0) { 126 return; 127 } 128 129 for (const group of groups) { 130 const groupDiv = document.createElement("div"); 131 groupDiv.className = "result-group"; 132 133 // Icon column 134 const iconDiv = document.createElement("div"); 135 iconDiv.className = "result-group-icon"; 136 if (group.providerIcon) { 137 const icon = document.createElement("lucide-icon"); 138 icon.setAttribute("name", group.providerIcon); 139 iconDiv.appendChild(icon); 140 } 141 groupDiv.appendChild(iconDiv); 142 143 // Items column 144 const itemsDiv = document.createElement("div"); 145 itemsDiv.className = "result-group-items"; 146 147 for (const result of group.items) { 148 const itemDiv = document.createElement("div"); 149 itemDiv.className = "result-item"; 150 itemDiv.dataset.kind = result.kind; 151 152 if (result.kind === "link" || result.kind === "webview") { 153 const link = document.createElement("a"); 154 link.href = result.kind === "link" ? result.value.url : "#"; 155 link.className = "result-link"; 156 link.textContent = result.value.title; 157 link.addEventListener("click", (e) => { 158 e.preventDefault(); 159 controller.handleResultClick(result); 160 }); 161 itemDiv.appendChild(link); 162 } else if (result.kind === "text") { 163 const text = document.createElement("span"); 164 text.className = "result-text"; 165 text.textContent = result.value; 166 itemDiv.appendChild(text); 167 } 168 169 itemsDiv.appendChild(itemDiv); 170 } 171 172 groupDiv.appendChild(itemsDiv); 173 resultsList.appendChild(groupDiv); 174 } 175} 176 177// Event listeners 178searchInput.addEventListener("input", () => { 179 const query = searchInput.value.trim(); 180 if (query) { 181 document.body.classList.add("searching"); 182 controller.query(query); 183 } else { 184 document.body.classList.remove("searching"); 185 controller.clear(); 186 } 187}); 188 189searchInput.addEventListener("keydown", (e) => { 190 if (e.key === "Enter") { 191 e.preventDefault(); 192 controller.handleSubmit(searchInput.value); 193 } 194}); 195 196// Initialize 197loadWidgets(); 198loadBookmarks();