|
| 1 | +const dataJSON = "/feeds/posts/summary?alt=json", |
| 2 | + iconJSON = "https://cdn.jsdelivr.net/npm/meteor-icons@3/variants/icons.json", |
| 3 | + dataConfig = { |
| 4 | + cache: !0 |
| 5 | + }, |
| 6 | + input = document.getElementById("search-input"), |
| 7 | + results = document.getElementById("search-results"), |
| 8 | + indexConfig = Object.assign(dataConfig, { |
| 9 | + doc: { |
| 10 | + id: "id", |
| 11 | + field: ["title", "summary"], |
| 12 | + store: ["title", "url", "summary", "icon", "labels"] |
| 13 | + } |
| 14 | + }), |
| 15 | + stringToHTML = e => (new DOMParser).parseFromString(e, "text/html").body.firstChild; |
| 16 | +let iconData = {}, |
| 17 | + docsIndex, displayedArticles = new Set; |
| 18 | + |
| 19 | +function parseFeed(e) { |
| 20 | + return e.feed.entry.map(e => ({ |
| 21 | + id: e.id.$t, |
| 22 | + title: e.title.$t, |
| 23 | + summary: e.summary ? e.summary.$t : "", |
| 24 | + url: e.link.find(e => "alternate" === e.rel).href, |
| 25 | + labels: e.category ? e.category.map(e => e.term) : [], |
| 26 | + icon: e.category && e.category.length > 0 ? e.category[0].term : "default-icon" |
| 27 | + })) |
| 28 | +} |
| 29 | + |
| 30 | +function getIcon(e) { |
| 31 | + let t = { |
| 32 | + android: "android", |
| 33 | + blogger: "blogger", |
| 34 | + css: "brackets-curly", |
| 35 | + javascript: "brackets", |
| 36 | + widgets: "grid-plus", |
| 37 | + default: "folder-search" |
| 38 | + }; |
| 39 | + return t[e.toLowerCase()] || t.default |
| 40 | +} |
| 41 | + |
| 42 | +function init() { |
| 43 | + input.removeEventListener("focus", init), input.required = !0, fetch("/feeds/posts/summary?alt=json").then(e => e.json()).then(e => { |
| 44 | + let t = parseFeed(e); |
| 45 | + (docsIndex = FlexSearch.create("balance", indexConfig)).add(t) |
| 46 | + }).then(() => input.required = !1) |
| 47 | +} |
| 48 | + |
| 49 | +function truncate(e, t) { |
| 50 | + return e.length > t ? e.slice(0, t) + "..." : e |
| 51 | +} |
| 52 | + |
| 53 | +function search() { |
| 54 | + if (!docsIndex) return; |
| 55 | + results.innerHTML = ""; |
| 56 | + let e = input.value.trim().toLowerCase(); |
| 57 | + if (!e) return; |
| 58 | + let t = docsIndex.search(e, 10), |
| 59 | + s = new Set; |
| 60 | + t.forEach(e => { |
| 61 | + e.labels.forEach(e => { |
| 62 | + s.add(e) |
| 63 | + }) |
| 64 | + }), s.forEach(e => { |
| 65 | + let s = t.filter(t => t.labels.includes(e)), |
| 66 | + r = getIcon(e), |
| 67 | + n = `<div class="search-group"> |
| 68 | + <div class="search-group-title capitalize fs-6 fw-500 has-icon"> |
| 69 | + <svg class="i i-${r} flex-none" viewBox="0 0 24 24">${iconData[r]}</svg> |
| 70 | + <h3 class="">${e}</h3> |
| 71 | + </div> |
| 72 | + <ul class="search-group-list"></ul> |
| 73 | + </div>`, |
| 74 | + i = stringToHTML(n), |
| 75 | + a = i.querySelector(".search-group-list"); |
| 76 | + s.forEach(e => { |
| 77 | + let t = truncate(e.summary, 100), |
| 78 | + s = `<li class="search-item"> |
| 79 | + <a class="search-link" href="${e.url}"> |
| 80 | + <div class="search-title fs-6 fw-500">${e.title}</div> |
| 81 | + ${t?`<p class="search-summary">${t}</p>`:""} |
| 82 | + </a> |
| 83 | + </li>`; |
| 84 | + a.insertAdjacentHTML("beforeend", s), displayedArticles.add(e.id) |
| 85 | + }), results.appendChild(i) |
| 86 | + }) |
| 87 | +} |
| 88 | + |
| 89 | +function initSearch(e, t) { |
| 90 | + e && t && (e.addEventListener("focus", init), e.addEventListener("keyup", search)) |
| 91 | +} |
| 92 | +fetch("https://cdn.jsdelivr.net/npm/meteor-icons@3/variants/icons.json").then(e => e.json()).then(e => { |
| 93 | + iconData = e, initSearch(input, results) |
| 94 | +}); |
0 commit comments