This commit is contained in:
Radon 2025-01-21 14:32:54 -06:00
parent ef29695427
commit 7fae9df5b4
4 changed files with 361 additions and 54 deletions

View File

@ -1,47 +1,144 @@
[data-theme="dark"] { [data-theme="dark"] {
--radchat-color: #40a02b; --radchat-color: #40a02b;
--main-bg-color: #11111b; --main-bg-color: #11111b;
--pum-button-inactive-fg: #cdd6f4; --pum-button-inactive-fg: #cdd6f4;
--pum-button-inactive-bg: #11111b; --pum-button-inactive-bg: #11111b;
--pum-button-active-fg: #89b4fa; --pum-button-active-fg: #89b4fa;
--pum-button-active-bg: #1e1e2e; --pum-button-active-bg: #1e1e2e;
--pum-title-color: #cdd6f4; --pum-title-color: #cdd6f4;
--pum-bg-color: #1e1e2e; --pum-bg-color: #1e1e2e;
--user-color: #89b4fa; --user-color: #89b4fa;
--timestamp-color: #313244; --timestamp-color: #313244;
--separator-color: #181825; --separator-color: #181825;
--message-color: #cdd6f4; --message-color: #cdd6f4;
--message-bg-color: #1e1e2e; --message-bg-color: #1e1e2e;
--input-bg-color: #181825; --input-bg-color: #181825;
--input-text-color: #cdd6f4; --input-text-color: #cdd6f4;
--input-button-inactive-bg: #b4befe; --input-button-inactive-bg: #b4befe;
--input-button-inactive-fg: #11111b; --input-button-inactive-fg: #11111b;
--input-button-active-bg: #89b4fa; --input-button-active-bg: #89b4fa;
--input-button-active-fg: #11111b; --input-button-active-fg: #11111b;
} }
[data-theme="light"] { [data-theme="light"] {
--radchat-color: #40a02b; --radchat-color: #40a02b;
--main-bg-color: #dce0e8; --main-bg-color: #dce0e8;
--pum-button-inactive-fg: #4c4f69; --pum-button-inactive-fg: #4c4f69;
--pum-button-inactive-bg: #dce0e8; --pum-button-inactive-bg: #dce0e8;
--pum-button-active-fg: #1e66f5; --pum-button-active-fg: #1e66f5;
--pum-button-active-bg: #eff1f5; --pum-button-active-bg: #eff1f5;
--pum-title-color: #4c4f69; --pum-title-color: #4c4f69;
--pum-bg-color: #eff1f5; --pum-bg-color: #eff1f5;
--user-color: #1e66f5; --user-color: #1e66f5;
--timestamp-color: #8c8fa1; --timestamp-color: #8c8fa1;
--separator-color: #e6e9ef; --separator-color: #e6e9ef;
--message-color: #4c4f69; --message-color: #4c4f69;
--message-bg-color: #eff1f5; --message-bg-color: #eff1f5;
--input-bg-color: #e6e9ef; --input-bg-color: #e6e9ef;
--input-text-color: #4c4f69; --input-text-color: #4c4f69;
--input-button-inactive-bg: #7287fd; --input-button-inactive-bg: #7287fd;
--input-button-inactive-fg: #dce0e8; --input-button-inactive-fg: #dce0e8;
--input-button-active-bg: #1e66f5; --input-button-active-bg: #1e66f5;
--input-button-active-fg: #dce0e8; --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 { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
margin: 0; margin: 0;
@ -282,16 +379,16 @@ input {
flex-grow: 1; flex-grow: 1;
} }
textarea { textarea {
padding: 10px; padding: 10px;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
background-color: var(--input-bg-color); background-color: var(--input-bg-color);
color: var(--input-text-color); color: var(--input-text-color);
flex-grow: 1; flex-grow: 1;
resize: none; /* Prevents manual resizing */ resize: none; /* Prevents manual resizing */
min-height: 38px; /* Match your previous input height */ min-height: 38px; /* Match your previous input height */
line-height: 1.4; line-height: 1.4;
font-family: Arial, sans-serif; /* Match your body font */ font-family: Arial, sans-serif; /* Match your body font */
} }
button { button {
padding: 10px 20px; padding: 10px 20px;
@ -356,20 +453,23 @@ button.scroll:hover {
max-width: 100%; max-width: 100%;
} }
.radchat { .search-container {
display: none; display: none;
} }
.radchat {
display: none;
}
.current-user { .current-user {
display: none; display: none;
} }
.header-controls { .header-controls {
top: 10px; top: 10px;
gap: 15px; gap: 15px;
width: 100%; width: 100%;
justify-content: center; justify-content: center;
} }
.settings-button, .users-button, .theme-button { .settings-button, .users-button, .theme-button {
width: 36px; width: 36px;
@ -451,6 +551,9 @@ button.scroll:hover {
/* Small mobile devices */ /* Small mobile devices */
@media screen and (max-width: 380px) { @media screen and (max-width: 380px) {
.search-container {
display: none;
}
.radchat { .radchat {
display: none; display: none;
} }

View File

@ -44,7 +44,7 @@
<!-- Users will be loaded here --> <!-- Users will be loaded here -->
</div> </div>
</div> </div>
<div class="username-section" id="settings-panel"> <div class="username-section" id="settings-panel">
<h3>Username</h3> <h3>Username</h3>
<div style="display: flex; gap: 10px;"> <div style="display: flex; gap: 10px;">
@ -67,6 +67,20 @@
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/> <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
</svg></button> </svg></button>
</div> </div>
<div class="search-trigger">
<div class="search-container" id="searchContainer">
<button class="search-button" id="searchButton">
<div class="search-icon"></div>
</button>
<input
type="text"
class="search-input"
id="searchInput"
placeholder="Type to search page..."
>
<span class="search-count" id="searchCount"></span>
</div>
</div>
<div id="status"></div> <div id="status"></div>
</div> </div>

View File

@ -495,6 +495,195 @@ async function initialize() {
setInterval(loadUsers, 1000); setInterval(loadUsers, 1000);
setInterval(pingCheck, 3000); setInterval(pingCheck, 3000);
await loadMessages(true); 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) =>
`<span class="search-highlight">${match}</span>`,
);
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(); initialize();

View File

@ -5,11 +5,12 @@
### Mid Priority ### Mid Priority
- Other embeds (Twitter posts, spotify tracks, soundcloud, github repos, instagram posts, other video platforms) - Other embeds (Twitter posts, spotify tracks, soundcloud, github repos, instagram posts, other video platforms)
### Low Priority ### Low Priority
- Nothing yet - Reposition the search button
- Fix mobile views instead of hiding elements that you don't want to position properly
## Backend ## Backend
### High Priority ### High Priority
- Lazy load with pagination (frontend and backend) - Lazy load with pagination (frontend and backend)
### Mid Priority ### Mid Priority
- Nothing yet - Nothing yet
### Low Priority ### Low Priority
- Search functionality? - Nothing yet