search updates

This commit is contained in:
MathMan05 committed 2026-02-06 16:02:21 -06:00
1 parent e93a1745b0
commit 6b3fa521fa
6 files changed
+399 -8

No files matched your search

+304 -5
View File
@@ -1901,8 +1901,85 @@ class Channel extends SnowFlake {
return;
}
async forumSearch(text: string, div: HTMLDivElement) {
while (!this.hasFetchedForForum) await this.fetchForum();
umap = new Map<string, [number, string]>();
search: [string, number, string][] = [];
hasAllThreads = false;
async fetchThreads(text: string, offset: number) {
const filters = this.forumFilters;
const uid = `${filters.recentFirst}-${filters.sortActive}-${filters.tagMatchAll}-${filters.tags.join("-")}-${text}`;
const search = new URLSearchParams([
["archived", "true"],
["limit", "25"],
["sort_by", filters.sortActive ? "last_message_time" : "creation_time"],
["sort_order", filters.recentFirst ? "desc" : "asc"],
["tag_setting", filters.tagMatchAll ? "match_all" : "match_some"],
["offset", offset + ""],
["tag", filters.tags.join(",")],
]);
const res = (await (
await fetch(this.info.api + "/channels/" + this.id + "/threads/search?" + search, {
headers: this.headers,
})
).json()) as {
threads: channeljson[];
members: memberjson[];
messages: messagejson[];
total_results: number;
has_more: boolean;
};
for (const threadjson of res.threads) {
if (this.localuser.channelids.has(threadjson.id)) continue;
const thread = new Channel(threadjson, this.guild);
this.localuser.channelids.set(threadjson.id, thread);
thread.resolveparent();
}
for (const message of res.messages) {
const thread = this.localuser.channelids.get(message.channel_id);
if (!thread) continue;
const m = this.localuser.messages.get(message.id);
if (!m) this.localuser.messages.set(message.id, new Message(message, thread));
}
for (const member of res.members) {
Member.new(member, this.guild);
}
if (res.has_more) {
const lastid = res.threads.at(-1)?.id as string;
if (text) {
const arr = this.search.find(([id]) => id === uid);
if (arr) {
arr[1] = offset + 25;
arr[2] = lastid;
} else {
this.search.push([uid, offset + 25, lastid]);
if (this.search.length > 200) {
this.search.shift();
}
}
} else {
this.umap.set(uid, [offset + 25, lastid]);
}
} else {
if (text) {
const arr = this.search.find(([id]) => id === uid);
if (arr) {
arr[1] = offset + 25;
arr[2] = "-1";
} else {
this.search.push([uid, offset + 25, "-1"]);
if (this.search.length > 200) {
this.search.shift();
}
}
} else {
this.umap.clear();
this.search = [];
this.hasAllThreads = true;
}
}
}
genCurSort(text: string, offset: number) {
const filters = this.forumFilters;
text = text.toLowerCase();
let match = this.children.filter((thread) => thread.name.toLowerCase().includes(text));
@@ -1911,7 +1988,7 @@ class Channel extends SnowFlake {
match = match.filter((thread) => {
const tags = new Set(thread.appliedTags);
if (filters.tagMatchAll) {
return tags.isSubsetOf(tagSet);
return tagSet.isSubsetOf(tags);
} else {
return tags.intersection(tagSet).size;
}
@@ -1926,8 +2003,222 @@ class Channel extends SnowFlake {
return +(Number(c1.id) > Number(c2.id)) ^ +filters.recentFirst ? 1 : -1;
}
});
div.innerHTML = "";
div.append(...(await Promise.all(match.map((_) => _.renderThread()))));
return [match.splice(offset, offset + 24), !!match.at(offset + 25)] as const;
}
async findMatches(text: string, offset: number): Promise<readonly [Channel[], boolean]> {
if (this.hasAllThreads) return this.genCurSort(text, offset);
const filters = this.forumFilters;
const uid = `${filters.recentFirst}-${filters.sortActive}-${filters.tagMatchAll}-${filters.tags.join("-")}-${text}`;
let offset2 = 0;
let lastid = "";
if (text) {
[, offset2, lastid] = this.search.find(([id]) => id === uid) || ["", 0, ""];
} else {
[offset2, lastid] = this.umap.get(uid) || [0, ""];
}
if (lastid) {
const [list, more] = this.genCurSort(text, offset);
for (const elm of list) {
if (elm.id === lastid) {
await this.fetchThreads(text, offset2);
return this.genCurSort(text, offset);
}
}
return [list, more];
} else {
await this.fetchThreads(text, offset2);
return this.genCurSort(text, offset);
}
}
async forumSearch(text: string, div: HTMLDivElement) {
while (!this.hasFetchedForForum) await this.fetchForum();
let offset = 0;
const flipPage = async (by: number) => {
offset += by;
const [match, more] = await this.findMatches(text, 0);
await renderDiv(match, more);
};
const renderDiv = async (threads: Channel[], more: boolean) => {
div.innerHTML = "";
const sortRow = document.createElement("div");
sortRow.classList.add("flexltr", "forumSortRow");
div.append(sortRow);
const cMenu = new Contextmenu<void, void>("sortAndStuff");
const opts = I18n.forum.sortOptions;
cMenu.addText(opts.sortby.title());
cMenu.addButton(
opts.sortby.recent(),
() => {
this.forumFilters.sortActive = true;
flipPage(0);
},
{
icon: {
css: this.forumFilters.sortActive ? "svg-select" : "svg-noSelect",
},
},
);
cMenu.addButton(
opts.sortby.posted(),
() => {
this.forumFilters.sortActive = false;
flipPage(0);
},
{
icon: {
css: this.forumFilters.sortActive ? "svg-noSelect" : "svg-select",
},
},
);
cMenu.addSeperator();
cMenu.addText(opts.sortOrder.title());
cMenu.addButton(
opts.sortOrder.recent(),
() => {
this.forumFilters.recentFirst = true;
flipPage(0);
},
{
icon: {
css: this.forumFilters.recentFirst ? "svg-select" : "svg-noSelect",
},
},
);
cMenu.addButton(
opts.sortOrder.old(),
() => {
this.forumFilters.recentFirst = false;
flipPage(0);
},
{
icon: {
css: this.forumFilters.recentFirst ? "svg-noSelect" : "svg-select",
},
},
);
cMenu.addSeperator();
cMenu.addText(opts.tagMatch.title());
cMenu.addButton(
opts.tagMatch.some(),
() => {
this.forumFilters.tagMatchAll = false;
flipPage(0);
},
{
icon: {css: this.forumFilters.tagMatchAll ? "svg-noSelect" : "svg-select"},
},
);
cMenu.addButton(
opts.tagMatch.all(),
() => {
this.forumFilters.tagMatchAll = true;
flipPage(0);
},
{
icon: {
css: this.forumFilters.tagMatchAll ? "svg-select" : "svg-noSelect",
},
},
);
const sortOptionButton = document.createElement("button");
sortOptionButton.textContent = opts.name();
sortOptionButton.onclick = (e) => {
if (e.button !== 0) return;
e.preventDefault();
e.stopImmediatePropagation();
const box = sortOptionButton.getBoundingClientRect();
cMenu.makemenu(box.left, box.bottom + 6);
};
const tags = document.createElement("div");
tags.classList.add("forumTagSelect");
const allMenu = new Contextmenu<void, void>("allTagMenu");
for (const tag of this.availableTags) {
const html = tag.makeHTML();
if (this.forumFilters.tags.includes(tag.id)) html.classList.add("selected");
html.onclick = () => {
if (this.forumFilters.tags.includes(tag.id)) {
this.forumFilters.tags = this.forumFilters.tags.filter((id) => id !== tag.id);
} else {
this.forumFilters.tags.push(tag.id);
this.forumFilters.tags.sort();
}
flipPage(0);
};
tags.append(html);
allMenu.addButton(
tag.name,
() => {
if (this.forumFilters.tags.includes(tag.id)) {
this.forumFilters.tags = this.forumFilters.tags.filter((id) => id !== tag.id);
} else {
this.forumFilters.tags.push(tag.id);
this.forumFilters.tags.sort();
}
flipPage(0);
},
{
icon: {css: this.forumFilters.tags.includes(tag.id) ? "svg-select" : "svg-noSelect"},
},
);
}
const allTags = document.createElement("button");
allTags.classList.add("allTagButton");
allTags.textContent = I18n.forum.allTags();
allTags.onclick = (e) => {
if (e.button !== 0) return;
e.preventDefault();
e.stopImmediatePropagation();
const box = allTags.getBoundingClientRect();
allMenu.makemenu(box.right - window.innerWidth, box.bottom + 6);
};
sortRow.append(sortOptionButton, tags, allTags);
div.append(...(await Promise.all(threads.map((_) => _.renderThread()))));
const buttonRow = document.createElement("div");
buttonRow.classList.add("flexltr", "forumButtonRow");
const next = document.createElement("button");
next.textContent = I18n.forum.next();
next.disabled = !more;
if (more) {
next.onclick = () => {
flipPage(25);
};
}
const back = document.createElement("button");
back.textContent = I18n.forum.back();
back.disabled = !offset;
if (offset) {
back.onclick = () => {
flipPage(-25);
};
}
buttonRow.append(back, next);
div.append(buttonRow);
};
await flipPage(0);
}
renderForum() {
(document.getElementById("typediv") as HTMLElement).style.visibility = "hidden";
@@ -1951,6 +2242,14 @@ class Channel extends SnowFlake {
const theadList = document.createElement("div");
theadList.classList.add("flexttb", "forumList");
container.append(theadList);
let curidsearch = 0;
text.onkeyup = async () => {
const thisid = ++curidsearch;
await new Promise((res) => setTimeout(res, 120));
if (thisid !== curidsearch) return;
this.forumSearch(text.value, theadList);
};
this.forumSearch("", theadList);
}
async getHTML(addstate = true, getMessages: boolean | void = undefined, aroundMessage?: string) {
+31 -2
View File
@@ -166,6 +166,25 @@ class Seperator<x, y> implements menuPart<x, y> {
}
}
}
class ContextMenuText<x, y> implements menuPart<x, y> {
private visible?: (obj1: x, obj2: y) => boolean;
group?: string;
text: string;
constructor(text: string, visible?: (obj1: x, obj2: y) => boolean, group?: string) {
this.visible = visible;
this.group = group;
this.text = text;
}
makeContextHTML(obj1: x, obj2: y, menu: HTMLDivElement): void {
if (!this.visible || this.visible(obj1, obj2)) {
const span = document.createElement("span");
span.textContent = this.text;
menu.append(span);
}
}
}
declare global {
interface HTMLElementEventMap {
layered: LayeredEvent;
@@ -242,11 +261,16 @@ class Contextmenu<x, y> {
group?: string;
} = {},
) {
this.buttons.push(new ContextButton(text, onClick, addProps));
const button = new ContextButton(text, onClick, addProps);
this.buttons.push(button);
return button;
}
addSeperator(visible?: (obj1: x, obj2: y) => boolean, group?: string) {
this.buttons.push(new Seperator(visible, group));
}
addText(text: string, visible?: (obj1: x, obj2: y) => boolean, group?: string) {
this.buttons.push(new ContextMenuText(text, visible, group));
}
addGroup(
group: string,
addprops?: {
@@ -287,7 +311,12 @@ class Contextmenu<x, y> {
} else {
div.style.bottom = y * -1 + "px";
}
div.style.left = x + "px";
if (x > 0) {
div.style.left = x + "px";
} else {
div.style.right = x * -1 + "px";
}
document.body.appendChild(div);
Contextmenu.keepOnScreen(div);
+1
View File
@@ -0,0 +1 @@
<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fff" stroke-width="15.2" stroke-linejoin="round" paint-order="stroke fill markers"><circle cx="60" cy="60" r="42.4"/><circle cx="60" cy="60" r="42.4"/></g></svg>

After

Width:  |  Height:  |  Size: 250 B

+1
View File
@@ -0,0 +1 @@
<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg"><g stroke="#fff" stroke-linejoin="round" paint-order="stroke fill markers"><circle cx="60" cy="60" fill="none" stroke-width="15.2" r="42.4"/><circle cx="60" cy="60" fill="#fff" stroke-width="7.6" r="21.2"/></g></svg>

After

Width:  |  Height:  |  Size: 281 B

+40
View File
@@ -895,6 +895,16 @@ textarea {
mask-size: contain !important;
background: var(--primary-text);
}
.svg-select {
mask: url(./icons/select.svg);
mask-size: contain !important;
background: var(--primary-text);
}
.svg-noSelect {
mask: url(./icons/noSelect.svg);
mask-size: contain !important;
background: var(--primary-text);
}
.svg-stopstream {
mask: url(./icons/stopstream.svg);
mask-size: contain !important;
@@ -1107,10 +1117,32 @@ textarea {
flex: 1;
overflow-y: scroll;
}
.allTagButton {
margin-left: auto;
}
.forumSortRow {
flex-shrink: 0;
padding: 4px 12px;
align-items: center;
}
.forumTagSelect {
display: flex;
flex-direction: row;
flex-wrap: wrap;
overflow-y: clip;
height: 26px;
> * {
margin-left: 6px;
}
.selected {
background: #ffffff5e;
}
}
.forumTag {
padding: 3px;
background: #ffffff21;
border-radius: 12px;
cursor: pointer;
}
.forumPostBody {
background: #00000075;
@@ -1122,6 +1154,14 @@ textarea {
}
cursor: pointer;
}
.forumButtonRow {
flex-shrink: 0;
mix-height: fit-content;
padding: 12px;
button {
margin-right: 10px;
}
}
.forumMessageRow {
.svg-frmessage {
width: 16px;
+22 -1
View File
@@ -144,7 +144,28 @@
"name":"Name:"
},
"forum":{
"creorsear":"Create or search for post"
"creorsear":"Create or search for post",
"next":"Next",
"back":"Back",
"sortOptions":{
"sortby":{
"title":"Sort By",
"recent":"Recently Active",
"posted":"Posted Date"
},
"sortOrder":{
"title":"Sort Order",
"recent":"Recent First",
"old":"Old First"
},
"tagMatch":{
"title":"Tag Matching",
"some":"Match Some",
"all":"Match All"
},
"name":"Sort Options"
},
"allTags":"All Tags"
},
"channelLink": "# $1",
"clientDesc": "Client version: $1\n\n[Join the official Fermi guild]($2/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat)\n\n[Help translate Fermi](https://translatewiki.net/wiki/Translating:JankClient#sortable:3=desc) \n\n[Help create Fermi](https://github.com/MathMan05/Fermi)\n\n[Help maintain the server Fermi relies on](https://github.com/spacebarchat/server)\n\n[Read the blog](https://blog.fermi.chat/)\n\nCalculated rights: $3",