better search

This commit is contained in:
MathMan05 2025-04-10 12:10:34 -05:00
parent 01e267846e
commit 83dc0ebb71
11 changed files with 321 additions and 70 deletions

View file

@ -928,12 +928,35 @@ class Channel extends SnowFlake {
} }
} }
async focus(id: string) { async focus(id: string) {
console.time(); const m = this.messages.get(id);
if (m && m.div) {
if (document.contains(m.div)) {
m.div.scrollIntoView({
behavior: "smooth",
block: "center",
});
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
m.div.classList.remove("jumped");
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
m.div.classList.add("jumped");
return;
}
}
console.log(await this.getmessage(id)); console.log(await this.getmessage(id));
await this.getHTML();
console.timeEnd(); if (this.localuser.channelfocus === this) {
this.localuser.channelfocus?.infinite.delete();
this.localuser.channelfocus = undefined;
}
await this.getHTML(true, false);
console.warn(id); console.warn(id);
this.infinite.focus(id); await this.buildmessages(id);
//debugger;
this.infinite.focus(id, true, true);
} }
editLast() { editLast() {
let message: Message | undefined = this.lastmessage; let message: Message | undefined = this.lastmessage;
@ -987,7 +1010,7 @@ class Channel extends SnowFlake {
if (!this.last_pin_timestamp && !this.lastpin) return false; if (!this.last_pin_timestamp && !this.lastpin) return false;
return this.last_pin_timestamp !== this.lastpin; return this.last_pin_timestamp !== this.lastpin;
} }
async getHTML(addstate = true) { async getHTML(addstate = true, getMessages = true) {
const pinnedM = document.getElementById("pinnedMDiv"); const pinnedM = document.getElementById("pinnedMDiv");
if (pinnedM) { if (pinnedM) {
if (this.unreadPins()) { if (this.unreadPins()) {
@ -1059,14 +1082,14 @@ class Channel extends SnowFlake {
if (!mobile) { if (!mobile) {
(document.getElementById("typebox") as HTMLDivElement).focus(); (document.getElementById("typebox") as HTMLDivElement).focus();
} }
await this.putmessages(); if (getMessages) await this.putmessages();
await prom; await prom;
if (id !== Channel.genid) { if (id !== Channel.genid) {
return; return;
} }
this.makereplybox(); this.makereplybox();
await this.buildmessages(); if (getMessages) await this.buildmessages();
//loading.classList.remove("loading"); //loading.classList.remove("loading");
} }
typingmap: Map<Member, number> = new Map(); typingmap: Map<Member, number> = new Map();
@ -1354,12 +1377,12 @@ class Channel extends SnowFlake {
} }
}); });
} }
async buildmessages() { async buildmessages(id: string | void) {
this.infinitefocus = false; this.infinitefocus = false;
await this.tryfocusinfinate(); await this.tryfocusinfinate(id);
} }
infinitefocus = false; infinitefocus = false;
async tryfocusinfinate() { async tryfocusinfinate(id: string | void) {
if (this.infinitefocus) return; if (this.infinitefocus) return;
this.infinitefocus = true; this.infinitefocus = true;
const messages = document.getElementById("channelw") as HTMLDivElement; const messages = document.getElementById("channelw") as HTMLDivElement;
@ -1370,13 +1393,14 @@ class Channel extends SnowFlake {
const loading = document.getElementById("loadingdiv") as HTMLDivElement; const loading = document.getElementById("loadingdiv") as HTMLDivElement;
const removetitle = document.getElementById("removetitle"); const removetitle = document.getElementById("removetitle");
//messages.innerHTML=""; //messages.innerHTML="";
let id: string | undefined; if (!id) {
if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) {
id = this.lastreadmessageid; id = this.lastreadmessageid;
} else if (this.lastreadmessageid && (id = this.findClosest(this.lastreadmessageid))) { } else if (this.lastreadmessageid && (id = this.findClosest(this.lastreadmessageid))) {
} else if (this.lastmessageid && this.messages.has(this.lastmessageid)) { } else if (this.lastmessageid && this.messages.has(this.lastmessageid)) {
id = this.goBackIds(this.lastmessageid, 50); id = this.goBackIds(this.lastmessageid, 50);
} }
}
if (!id) { if (!id) {
if (!removetitle) { if (!removetitle) {
const title = document.createElement("h2"); const title = document.createElement("h2");
@ -1400,8 +1424,9 @@ class Channel extends SnowFlake {
console.warn("rouge element detected and removed"); console.warn("rouge element detected and removed");
} }
messages.append(await this.infinite.getDiv(id)); messages.append(await this.infinite.getDiv(id));
this.infinite.updatestuff(); this.infinite.updatestuff();
this.infinite.watchForChange().then(async (_) => { await this.infinite.watchForChange().then(async (_) => {
//await new Promise(resolve => setTimeout(resolve, 0)); //await new Promise(resolve => setTimeout(resolve, 0));
this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P
loading.classList.remove("loading"); loading.classList.remove("loading");

View file

@ -54,6 +54,11 @@ class Direct extends Guild {
} }
} }
getHTML() { getHTML() {
const sideContainDiv = document.getElementById("sideContainDiv");
if (sideContainDiv) sideContainDiv.classList.remove("searchDiv");
const searchBox = document.getElementById("searchBox");
if (searchBox) searchBox.textContent = "";
const ddiv = document.createElement("div"); const ddiv = document.createElement("div");
const build = super.getHTML(); const build = super.getHTML();
const freindDiv = document.createElement("div"); const freindDiv = document.createElement("div");

View file

@ -855,6 +855,11 @@ class Guild extends SnowFlake {
} }
} }
getHTML() { getHTML() {
const sideContainDiv = document.getElementById("sideContainDiv");
if (sideContainDiv) sideContainDiv.classList.remove("searchDiv");
const searchBox = document.getElementById("searchBox");
if (searchBox) searchBox.textContent = "";
//this.printServers(); //this.printServers();
this.sortchannels(); this.sortchannels();
this.printServers(); this.printServers();

View file

@ -0,0 +1 @@
<svg viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg"><path fill="#c9c9c9" stroke="red" stroke-linecap="round" stroke-width="32.2" d="M16 164 164 16M16 16l148 148"/></svg>

After

Width:  |  Height:  |  Size: 179 B

View file

@ -0,0 +1 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg"><circle style="fill:none;stroke:#fe0000;stroke-width:80;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" cx="-178" cy="177.4" r="136" transform="scale(-1 1)"/><path style="fill:none;stroke:#fe0000;stroke-width:80;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="m274 280 200 192"/></svg>

After

Width:  |  Height:  |  Size: 389 B

View file

@ -78,7 +78,10 @@
<div id="pinnedMDiv"> <div id="pinnedMDiv">
<span class="svgicon svg-pin" id="pinnedM"></span> <span class="svgicon svg-pin" id="pinnedM"></span>
</div> </div>
<div class="searchMeta">
<div contenteditable="true" class="searchBox" id="searchBox"></div> <div contenteditable="true" class="searchBox" id="searchBox"></div>
<span id="searchX" class="svgicon svg-search" contenteditable="false"></span>
</div>
<label for="memberlisttoggle" id="memberlisttoggleicon"> <label for="memberlisttoggle" id="memberlisttoggleicon">
<span class="svgicon svg-friends"></span> <span class="svgicon svg-friends"></span>
</label> </label>

View file

@ -149,13 +149,32 @@ import {I18n} from "./i18n.js";
const searchBox = document.getElementById("searchBox") as CustomHTMLDivElement; const searchBox = document.getElementById("searchBox") as CustomHTMLDivElement;
const markdown = new MarkDown("", thisUser); const markdown = new MarkDown("", thisUser);
searchBox.markdown = markdown; searchBox.markdown = markdown;
const searchX = document.getElementById("searchX") as HTMLElement;
searchBox.addEventListener("keydown", (event) => { searchBox.addEventListener("keydown", (event) => {
if (event.key === "Enter") { if (event.key === "Enter") {
event.preventDefault(); event.preventDefault();
thisUser.mSearch(markdown.rawString); thisUser.mSearch(markdown.rawString);
} }
}); });
searchBox.addEventListener("keyup", () => {
if (searchBox.textContent === "") {
setTimeout(() => (searchBox.innerHTML = ""), 0);
searchX.classList.add("svg-search");
searchX.classList.remove("svg-plainx");
} else {
searchX.classList.remove("svg-search");
searchX.classList.add("svg-plainx");
}
});
searchX.onclick = () => {
if (searchX.classList.contains("svg-plainx")) {
markdown.txt = [];
searchBox.innerHTML = "";
searchX.classList.add("svg-search");
searchX.classList.remove("svg-plainx");
thisUser.mSearch("");
}
};
markdown.giveBox(searchBox); markdown.giveBox(searchBox);
markdown.setCustomBox((e) => { markdown.setCustomBox((e) => {

View file

@ -242,7 +242,18 @@ class InfiniteScroller {
} }
} }
async watchForChange(): Promise<boolean> { async watchForChange(stop = false): Promise<boolean> {
if (stop == true) {
let prom = this.changePromise;
while (this.changePromise) {
prom = this.changePromise;
await this.changePromise;
if (prom === this.changePromise) {
this.changePromise = undefined;
break;
}
}
}
if (this.changePromise) { if (this.changePromise) {
this.watchtime = true; this.watchtime = true;
return await this.changePromise; return await this.changePromise;
@ -268,6 +279,10 @@ class InfiniteScroller {
console.error(e); console.error(e);
res(false); res(false);
} finally { } finally {
if (stop === true) {
this.changePromise = undefined;
return;
}
setTimeout(() => { setTimeout(() => {
this.changePromise = undefined; this.changePromise = undefined;
if (this.watchtime) { if (this.watchtime) {
@ -279,17 +294,18 @@ class InfiniteScroller {
return await this.changePromise; return await this.changePromise;
} }
async focus(id: string, flash = true): Promise<void> { async focus(id: string, flash = true, sec = false): Promise<void> {
let element: HTMLElement | undefined; let element: HTMLElement | undefined;
for (const thing of this.HTMLElements) { for (const thing of this.HTMLElements) {
if (thing[1] === id) { if (thing[1] === id) {
element = thing[0]; element = thing[0];
} }
} }
if (element) { if (sec && element) {
if (flash) { if (flash) {
element.scrollIntoView({ element.scrollIntoView({
behavior: "smooth", behavior: "smooth",
inline: "center",
block: "center", block: "center",
}); });
await new Promise((resolve) => { await new Promise((resolve) => {
@ -301,9 +317,11 @@ class InfiniteScroller {
}); });
element.classList.add("jumped"); element.classList.add("jumped");
} else { } else {
element.scrollIntoView(); element.scrollIntoView({
block: "center",
});
} }
} else { } else if (!sec) {
this.resetVars(); this.resetVars();
//TODO may be a redundant loop, not 100% sure :P //TODO may be a redundant loop, not 100% sure :P
for (const thing of this.HTMLElements) { for (const thing of this.HTMLElements) {
@ -312,11 +330,16 @@ class InfiniteScroller {
this.HTMLElements = []; this.HTMLElements = [];
await this.firstElement(id); await this.firstElement(id);
this.updatestuff(); this.updatestuff();
await this.watchForChange(); await this.watchForChange(true);
await new Promise((resolve) => { this.changePromise = new Promise<boolean>((resolve) => {
setTimeout(resolve, 100); setTimeout(() => {
this.changePromise = undefined;
resolve(true);
}, 1000);
}); });
await this.focus(id, true); await this.focus(id, !element, true);
} else {
console.warn("elm not exist");
} }
} }

View file

@ -240,6 +240,7 @@ class Localuser {
this.rights = new Rights(rights); this.rights = new Rights(rights);
if (this.perminfo.user.disableColors === undefined) this.perminfo.user.disableColors = true; if (this.perminfo.user.disableColors === undefined) this.perminfo.user.disableColors = true;
this.updateTranslations();
} }
async gottenReady(ready: readyjson): Promise<void> { async gottenReady(ready: readyjson): Promise<void> {
await I18n.done; await I18n.done;
@ -1751,6 +1752,7 @@ class Localuser {
I18n.getTranslation("localuser.language"), I18n.getTranslation("localuser.language"),
(e) => { (e) => {
I18n.setLanguage(I18n.options()[e]); I18n.setLanguage(I18n.options()[e]);
this.updateTranslations();
}, },
[...langmap.values()], [...langmap.values()],
{ {
@ -2671,15 +2673,33 @@ class Localuser {
box.innerHTML = ""; box.innerHTML = "";
} }
searching = false; searching = false;
updateTranslations() {
const searchBox = document.getElementById("searchBox") as HTMLDivElement;
searchBox.style.setProperty("--hint-text", JSON.stringify(I18n.search.search()));
}
curSearch?: Symbol;
mSearch(query: string) { mSearch(query: string) {
const searchy = Symbol("search");
this.curSearch = searchy;
const p = new URLSearchParams("?"); const p = new URLSearchParams("?");
this.searching = true; this.searching = true;
p.set("content", query.trim()); p.set("content", query.trim());
p.set("sort_by", "timestamp");
p.set("sort_order", "desc");
let maxpage: undefined | number = undefined;
const sideDiv = document.getElementById("sideDiv");
const sideContainDiv = document.getElementById("sideContainDiv");
if (!sideDiv || !sideContainDiv) return;
const genPage = (page: number) => {
p.set("offset", page * 50 + "");
fetch(this.info.api + `/guilds/${this.lookingguild?.id}/messages/search/?` + p.toString(), { fetch(this.info.api + `/guilds/${this.lookingguild?.id}/messages/search/?` + p.toString(), {
headers: this.headers, headers: this.headers,
}) })
.then((_) => _.json()) .then((_) => _.json())
.then((json: {messages: [messagejson][]; total_results: number}) => { .then((json: {messages: [messagejson][]; total_results: number}) => {
if (this.curSearch !== searchy) {
return;
}
//FIXME total_results shall be ignored as it's known to be bad, spacebar bug. //FIXME total_results shall be ignored as it's known to be bad, spacebar bug.
const messages = json.messages const messages = json.messages
.map(([m]) => { .map(([m]) => {
@ -2691,12 +2711,49 @@ class Localuser {
return new Message(m, c, true); return new Message(m, c, true);
}) })
.filter((_) => _ !== undefined); .filter((_) => _ !== undefined);
const sideDiv = document.getElementById("sideDiv");
const sideContainDiv = document.getElementById("sideContainDiv");
if (!sideDiv || !sideContainDiv) return;
sideDiv.innerHTML = ""; sideDiv.innerHTML = "";
if (messages.length == 0 && page !== 0) {
maxpage = page - 1;
genPage(page - 1);
return;
} else if (messages.length !== 50) {
maxpage = page;
}
const sortBar = document.createElement("div");
sortBar.classList.add("flexltr", "sortBar");
const newB = document.createElement("button");
const old = document.createElement("button");
[newB.textContent, old.textContent] = [I18n.search.new(), I18n.search.old()];
old.onclick = () => {
p.set("sort_order", "asc");
deleteMessages();
genPage(0);
};
newB.onclick = () => {
p.set("sort_order", "desc");
deleteMessages();
genPage(0);
};
if (p.get("sort_order") === "asc") {
old.classList.add("selectedB");
} else {
newB.classList.add("selectedB");
}
const spaceElm = document.createElement("div");
spaceElm.classList.add("spaceElm");
sortBar.append(I18n.search.page(page + 1 + ""), spaceElm, newB, old);
sideDiv.append(sortBar);
sideContainDiv.classList.add("searchDiv"); sideContainDiv.classList.add("searchDiv");
let channel: Channel | undefined = undefined; let channel: Channel | undefined = undefined;
function deleteMessages() {
for (const elm of htmls) elm.remove();
}
const htmls: HTMLElement[] = [];
for (const message of messages) { for (const message of messages) {
if (channel !== message.channel) { if (channel !== message.channel) {
channel = message.channel; channel = message.channel;
@ -2704,6 +2761,7 @@ class Localuser {
h3.textContent = channel.name; h3.textContent = channel.name;
h3.classList.add("channelSTitle"); h3.classList.add("channelSTitle");
sideDiv.append(h3); sideDiv.append(h3);
htmls.push(h3);
} }
const html = message.buildhtml(undefined, true); const html = message.buildhtml(undefined, true);
html.addEventListener("click", async () => { html.addEventListener("click", async () => {
@ -2714,8 +2772,41 @@ class Localuser {
} }
}); });
sideDiv.append(html); sideDiv.append(html);
htmls.push(html);
} }
if (messages.length === 0) {
const noMs = document.createElement("h3");
noMs.textContent = I18n.search.nofind();
sideDiv.append(noMs);
}
const bottombuttons = document.createElement("div");
bottombuttons.classList.add("flexltr", "searchNavButtons");
const next = document.createElement("button");
if (page == maxpage) next.disabled = true;
next.onclick = () => {
deleteMessages();
genPage(page + 1);
};
const prev = document.createElement("button");
prev.onclick = () => {
deleteMessages();
genPage(page - 1);
};
if (page == 0) prev.disabled = true;
[next.textContent, prev.textContent] = [I18n.search.next(), I18n.search.back()];
bottombuttons.append(prev, next);
sideDiv.append(bottombuttons);
sideDiv.scrollTo({top: 0, behavior: "instant"});
}); });
};
if (query === "") {
sideContainDiv.classList.remove("searchDiv");
sideDiv.innerHTML = "";
this.searching = false;
this.getSidePannel();
return;
}
genPage(0);
} }
keydown: (event: KeyboardEvent) => unknown = () => {}; keydown: (event: KeyboardEvent) => unknown = () => {};

View file

@ -57,16 +57,52 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.searchNavButtons {
height: 0.3in;
flex-shrink: 0;
background: var(--secondary-bg);
top: 0px;
padding: 0.1in;
display: flex;
align-items: center;
margin-top: auto;
button {
flex-shrink: 0;
margin-left: 0.1in;
}
}
.sortBar {
height: 0.3in;
flex-shrink: 0;
background: var(--secondary-bg);
position: sticky;
top: 0px;
padding: 0.1in;
display: flex;
align-items: center;
z-index: 1;
button {
flex-shrink: 0;
margin-left: 0.1in;
}
}
.selectedB {
background: color-mix(in srgb, black, var(--button-bg) 60%);
}
.pinnedMessages { .pinnedMessages {
position: absolute; position: absolute;
background: var(--secondary-bg); background: var(--secondary-bg);
width: 3.5in; width: 3.5in;
padding: 8px; padding: 8px;
border-radius: 6px; border-radius: 6px;
box-shadow: 1px 1px 10px black; box-shadow: 1px 3px 10px var(--shadow);
max-height: 60vh; max-height: 60vh;
overflow-y: auto; overflow-y: auto;
min-height: 1in; min-height: 1in;
z-index: 2;
b { b {
width: 100%; width: 100%;
height: 1in; height: 1in;
@ -129,6 +165,7 @@ body {
.channelSTitle { .channelSTitle {
margin-top: 0.2in; margin-top: 0.2in;
margin-bottom: 0; margin-bottom: 0;
margin-left: 10px;
} }
p, p,
h1, h1,
@ -352,6 +389,14 @@ textarea {
display: block; display: block;
mask-size: cover !important; mask-size: cover !important;
} }
.svg-plainx {
mask: url(/icons/plainx.svg);
mask-size: contain !important;
}
.svg-search {
mask: url(/icons/search.svg);
mask-size: contain !important;
}
.svg-spoiler { .svg-spoiler {
mask: url(/icons/spoiler.svg); mask: url(/icons/spoiler.svg);
} }
@ -458,6 +503,16 @@ textarea {
aspect-ratio: 1/1; aspect-ratio: 1/1;
flex-shrink: 0; flex-shrink: 0;
} }
#searchX {
width: 0.16in;
height: 0.16in;
position: absolute;
right: 13px;
top: 6px;
}
#searchX.svg-plainx{
cursor:pointer;
}
#pinnedM { #pinnedM {
width: 0.25in; width: 0.25in;
height: 0.25in; height: 0.25in;
@ -891,7 +946,10 @@ span.instanceStatus {
background: var(--channels-bg); background: var(--channels-bg);
user-select: none; user-select: none;
} }
.searchMeta {
position: relative;
display: flex;
}
.header { .header {
flex: none; flex: none;
height: 48px; height: 48px;
@ -1225,6 +1283,10 @@ span.instanceStatus {
.searchBox:empty { .searchBox:empty {
width: 2in; width: 2in;
} }
.searchBox:empty::after {
content: var(--hint-text);
color: var(--primary-text-soft);
}
.searchBox { .searchBox {
white-space: nowrap; white-space: nowrap;
height: 0.075in; height: 0.075in;
@ -1739,6 +1801,7 @@ img.bigembedimg {
/* Sidebar */ /* Sidebar */
#sideContainDiv { #sideContainDiv {
padding: 16px 8px;
display: none; display: none;
flex: none; flex: none;
width: 240px; width: 240px;
@ -1747,17 +1810,22 @@ img.bigembedimg {
justify-content: space-between; justify-content: space-between;
} }
#sideDiv { #sideDiv {
padding: 16px 8px;
overflow-y: auto; overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
position: relative;
overflow: auto;
flex-grow: 1;
} }
#page:has(#memberlisttoggle:checked) #sideContainDiv { #page:has(#memberlisttoggle:checked) #sideContainDiv {
display: flex; display: flex;
} }
#sideContainDiv.searchDiv { #sideContainDiv.searchDiv {
padding: 0px;
display: flex; display: flex;
width: 30vw; width: 30vw;
.topMessage { .topMessage {
margin: 0px 10px;
margin-top: 2px; margin-top: 2px;
margin-bottom: 10px; margin-bottom: 10px;
cursor: pointer; cursor: pointer;
@ -2033,6 +2101,7 @@ img.bigembedimg {
height: 100%; height: 100%;
width: 100%; width: 100%;
background: color-mix(in srgb, var(--black) 75%, transparent); background: color-mix(in srgb, var(--black) 75%, transparent);
z-index: 2;
} }
.imgfit { .imgfit {
max-height: 85svh; max-height: 85svh;

View file

@ -380,6 +380,15 @@
"mustTypePhrase": "To delete your account you must type the phrase", "mustTypePhrase": "To delete your account you must type the phrase",
"manageInstance": "Manage Instance" "manageInstance": "Manage Instance"
}, },
"search": {
"back": "Back",
"next": "Next",
"page": "Page $1",
"new": "New",
"old": "Old",
"search": "Search",
"nofind": "There seems to be no messages that match your search, maybe trying broadening your search to try and find what you want"
},
"manageInstance": { "manageInstance": {
"stop": "Stop instance", "stop": "Stop instance",
"AreYouSureStop": "Are you sure you want to stop this instance?", "AreYouSureStop": "Are you sure you want to stop this instance?",