changes
This commit is contained in:
parent
ef29695427
commit
7fae9df5b4
103
content/root.css
103
content/root.css
@ -42,6 +42,103 @@
|
||||
--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;
|
||||
@ -356,6 +453,9 @@ button.scroll:hover {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: none;
|
||||
}
|
||||
.radchat {
|
||||
display: none;
|
||||
}
|
||||
@ -451,6 +551,9 @@ button.scroll:hover {
|
||||
|
||||
/* Small mobile devices */
|
||||
@media screen and (max-width: 380px) {
|
||||
.search-container {
|
||||
display: none;
|
||||
}
|
||||
.radchat {
|
||||
display: none;
|
||||
}
|
||||
|
@ -67,6 +67,20 @@
|
||||
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
|
||||
</svg></button>
|
||||
</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>
|
||||
|
||||
|
189
content/root.js
189
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) =>
|
||||
`<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();
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user