From 58e705473cfe5078f109267a1164db81c7fb2da7 Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sun, 14 Dec 2025 11:35:04 +0200 Subject: Update --- main.js | 972 ---------------------------------------------------------------- 1 file changed, 972 deletions(-) delete mode 100644 main.js (limited to 'main.js') diff --git a/main.js b/main.js deleted file mode 100644 index 8bba38e..0000000 --- a/main.js +++ /dev/null @@ -1,972 +0,0 @@ -// ═══════════════════════════════════════════════════════════ -// SOURCES -// ═══════════════════════════════════════════════════════════ -const SOURCES = { - dictionary: { - bang: "!d", - count: 0, - enabled: true, - fetch: async (q) => { - const r = await fetch( - `https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(q)}`, - ); - if (!r.ok) return []; - const d = await r.json(); - return d.slice(0, 3).flatMap((e) => - e.meanings.slice(0, 2).map((m) => ({ - meta: { phonetic: e.phonetic }, - snippet: m.definitions[0]?.definition || "", - source: "dictionary", - title: `${e.word} [${m.partOfSpeech}]`, - url: e.sourceUrls?.[0] || "#", - })), - ); - }, - name: "DICTIONARY", - }, - github: { - bang: "!g", - count: 0, - enabled: true, - fetch: async (q) => { - const r = await fetch( - `https://api.github.com/search/repositories?q=${encodeURIComponent(q)}&per_page=8&sort=stars`, - ); - const d = await r.json(); - return (d.items || []).map((i) => ({ - meta: { forks: i.forks_count, lang: i.language, stars: i.stargazers_count }, - snippet: i.description || "NO DESCRIPTION", - source: "github", - title: i.full_name, - url: i.html_url, - })); - }, - name: "GITHUB", - }, - hackernews: { - bang: "!hn", - count: 0, - enabled: true, - fetch: async (q) => { - const r = await fetch( - `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(q)}&hitsPerPage=8`, - ); - const d = await r.json(); - return (d.hits || []) - .filter((h) => h.title) - .map((h) => ({ - meta: { comments: h.num_comments, points: h.points }, - snippet: `${h.points || 0} POINTS // ${h.author}`, - source: "hackernews", - title: h.title, - url: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, - })); - }, - name: "HACKERNEWS", - }, - news: { - bang: "!n", - count: 0, - enabled: true, - fetch: async (q) => { - // Combine HN, Reddit News, and Wiki Current Events logic - const p1 = fetch( - `https://hn.algolia.com/api/v1/search_by_date?query=${encodeURIComponent(q)}&tags=story&hitsPerPage=6`, - ) - .then((r) => r.json()) - .catch(() => ({ hits: [] })); - const p2 = fetch( - `https://www.reddit.com/r/worldnews+news+technology/search.json?q=${encodeURIComponent(q)}&restrict_sr=1&sort=new&limit=6`, - ) - .then((r) => r.json()) - .catch(() => ({ data: { children: [] } })); - - const [hn, rd] = await Promise.all([p1, p2]); - - const hnRes = (hn.hits || []).map((h) => ({ - meta: { date: h.created_at_i, lang: "HN" }, - snippet: `HACKERNEWS // ${h.points || 0} PTS // ${timeAgo(h.created_at_i * 1000)}`, - source: "news", - title: h.title, - url: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, - })); - - const rdRes = (rd.data?.children || []).map((c) => ({ - meta: { date: c.data.created_utc, lang: "RD" }, - snippet: `REDDIT r/${c.data.subreddit.toUpperCase()} // ${c.data.score} UPVOTES`, - source: "news", - title: c.data.title, - url: `https://reddit.com${c.data.permalink}`, - })); - - // Interleave/Sort by recency - return [...hnRes, ...rdRes].sort((a, b) => b.meta.date - a.meta.date); - }, - name: "AGGREGATE NEWS", - }, - npm: { - bang: "!npm", - count: 0, - enabled: true, - fetch: async (q) => { - const r = await fetch( - `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(q)}&size=8`, - ); - const d = await r.json(); - return (d.objects || []).map((o) => ({ - meta: { version: o.package.version }, - snippet: o.package.description || "NO DESCRIPTION", - source: "npm", - title: o.package.name, - url: `https://npmjs.com/package/${o.package.name}`, - })); - }, - name: "NPM", - }, - openlibrary: { - bang: "!b", - count: 0, - enabled: true, - fetch: async (q) => { - const r = await fetch( - `https://openlibrary.org/search.json?q=${encodeURIComponent(q)}&limit=6`, - ); - const d = await r.json(); - return (d.docs || []).map((b) => ({ - meta: { year: b.first_publish_year }, - snippet: `BY ${(b.author_name || ["UNKNOWN"]).join(", ").toUpperCase()}`, - source: "openlibrary", - title: b.title, - url: `https://openlibrary.org${b.key}`, - })); - }, - name: "OPENLIBRARY", - }, - reddit: { - bang: "!r", - count: 0, - enabled: true, - fetch: async (q) => { - const r = await fetch( - `https://www.reddit.com/search.json?q=${encodeURIComponent(q)}&limit=8`, - ); - const d = await r.json(); - return (d.data?.children || []).map((c) => ({ - meta: { comments: c.data.num_comments, score: c.data.score }, - snippet: c.data.selftext?.substring(0, 200) || `r/${c.data.subreddit}`, - source: "reddit", - title: c.data.title, - url: `https://reddit.com${c.data.permalink}`, - })); - }, - name: "REDDIT", - }, - stackoverflow: { - bang: "!so", - count: 0, - enabled: true, - fetch: async (q) => { - const r = await fetch( - `https://api.stackexchange.com/2.3/search?order=desc&sort=relevance&intitle=${encodeURIComponent(q)}&site=stackoverflow&pagesize=8`, - ); - const d = await r.json(); - return (d.items || []).map((i) => ({ - meta: { answers: i.answer_count, score: i.score }, - snippet: `${i.answer_count} ANSWERS // ${i.view_count} VIEWS`, - source: "stackoverflow", - title: decodeHTML(i.title), - url: i.link, - })); - }, - name: "STACKOVERFLOW", - }, - wikipedia: { - bang: "!w", - count: 0, - enabled: true, - fetch: async (q) => { - const r = await fetch( - `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(q)}&format=json&origin=*&srlimit=8`, - ); - const d = await r.json(); - return (d.query?.search || []).map((i) => ({ - meta: { words: i.wordcount }, - snippet: i.snippet.replace(/<[^>]*>/g, ""), - source: "wikipedia", - title: i.title, - url: `https://en.wikipedia.org/wiki/${encodeURIComponent(i.title)}`, - })); - }, - instant: async (q) => { - const s = await fetch( - `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(q)}&format=json&origin=*&srlimit=1`, - ); - const sd = await s.json(); - if (!sd.query?.search?.length) return null; - const title = sd.query.search[0].title; - const r = await fetch( - `https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&titles=${encodeURIComponent(title)}&format=json&origin=*`, - ); - const d = await r.json(); - const page = Object.values(d.query.pages)[0]; - return { - content: page.extract, - source: "WIKIPEDIA", - title: page.title, - url: `https://en.wikipedia.org/wiki/${encodeURIComponent(title)}`, - }; - }, - name: "WIKIPEDIA", - }, -}; - -const STOCK_EXCHANGES = { - AAPL: "NASDAQ", - ADA: "CRYPTO", - AMD: "NASDAQ", - AMZN: "NASDAQ", - BAC: "NYSE", - BTC: "CRYPTO", - DIS: "NYSE", - DOGE: "CRYPTO", - ETH: "CRYPTO", - EURUSD: "FX", - GBPUSD: "FX", - GOOG: "NASDAQ", - GOOGL: "NASDAQ", - INTC: "NASDAQ", - JNJ: "NYSE", - JPM: "NYSE", - KO: "NYSE", - META: "NASDAQ", - MSFT: "NASDAQ", - NFLX: "NASDAQ", - NVDA: "NASDAQ", - PYPL: "NASDAQ", - QQQ: "NASDAQ", - SOL: "CRYPTO", - SPY: "AMEX", - TSLA: "NASDAQ", - USDJPY: "FX", - V: "NYSE", - WMT: "NYSE", - XOM: "NYSE", - XRP: "CRYPTO", -}; - -function getSymbol(ticker) { - const t = ticker.toUpperCase(); - const ex = STOCK_EXCHANGES[t]; - if (ex === "CRYPTO") return `BINANCE:${t}USDT`; - if (ex === "FX") return `FX:${t}`; - if (ex) return `${ex}:${t}`; - return `NASDAQ:${t}`; -} - -// ═══════════════════════════════════════════════════════════ -// STATE -// ═══════════════════════════════════════════════════════════ -const state = { - calcExpr: "", - calcResult: "0", - hasSearched: false, - history: JSON.parse(localStorage.getItem("sh") || "[]"), - query: "", - results: [], - watchlist: JSON.parse(localStorage.getItem("sw") || '["TSLA", "AAPL", "BTC", "ETH"]'), -}; - -const $ = (id) => document.getElementById(id); -const searchInput = $("search-input"); -const resultsEl = $("results"); -const heroEl = $("hero"); -const mainEl = $("main-content"); -const statsBar = $("stats-bar"); - -// ═══════════════════════════════════════════════════════════ -// INIT -// ═══════════════════════════════════════════════════════════ -function init() { - renderSources(); - renderHistory(); - renderWatchlist(); - initTheme(); - updateClocks(); - setInterval(updateClocks, 1000); - setupCalc(); - setupListeners(); - setupWatchlistListeners(); -} - -function renderSources() { - $("sources-list").innerHTML = Object.entries(SOURCES) - .map( - ([k, s]) => ` -
- ${s.name} ${s.count} -
- `, - ) - .join(""); -} - -function toggleSource(k) { - SOURCES[k].enabled = !SOURCES[k].enabled; - renderSources(); - if (state.query) search(state.query); -} - -function toggleWidget(id) { - $(id).classList.toggle("open"); -} - -// ═══════════════════════════════════════════════════════════ -// THEME -// ═══════════════════════════════════════════════════════════ -function initTheme() { - const t = localStorage.getItem("theme"); - if (t === "light") document.body.classList.add("light-mode"); -} - -function toggleTheme() { - document.body.classList.toggle("light-mode"); - const isLight = document.body.classList.contains("light-mode"); - localStorage.setItem("theme", isLight ? "light" : "dark"); -} - -// ═══════════════════════════════════════════════════════════ -// WATCHLIST -// ═══════════════════════════════════════════════════════════ -function renderWatchlist() { - const c = $("watchlist-container"); - if (!state.watchlist.length) { - c.innerHTML = '
EMPTY
'; - return; - } - c.innerHTML = state.watchlist - .map((t) => { - const ex = STOCK_EXCHANGES[t] || "STOCK"; - return ` -
- ${t} ${ex} - × -
`; - }) - .join(""); -} - -function addTicker() { - const val = $("watchlist-input").value.toUpperCase().trim(); - if (val && !state.watchlist.includes(val)) { - state.watchlist.push(val); - localStorage.setItem("sw", JSON.stringify(state.watchlist)); - renderWatchlist(); - $("watchlist-input").value = ""; - } -} - -window.removeTicker = (e, t) => { - e.stopPropagation(); // Prevent search trigger - state.watchlist = state.watchlist.filter((i) => i !== t); - localStorage.setItem("sw", JSON.stringify(state.watchlist)); - renderWatchlist(); -}; - -function setupWatchlistListeners() { - $("watchlist-add-btn").onclick = addTicker; - $("watchlist-input").addEventListener("keydown", (e) => { - if (e.key === "Enter") addTicker(); - }); -} - -// ═══════════════════════════════════════════════════════════ -// SEARCH -// ═══════════════════════════════════════════════════════════ -async function search(query) { - if (!query.trim()) return goHome(); - - // Show results view - if (!state.hasSearched) { - state.hasSearched = true; - heroEl.classList.add("compact"); - mainEl.style.display = "grid"; - statsBar.classList.add("visible"); - } - - // Stock ticker - const stockMatch = query.match(/^\$([A-Za-z]{1,5})$/); - if (stockMatch) { - showStockWidget(stockMatch[1].toUpperCase()); - addHistory(query); - return; - } - - // Bangs - const bangMatch = query.match(/^!(\w+)\s*(.*)/); - if (bangMatch) { - const bang = "!" + bangMatch[1]; - const q = bangMatch[2]; - const src = Object.entries(SOURCES).find(([_, s]) => s.bang === bang); - if (src && q) { - Object.keys(SOURCES).forEach((k) => (SOURCES[k].enabled = k === src[0])); - renderSources(); - query = q; - } - } - - // Calc - if (query.startsWith("=")) { - showCalcResult(query.slice(1)); - return; - } - - state.query = query; - showLoading(); - addHistory(query); - - const start = performance.now(); - const enabled = Object.entries(SOURCES).filter(([_, s]) => s.enabled); - - fetchInstant(query); - - const promises = enabled.map(async ([k, s]) => { - try { - const r = await s.fetch(query); - SOURCES[k].count = r.length; - $(`c-${k}`).textContent = r.length; - return r; - } catch (e) { - return []; - } - }); - - const all = await Promise.all(promises); - state.results = all.flat(); - const elapsed = Math.round(performance.now() - start); - - $("stat-results").textContent = state.results.length; - $("stat-sources").textContent = enabled.length; - $("stat-time").textContent = elapsed + "MS"; - $("results-label").textContent = `${state.results.length} RESULTS`; - $("results-info").textContent = `${elapsed}MS // ${enabled.length} SOURCES`; - - renderResults(); -} - -function goHome() { - state.hasSearched = false; - heroEl.classList.remove("compact"); - mainEl.style.display = "none"; - statsBar.classList.remove("visible"); - searchInput.value = ""; -} - -// ═══════════════════════════════════════════════════════════ -// STOCK WIDGET -// ═══════════════════════════════════════════════════════════ -function showStockWidget(ticker) { - const symbol = getSymbol(ticker); - const widgetId = "tv_" + Math.random().toString(36).substr(2, 9); - const isLight = document.body.classList.contains("light-mode"); - - resultsEl.innerHTML = ` -
-
-
-

