From 7fae9df5b4cd7e94fc35f14ddbb824734de27f8c Mon Sep 17 00:00:00 2001 From: Radon Date: Tue, 21 Jan 2025 14:32:54 -0600 Subject: [PATCH] changes --- content/root.css | 205 ++++++++++++++++++++++++++++++++++------------ content/root.html | 16 +++- content/root.js | 189 ++++++++++++++++++++++++++++++++++++++++++ readme.md | 5 +- 4 files changed, 361 insertions(+), 54 deletions(-) diff --git a/content/root.css b/content/root.css index 894c77d..ec90449 100644 --- a/content/root.css +++ b/content/root.css @@ -1,47 +1,144 @@ [data-theme="dark"] { - --radchat-color: #40a02b; - --main-bg-color: #11111b; - --pum-button-inactive-fg: #cdd6f4; - --pum-button-inactive-bg: #11111b; - --pum-button-active-fg: #89b4fa; - --pum-button-active-bg: #1e1e2e; - --pum-title-color: #cdd6f4; - --pum-bg-color: #1e1e2e; - --user-color: #89b4fa; - --timestamp-color: #313244; - --separator-color: #181825; - --message-color: #cdd6f4; - --message-bg-color: #1e1e2e; - --input-bg-color: #181825; - --input-text-color: #cdd6f4; - --input-button-inactive-bg: #b4befe; - --input-button-inactive-fg: #11111b; - --input-button-active-bg: #89b4fa; - --input-button-active-fg: #11111b; + --radchat-color: #40a02b; + --main-bg-color: #11111b; + --pum-button-inactive-fg: #cdd6f4; + --pum-button-inactive-bg: #11111b; + --pum-button-active-fg: #89b4fa; + --pum-button-active-bg: #1e1e2e; + --pum-title-color: #cdd6f4; + --pum-bg-color: #1e1e2e; + --user-color: #89b4fa; + --timestamp-color: #313244; + --separator-color: #181825; + --message-color: #cdd6f4; + --message-bg-color: #1e1e2e; + --input-bg-color: #181825; + --input-text-color: #cdd6f4; + --input-button-inactive-bg: #b4befe; + --input-button-inactive-fg: #11111b; + --input-button-active-bg: #89b4fa; + --input-button-active-fg: #11111b; } [data-theme="light"] { - --radchat-color: #40a02b; - --main-bg-color: #dce0e8; - --pum-button-inactive-fg: #4c4f69; - --pum-button-inactive-bg: #dce0e8; - --pum-button-active-fg: #1e66f5; - --pum-button-active-bg: #eff1f5; - --pum-title-color: #4c4f69; - --pum-bg-color: #eff1f5; - --user-color: #1e66f5; - --timestamp-color: #8c8fa1; - --separator-color: #e6e9ef; - --message-color: #4c4f69; - --message-bg-color: #eff1f5; - --input-bg-color: #e6e9ef; - --input-text-color: #4c4f69; - --input-button-inactive-bg: #7287fd; - --input-button-inactive-fg: #dce0e8; - --input-button-active-bg: #1e66f5; - --input-button-active-fg: #dce0e8; + --radchat-color: #40a02b; + --main-bg-color: #dce0e8; + --pum-button-inactive-fg: #4c4f69; + --pum-button-inactive-bg: #dce0e8; + --pum-button-active-fg: #1e66f5; + --pum-button-active-bg: #eff1f5; + --pum-title-color: #4c4f69; + --pum-bg-color: #eff1f5; + --user-color: #1e66f5; + --timestamp-color: #8c8fa1; + --separator-color: #e6e9ef; + --message-color: #4c4f69; + --message-bg-color: #eff1f5; + --input-bg-color: #e6e9ef; + --input-text-color: #4c4f69; + --input-button-inactive-bg: #7287fd; + --input-button-inactive-fg: #dce0e8; + --input-button-active-bg: #1e66f5; + --input-button-active-fg: #dce0e8; } +.search-trigger { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1000; +} + +.search-container { + position: flex; + display: flex; + align-items: center; + background: var(--pum-button-active-bg); + border-radius: 24px; + transition: width 0.2s ease; + overflow: hidden; + width: 48px; + height: 48px; +} + +.search-container.expanded { + width: 300px; +} + +.search-button { + background: none; + border: none; + padding: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: var(--pum-button-active-bg); + color: var(--pum-button-inactive-fg); +} + +.search-button:hover { + background: var(--pum-button-active-bg); + color: var(--pum-button-active-fg); +} + +.search-input { + display: none; + flex: 2; + border: none; + padding: 8px; + margin-left: 4px; + outline: none; + font-size: 14px; +} + +.search-container.expanded .search-input { + display: block; +} + +.search-highlight { + background-color: yellow; + color: gray; +} + +.search-highlight.current { + background-color: darkorange; + color: black; +} + +/* Search icon using CSS */ +.search-icon { + width: 15px; + height: 15px; + border: 2px solid var(--pum-button-inactive-fg); + border-radius: 50%; + position: relative; +} + +.search-icon::after { + content: ''; + position: absolute; + width: 2px; + height: 10px; + background: var(--pum-button-inactive-fg); + bottom: -8px; + right: -3px; + transform: rotate(-45deg); +} + +.search-count { + display: none; + margin-left: 8px; + font-size: 12px; + color: var(--pum-title-color); +} + +.search-container.expanded .search-count { + display: block; +} + + body { font-family: Arial, sans-serif; margin: 0; @@ -282,16 +379,16 @@ input { flex-grow: 1; } textarea { - padding: 10px; - border: none; - border-radius: 4px; - background-color: var(--input-bg-color); - color: var(--input-text-color); - flex-grow: 1; - resize: none; /* Prevents manual resizing */ - min-height: 38px; /* Match your previous input height */ - line-height: 1.4; - font-family: Arial, sans-serif; /* Match your body font */ + padding: 10px; + border: none; + border-radius: 4px; + background-color: var(--input-bg-color); + color: var(--input-text-color); + flex-grow: 1; + resize: none; /* Prevents manual resizing */ + min-height: 38px; /* Match your previous input height */ + line-height: 1.4; + font-family: Arial, sans-serif; /* Match your body font */ } button { padding: 10px 20px; @@ -356,20 +453,23 @@ button.scroll:hover { max-width: 100%; } - .radchat { + .search-container { display: none; } + .radchat { + display: none; + } .current-user { display: none; - } + } .header-controls { top: 10px; gap: 15px; width: 100%; justify-content: center; - } + } .settings-button, .users-button, .theme-button { width: 36px; @@ -451,6 +551,9 @@ button.scroll:hover { /* Small mobile devices */ @media screen and (max-width: 380px) { + .search-container { + display: none; + } .radchat { display: none; } diff --git a/content/root.html b/content/root.html index bb720a1..9c274c0 100644 --- a/content/root.html +++ b/content/root.html @@ -44,7 +44,7 @@ - +

Username

@@ -67,6 +67,20 @@
+
+
+ + + +
+
diff --git a/content/root.js b/content/root.js index 34d723c..ff261e1 100644 --- a/content/root.js +++ b/content/root.js @@ -495,6 +495,195 @@ async function initialize() { setInterval(loadUsers, 1000); setInterval(pingCheck, 3000); await loadMessages(true); + initializeSearchBox(); +} + +function initializeSearchBox() { + const searchContainer = document.getElementById("searchContainer"); + const searchButton = document.getElementById("searchButton"); + const searchInput = document.getElementById("searchInput"); + const searchCount = document.getElementById("searchCount"); + + let currentMatchIndex = -1; + let matches = []; + + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + + function clearHighlights() { + // Remove all existing highlights + const highlights = document.querySelectorAll( + ".search-highlight", + ); + highlights.forEach((highlight) => { + const parent = highlight.parentNode; + parent.replaceChild( + document.createTextNode(highlight.textContent), + highlight, + ); + parent.normalize(); + }); + matches = []; + currentMatchIndex = -1; + searchCount.textContent = ""; + } + + function findTextNodes(element, textNodes = []) { + // Skip certain elements + if (element.nodeType === Node.ELEMENT_NODE) { // Check if it's an element node first + if ( + element.tagName === "SCRIPT" || + element.tagName === "STYLE" || + element.tagName === "NOSCRIPT" || + (element.classList && + element.classList.contains( + "search-container", + )) || + (element.classList && + element.classList.contains( + "search-highlight", + )) + ) { + return textNodes; + } + } + + // Check if this node is a text node with non-whitespace content + if ( + element.nodeType === Node.TEXT_NODE && + element.textContent.trim() + ) { + textNodes.push(element); + } + + // Recursively check all child nodes + const children = element.childNodes; + for (let i = 0; i < children.length; i++) { + findTextNodes(children[i], textNodes); + } + + return textNodes; + } + + function findAndHighlight(searchText) { + if (!searchText) { + clearHighlights(); + return; + } + + clearHighlights(); + + const searchRegex = new RegExp(escapeRegExp(searchText), "gi"); + const textNodes = findTextNodes(document.body); + + textNodes.forEach((node) => { + const matches = [ + ...node.textContent.matchAll(searchRegex), + ]; + if (matches.length > 0) { + const span = document.createElement("span"); + span.innerHTML = node.textContent.replace( + searchRegex, + (match) => + `${match}`, + ); + node.parentNode.replaceChild(span, node); + } + }); + + // Collect all highlights + matches = Array.from( + document.querySelectorAll(".search-highlight"), + ); + if (matches.length > 0) { + currentMatchIndex = 0; + matches[0].classList.add("current"); + matches[0].scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + + // Update count + searchCount.textContent = matches.length > 0 + ? `${currentMatchIndex + 1}/${matches.length}` + : "No matches"; + } + + function nextMatch() { + if (matches.length === 0) return; + + matches[currentMatchIndex].classList.remove("current"); + currentMatchIndex = (currentMatchIndex + 1) % matches.length; + matches[currentMatchIndex].classList.add("current"); + matches[currentMatchIndex].scrollIntoView({ + behavior: "smooth", + block: "center", + }); + searchCount.textContent = `${ + currentMatchIndex + 1 + }/${matches.length}`; + } + + searchButton.addEventListener("click", () => { + if (!searchContainer.classList.contains("expanded")) { + searchContainer.classList.add("expanded"); + searchInput.focus(); + } else { + nextMatch(); + } + }); + + searchInput.addEventListener("input", (e) => { + findAndHighlight(e.target.value.trim()); + }); + + searchInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + nextMatch(); + } else if (e.key === "Escape") { + searchContainer.classList.remove("expanded"); + searchInput.value = ""; + clearHighlights(); + } + }); + + // Debug function to check search coverage (call from console: checkSearchCoverage()) + window.checkSearchCoverage = function () { + const textNodes = findTextNodes(document.body); + console.log( + "Total searchable text nodes found:", + textNodes.length, + ); + textNodes.forEach((node, i) => { + console.log(`Node ${i + 1}:`, { + text: node.textContent, + parent: node.parentElement.tagName, + path: getNodePath(node), + }); + }); + }; + + function getNodePath(node) { + const path = []; + while (node && node.parentElement) { + let index = Array.from(node.parentElement.childNodes) + .indexOf(node); + path.unshift(`${node.parentElement.tagName}[${index}]`); + node = node.parentElement; + } + return path.join(" > "); + } + + // Close search when clicking outside + document.addEventListener("click", (e) => { + if (!searchContainer.contains(e.target)) { + searchContainer.classList.remove("expanded"); + searchInput.value = ""; + clearHighlights(); + } + }); } initialize(); diff --git a/readme.md b/readme.md index f709cb5..23ea664 100644 --- a/readme.md +++ b/readme.md @@ -5,11 +5,12 @@ ### Mid Priority - Other embeds (Twitter posts, spotify tracks, soundcloud, github repos, instagram posts, other video platforms) ### Low Priority -- Nothing yet +- Reposition the search button +- Fix mobile views instead of hiding elements that you don't want to position properly ## Backend ### High Priority - Lazy load with pagination (frontend and backend) ### Mid Priority - Nothing yet ### Low Priority -- Search functionality? +- Nothing yet