${ticker}

-
${symbol}
-
-
- - -
-
-
-
- `; - - $("results-label").textContent = `STOCK: ${ticker}`; - $("results-info").textContent = symbol; - - loadTradingView(widgetId, symbol, isLight); -} - -function loadTradingView(containerId, symbol, isLight) { - if (!window.TradingView) { - const s = document.createElement("script"); - s.src = "https://s3.tradingview.com/tv.js"; - s.onload = () => createChart(containerId, symbol, isLight); - document.head.appendChild(s); - } else { - createChart(containerId, symbol, isLight); - } -} - -function createChart(containerId, symbol, isLight) { - new TradingView.widget({ - allow_symbol_change: true, - backgroundColor: isLight ? "rgba(244, 244, 245, 1)" : "rgba(0,0,0,1)", - container_id: containerId, - enable_publishing: false, - gridColor: isLight ? "rgba(220, 220, 220, 1)" : "rgba(30,30,30,1)", - height: 500, - interval: "D", - locale: "en", - studies: ["Volume@tv-basicstudies"], - style: "1", - symbol: symbol, - theme: isLight ? "light" : "dark", - timezone: "Etc/UTC", - toolbar_bg: isLight ? "#f4f4f5" : "#000", - width: "100%", - }); -} - -async function fetchInstant(query) { - if (/^[a-zA-Z]+$/.test(query) && SOURCES.dictionary.enabled) { - try { - const r = await fetch( - `https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(query)}`, - ); - if (r.ok) { - const d = await r.json(); - if (d[0]) { - const meanings = d[0].meanings - .slice(0, 3) - .map((m) => `[${m.partOfSpeech.toUpperCase()}] ${m.definitions[0]?.definition}`) - .join("\n\n"); - showInstant({ - content: meanings, - source: "DICTIONARY", - title: d[0].word.toUpperCase() + (d[0].phonetic ? ` ${d[0].phonetic}` : ""), - }); - return; - } - } - } catch {} - } - if (SOURCES.wikipedia.enabled) { - const w = await SOURCES.wikipedia.instant?.(query); - if (w) showInstant(w); - } -} - -function showInstant(data) { - const el = document.createElement("div"); - el.className = "instant-box"; - el.innerHTML = ` -
INSTANT ANSWER
-
${esc(data.title)}
-
${esc(data.content) - .split("\n") - .map((p) => `

${p}

`) - .join("")}
-
- SOURCE: ${esc(data.source)} - ${data.url ? `VIEW FULL →` : ""} -
- `; - resultsEl.innerHTML = ""; - resultsEl.appendChild(el); -} - -function renderResults() { - if (state.results.length === 0) { - resultsEl.innerHTML = `
NO RESULTS
TRY DIFFERENT KEYWORDS OR ENABLE MORE SOURCES
`; - return; - } - - const instant = resultsEl.querySelector(".instant-box"); - resultsEl.innerHTML = ""; - if (instant) resultsEl.appendChild(instant); - - state.results.forEach((r, i) => { - const el = document.createElement("div"); - el.className = "result"; - el.innerHTML = ` -
${String(i + 1).padStart(2, "0")}
-
-
${r.source.toUpperCase()}
-
${esc(r.title)}
-
${esc(r.url)}
-
${highlight(esc(r.snippet), state.query)}
- ${ - r.meta - ? `
- ${r.meta.stars !== undefined ? `★ ${fmt(r.meta.stars)}` : ""} - ${r.meta.forks !== undefined ? `⑂ ${fmt(r.meta.forks)}` : ""} - ${r.meta.score !== undefined ? `↑ ${fmt(r.meta.score)}` : ""} - ${r.meta.comments !== undefined ? `◨ ${fmt(r.meta.comments)}` : ""} - ${r.meta.words !== undefined ? `◎ ${fmt(r.meta.words)}W` : ""} - ${r.meta.lang ? `${r.meta.lang.toUpperCase()}` : ""} - ${r.meta.version ? `V${r.meta.version}` : ""} -
` - : "" - } -
- `; - resultsEl.appendChild(el); - }); -} - -function showLoading() { - resultsEl.innerHTML = Array(4) - .fill( - `
FETCHING
`, - ) - .join(""); -} - -function showCalcResult(expr) { - try { - const result = Function('"use strict";return (' + expr.replace(/\^/g, "**") + ")")(); - resultsEl.innerHTML = `
CALCULATOR
${esc(expr)} = ${result}
`; - } catch { - toast("INVALID EXPRESSION"); - } -} - -// ═══════════════════════════════════════════════════════════ -// CALCULATOR -// ═══════════════════════════════════════════════════════════ -function setupCalc() { - document.querySelectorAll(".calc-btn").forEach((btn) => { - btn.onclick = () => { - const v = btn.dataset.v; - if (v === "C") { - state.calcExpr = ""; - state.calcResult = "0"; - } else if (v === "=") { - try { - state.calcResult = Function( - '"use strict";return (' + state.calcExpr.replace(/\^/g, "**") + ")", - )(); - } catch { - state.calcResult = "ERR"; - } - } else { - state.calcExpr += v; - } - $("calc-expr").textContent = state.calcExpr; - $("calc-result").textContent = state.calcResult; - }; - }); -} - -// ═══════════════════════════════════════════════════════════ -// TOOLS -// ═══════════════════════════════════════════════════════════ -function toolUUID() { - const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { - const r = (Math.random() * 16) | 0; - return (c === "x" ? r : (r & 0x3) | 0x8).toString(16); - }); - copy(uuid); - toast("UUID: " + uuid.substring(0, 13) + "..."); -} - -let timerInterval, - timerSec = 0; -function toolTimer() { - if (!state.hasSearched) { - state.hasSearched = true; - heroEl.classList.add("compact"); - mainEl.style.display = "grid"; - statsBar.classList.add("visible"); - } - resultsEl.innerHTML = `
TIMER
00:00:00
`; - $("results-label").textContent = "TIMER"; -} -window.startTimer = () => { - if (timerInterval) return; - timerInterval = setInterval(() => { - timerSec++; - const h = String(Math.floor(timerSec / 3600)).padStart(2, "0"); - const m = String(Math.floor((timerSec % 3600) / 60)).padStart(2, "0"); - const s = String(timerSec % 60).padStart(2, "0"); - $("timer-display").textContent = `${h}:${m}:${s}`; - }, 1000); -}; -window.stopTimer = () => { - clearInterval(timerInterval); - timerInterval = null; -}; -window.resetTimer = () => { - stopTimer(); - timerSec = 0; - $("timer-display").textContent = "00:00:00"; -}; - -function toolBase64() { - if (!state.hasSearched) { - state.hasSearched = true; - heroEl.classList.add("compact"); - mainEl.style.display = "grid"; - statsBar.classList.add("visible"); - } - resultsEl.innerHTML = `
BASE64
OUTPUT
`; - $("results-label").textContent = "BASE64"; -} - -function toolJSON() { - if (!state.hasSearched) { - state.hasSearched = true; - heroEl.classList.add("compact"); - mainEl.style.display = "grid"; - statsBar.classList.add("visible"); - } - resultsEl.innerHTML = `
JSON
OUTPUT
`; - $("results-label").textContent = "JSON"; -} -window.formatJSON = () => { - try { - $("json-output").textContent = JSON.stringify(JSON.parse($("json-input").value), null, 2); - } catch (e) { - $("json-output").textContent = "INVALID: " + e.message; - } -}; -window.minifyJSON = () => { - try { - $("json-output").textContent = JSON.stringify(JSON.parse($("json-input").value)); - } catch (e) { - $("json-output").textContent = "INVALID: " + e.message; - } -}; - -function toolHash() { - if (!state.hasSearched) { - state.hasSearched = true; - heroEl.classList.add("compact"); - mainEl.style.display = "grid"; - statsBar.classList.add("visible"); - } - resultsEl.innerHTML = `
SHA-256 HASH
OUTPUT
`; - $("results-label").textContent = "HASH"; -} -window.genHash = async () => { - const data = new TextEncoder().encode($("hash-input").value); - const hash = await crypto.subtle.digest("SHA-256", data); - $("hash-output").textContent = Array.from(new Uint8Array(hash)) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); -}; - -function toolColor() { - if (!state.hasSearched) { - state.hasSearched = true; - heroEl.classList.add("compact"); - mainEl.style.display = "grid"; - statsBar.classList.add("visible"); - } - resultsEl.innerHTML = `
COLOR
`; - const updateColor = () => { - const hex = $("color-input").value; - const r = parseInt(hex.slice(1, 3), 16); - const g = parseInt(hex.slice(3, 5), 16); - const b = parseInt(hex.slice(5, 7), 16); - $("color-output").innerHTML = - `HEX: ${hex}
RGB: ${r}, ${g}, ${b}
HSL: ${rgbToHsl(r, g, b)}`; - }; - $("color-input").oninput = updateColor; - updateColor(); - $("results-label").textContent = "COLOR"; -} -function rgbToHsl(r, g, b) { - r /= 255; - g /= 255; - b /= 255; - const max = Math.max(r, g, b), - min = Math.min(r, g, b); - let h, - s, - l = (max + min) / 2; - if (max === min) { - h = s = 0; - } else { - const d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = ((g - b) / d + (g < b ? 6 : 0)) / 6; - break; - case g: - h = ((b - r) / d + 2) / 6; - break; - case b: - h = ((r - g) / d + 4) / 6; - break; - } - } - return `${Math.round(h * 360)}°, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%`; -} - -function toolPassword() { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*"; - let pass = ""; - for (let i = 0; i < 20; i++) pass += chars[Math.floor(Math.random() * chars.length)]; - copy(pass); - toast("PASSWORD: " + pass.substring(0, 10) + "..."); -} - -function toolLoremIpsum() { - const lorem = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris."; - copy(lorem); - toast("LOREM IPSUM COPIED"); -} - -// ═══════════════════════════════════════════════════════════ -// HISTORY -// ═══════════════════════════════════════════════════════════ -function addHistory(q) { - state.history = state.history.filter((h) => h.q !== q); - state.history.unshift({ q, t: Date.now() }); - state.history = state.history.slice(0, 20); - localStorage.setItem("sh", JSON.stringify(state.history)); - renderHistory(); -} - -function renderHistory() { - const list = $("history-list"); - if (!state.history.length) { - list.innerHTML = '
NO HISTORY
'; - return; - } - list.innerHTML = state.history - .slice(0, 6) - .map( - (h) => - `
${esc(h.q)}${timeAgo(h.t)}
`, - ) - .join(""); -} - -// ═══════════════════════════════════════════════════════════ -// CLOCKS -// ═══════════════════════════════════════════════════════════ -function updateClocks() { - const now = new Date(); - $("clock").textContent = now.toLocaleTimeString("en-GB"); - const zones = [ - { label: "LOCAL", tz: Intl.DateTimeFormat().resolvedOptions().timeZone }, - { label: "UTC", tz: "UTC" }, - { label: "NYC", tz: "America/New_York" }, - { label: "TOKYO", tz: "Asia/Tokyo" }, - ]; - $("world-clocks").innerHTML = zones - .map( - (z) => - `
${new Date().toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: z.tz })}
${z.label}
`, - ) - .join(""); -} - -// ═══════════════════════════════════════════════════════════ -// LISTENERS -// ═══════════════════════════════════════════════════════════ -function setupListeners() { - let debounce; - searchInput.addEventListener("input", (e) => { - clearTimeout(debounce); - debounce = setTimeout(() => search(e.target.value), 300); - }); - searchInput.addEventListener("keydown", (e) => { - if (e.key === "Enter") { - clearTimeout(debounce); - search(searchInput.value); - } - if (e.key === "Escape") goHome(); - }); - document.addEventListener("keydown", (e) => { - if ( - e.key === "/" && - document.activeElement !== searchInput && - document.activeElement !== $("watchlist-input") - ) { - e.preventDefault(); - searchInput.focus(); - } - if (e.key === "Escape") goHome(); - }); - $("clear-btn").onclick = goHome; - $("voice-btn").onclick = voiceSearch; -} - -function voiceSearch() { - if (!("webkitSpeechRecognition" in window || "SpeechRecognition" in window)) { - toast("VOICE NOT SUPPORTED"); - return; - } - const Sr = window.SpeechRecognition || window.webkitSpeechRecognition; - const r = new Sr(); - r.lang = "en-US"; - $("voice-btn").style.background = "#fff"; - $("voice-btn").style.color = "#000"; - r.onresult = (e) => { - searchInput.value = e.results[0][0].transcript; - search(searchInput.value); - }; - r.onend = () => { - $("voice-btn").style.background = ""; - $("voice-btn").style.color = ""; - }; - r.start(); -} - -// ═══════════════════════════════════════════════════════════ -// UTILITIES -// ═══════════════════════════════════════════════════════════ -function esc(s) { - if (!s) return ""; - return s.replace( - /[&<>"']/g, - (m) => ({ "'": "'", '"': """, "&": "&", "<": "<", ">": ">" })[m], - ); -} -function decodeHTML(s) { - const t = document.createElement("textarea"); - t.innerHTML = s; - return t.value; -} -function highlight(text, query) { - if (!query) return text; - const words = query.split(/\s+/).filter((w) => w.length > 2); - let result = text; - words.forEach((w) => { - result = result.replace(new RegExp(`(${w})`, "gi"), "$1"); - }); - return result; -} -function fmt(n) { - if (n >= 1000000) return (n / 1000000).toFixed(1) + "M"; - if (n >= 1000) return (n / 1000).toFixed(1) + "K"; - return n?.toString() || "0"; -} -function timeAgo(ts) { - const s = Math.floor((Date.now() - ts) / 1000); - if (s < 60) return "NOW"; - if (s < 3600) return Math.floor(s / 60) + "M"; - if (s < 86400) return Math.floor(s / 3600) + "H"; - return Math.floor(s / 86400) + "D"; -} -function copy(text) { - navigator.clipboard.writeText(text); - toast("COPIED"); -} -function toast(msg) { - const t = document.createElement("div"); - t.className = "toast"; - t.textContent = msg; - $("toast-container").appendChild(t); - setTimeout(() => t.remove(), 3000); -} -function searchFor(q) { - searchInput.value = q; - search(q); -} -function insertBang(bang) { - searchInput.value = bang + " "; - searchInput.focus(); -} - -init(); -- cgit v1.2.3-70-g09d2