mirror of
https://github.com/slatinsky/DiscordChatExporter-frontend.git
synced 2025-05-11 18:36:49 +02:00
dev - wip channel pagination
This commit is contained in:
parent
f0fcd614a2
commit
0a99ee694d
16 changed files with 1069 additions and 226 deletions
|
@ -100,58 +100,141 @@ async def get_roles(guild_id: str):
|
|||
return list(cursor)
|
||||
|
||||
|
||||
@app.get("/message-ids")
|
||||
async def get_message_ids(channel_id: str, guild_id: str):
|
||||
|
||||
def sort_ids_asc(ids: list):
|
||||
"""
|
||||
Returns a list of message ids for a channel.
|
||||
Sort ids in ascending order.
|
||||
Because sometimes we are querying messages in descending order, we need to reverse the list.
|
||||
"""
|
||||
return sorted(ids, key=lambda x: int(x))
|
||||
|
||||
@app.get("/message-ids-paginated")
|
||||
async def get_message_ids(channel_id: str, guild_id: str, message_id: str = None, direction="around", limit=100):
|
||||
"""
|
||||
Returns a subset of message ids for a channel.
|
||||
User supplies a message id, for which we return a subset of message ids around it
|
||||
- special message id "first" returns the first messages
|
||||
- special message id "last" returns the last messages
|
||||
|
||||
Direction can be "around", "before" or "after"
|
||||
- "around" returns messages before and after the message id
|
||||
- requires message id
|
||||
- "before" returns messages before the message id
|
||||
- requires message id
|
||||
- "after" returns messages after the message id
|
||||
- requires message id
|
||||
|
||||
Message id can be 24 long id or special id "first" or "last"
|
||||
|
||||
|
||||
The endpoint returns special message ids "first" and "last" if there are more messages to load in that direction.
|
||||
|
||||
no cache
|
||||
"""
|
||||
if not message_id:
|
||||
raise Exception("Message id is required")
|
||||
|
||||
if message_id != 'first' and message_id != 'last':
|
||||
message_id = pad_id(message_id)
|
||||
|
||||
channel_id = pad_id(channel_id)
|
||||
guild_id = pad_id(guild_id)
|
||||
|
||||
limit = int(limit)
|
||||
collection_messages = get_guild_collection(guild_id, "messages")
|
||||
if is_compiled():
|
||||
cache_dir = "../../storage/cache/message-ids"
|
||||
else:
|
||||
cache_dir = "../../release/dcef/storage/cache/message-ids"
|
||||
|
||||
cache_path = f"{cache_dir}/{channel_id}.json"
|
||||
denylisted_user_ids_path = f"{cache_dir}/denylisted_user_ids.json"
|
||||
ids_before = []
|
||||
ids_after = []
|
||||
|
||||
# clear entire cache if denylisted user ids changed
|
||||
denylisted_user_ids = get_denylisted_user_ids()
|
||||
if os.path.exists(cache_path):
|
||||
with open(denylisted_user_ids_path, "r", encoding="utf-8") as f:
|
||||
file_content = f.read()
|
||||
|
||||
if file_content != str(denylisted_user_ids):
|
||||
shutil.rmtree(cache_dir)
|
||||
os.makedirs(cache_dir)
|
||||
if direction == "around" and (message_id != "first" and message_id != "last"):
|
||||
limit = limit // 2
|
||||
|
||||
# read cached ids if available
|
||||
if os.path.exists(cache_path):
|
||||
print("get_message_ids() cache hit - channel id", channel_id)
|
||||
with open(cache_path, "r", encoding="utf-8") as f:
|
||||
file_content = f.read()
|
||||
return json.loads(file_content)
|
||||
|
||||
print("get_message_ids() cache miss - channel id", channel_id)
|
||||
|
||||
query = {
|
||||
"channelId": channel_id,
|
||||
"author._id": {
|
||||
"$nin": denylisted_user_ids
|
||||
if message_id == "first":
|
||||
query = {
|
||||
"channelId": channel_id,
|
||||
"author._id": {
|
||||
"$nin": denylisted_user_ids
|
||||
}
|
||||
}
|
||||
}
|
||||
ids = collection_messages.find(query, {"_id": 1}).sort([("_id", pymongo.ASCENDING)])
|
||||
new_ids = [str(id["_id"]) for id in ids]
|
||||
ids_before = ['first']
|
||||
ids_after = collection_messages.find(query, {"_id": 1}).sort([("_id", pymongo.ASCENDING)]).limit(limit)
|
||||
ids_after = [str(id["_id"]) for id in ids_after]
|
||||
if len(ids_after) != limit:
|
||||
ids_after.append('last')
|
||||
ids = ids_before + ids_after
|
||||
|
||||
elif message_id == "last":
|
||||
query = {
|
||||
"channelId": channel_id,
|
||||
"author._id": {
|
||||
"$nin": denylisted_user_ids
|
||||
}
|
||||
}
|
||||
ids_after = ['last']
|
||||
ids_before = collection_messages.find(query, {"_id": 1}).sort([("_id", pymongo.DESCENDING)]).limit(limit)
|
||||
ids_before = sort_ids_asc([str(id["_id"]) for id in ids_before])
|
||||
if len(ids_before) != limit:
|
||||
ids_before.insert(0, 'first')
|
||||
ids = ids_before + ids_after
|
||||
else:
|
||||
if direction == "around" or direction == "before":
|
||||
ids_before = collection_messages.find(
|
||||
{
|
||||
"channelId": channel_id,
|
||||
"_id": {
|
||||
"$lt": message_id
|
||||
},
|
||||
"author._id": {
|
||||
"$nin": denylisted_user_ids
|
||||
}
|
||||
},
|
||||
{"_id": 1}
|
||||
).sort([("_id", pymongo.DESCENDING)]).limit(limit)
|
||||
ids_before = sort_ids_asc([str(id["_id"]) for id in ids_before])
|
||||
|
||||
if direction == "around" or direction == "after":
|
||||
ids_after = collection_messages.find(
|
||||
{
|
||||
"channelId": channel_id,
|
||||
"_id": {
|
||||
"$gt": message_id
|
||||
},
|
||||
"author._id": {
|
||||
"$nin": denylisted_user_ids
|
||||
}
|
||||
},
|
||||
{"_id": 1}
|
||||
).sort([("_id", pymongo.ASCENDING)]).limit(limit)
|
||||
ids_after = [str(id["_id"]) for id in ids_after]
|
||||
|
||||
if direction == "around":
|
||||
if len(ids_before) != limit:
|
||||
ids_before.insert(0, 'first')
|
||||
if len(ids_after) != limit:
|
||||
ids_after.append('last')
|
||||
if len(ids_before) + len(ids_after) > 2: # found anything?
|
||||
ids = ids_before + [message_id] + ids_after
|
||||
else:
|
||||
ids = ids_before + ids_after
|
||||
elif direction == "before":
|
||||
if len(ids_before) != limit:
|
||||
ids_before.insert(0, 'first')
|
||||
ids = ids_before
|
||||
|
||||
elif direction == "after":
|
||||
if len(ids_after) != limit:
|
||||
ids_after.append('last')
|
||||
ids = ids_after
|
||||
else:
|
||||
raise Exception("Invalid direction")
|
||||
|
||||
return ids
|
||||
|
||||
|
||||
# save to cache
|
||||
file_content = re.sub(r"'", '"', str(new_ids))
|
||||
with open(cache_path, "w", encoding="utf-8") as f:
|
||||
f.write(file_content)
|
||||
|
||||
file_content = re.sub(r"'", '"', str(denylisted_user_ids))
|
||||
with open(denylisted_user_ids_path, "w", encoding="utf-8") as f:
|
||||
f.write(file_content)
|
||||
|
||||
return new_ids
|
||||
|
||||
|
||||
class MessageRequest(BaseModel):
|
||||
|
|
8
newfrontend/package-lock.json
generated
8
newfrontend/package-lock.json
generated
|
@ -17,7 +17,7 @@
|
|||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"svelte": "^5.0.0-next.144",
|
||||
"svelte": "^5.0.0-next.148",
|
||||
"svelte-check": "^3.6.7",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
|
@ -1631,9 +1631,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.0.0-next.144",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.0.0-next.144.tgz",
|
||||
"integrity": "sha512-akjtRBHzaLa1XdMv9tBGkXE5N2JaRc3gL+ZIctjc9Gew9DF7NxGTlxXq+HR9yUV7Lsg4o9ltMfkxz8H3K7piNQ==",
|
||||
"version": "5.0.0-next.148",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.0.0-next.148.tgz",
|
||||
"integrity": "sha512-4N1V1BjClRsXBFOUKQZxmwg0Xb62ZUEM0B/5Q9bfeuR37uBpqkv4HYzso/QNqwVYPmnOmeMzQkrHJcuwh+ZCsQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.1",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"svelte": "^5.0.0-next.144",
|
||||
"svelte": "^5.0.0-next.148",
|
||||
"svelte-check": "^3.6.7",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
|
|
|
@ -18,8 +18,13 @@ async function _fetchMessagesFromApi(guildId: string | null, messageIds: string[
|
|||
if (guildId === null) {
|
||||
guildId = "000000000000000000000000"
|
||||
}
|
||||
|
||||
messageIds = messageIds.filter((messageId) => {
|
||||
return messageId !== "first" && messageId !== "last";
|
||||
})
|
||||
|
||||
let notInCache = messageIds.filter((messageId) => {
|
||||
return !messageCacheWithoutReference[messageId]
|
||||
return !messageCacheWithoutReference[messageId];
|
||||
})
|
||||
|
||||
if (notInCache.length > 0) {
|
||||
|
@ -44,6 +49,8 @@ async function _fetchMessagesFromApi(guildId: string | null, messageIds: string[
|
|||
|
||||
|
||||
export async function messsageIdsToMessages(guildId: string, messageIds: string[]) {
|
||||
const containsFirst = messageIds.includes("first");
|
||||
const containsLast = messageIds.includes("last");
|
||||
console.log("api - fetching " + messageIds.length + " messages");
|
||||
if (messageIds.length === 0) {
|
||||
return [];
|
||||
|
@ -80,5 +87,12 @@ export async function messsageIdsToMessages(guildId: string, messageIds: string[
|
|||
outMessages.push(messageCopy);
|
||||
}
|
||||
|
||||
if (containsFirst) {
|
||||
outMessages.unshift({_id: "first"});
|
||||
}
|
||||
if (containsLast) {
|
||||
outMessages.push({_id: "last"});
|
||||
}
|
||||
|
||||
return outMessages;
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import type { Category, Channel } from "../interfaces";
|
||||
|
||||
export async function fetchMessageIds(guildId: string | null, channelId: string) {
|
||||
export async function fetchMessageIds(guildId: string | null, channelId: string, direction: "before" | "after" | "around" | "first" | "last", messageId: string | null = null, limit: number = 50) {
|
||||
if (guildId === null) {
|
||||
guildId = "000000000000000000000000"
|
||||
}
|
||||
try {
|
||||
let response = await fetch(`/api/message-ids?guild_id=${encodeURIComponent(guildId)}&channel_id=${encodeURIComponent(channelId)}`)
|
||||
let response = await fetch(`/api/message-ids-paginated?guild_id=${encodeURIComponent(guildId)}&channel_id=${encodeURIComponent(channelId)}&direction=${direction}&message_id=${encodeURIComponent(messageId)}&limit=${limit}`)
|
||||
let messageIds = await response.json()
|
||||
return messageIds
|
||||
}
|
||||
|
|
|
@ -150,6 +150,20 @@ export function getGuildState() {
|
|||
console.log("router - replaced", state);
|
||||
}
|
||||
|
||||
async function fetchChannelPinnedMessagesIds() {
|
||||
if (!guildId || !channelId) {
|
||||
return []
|
||||
}
|
||||
channelPinnedMessagesIds = await fetchPinnedMessageIds(guildId, channelId)
|
||||
}
|
||||
|
||||
async function fetchThreadPinnedMessagesIds() {
|
||||
if (!guildId || !threadId) {
|
||||
return []
|
||||
}
|
||||
threadPinnedMessagesIds = await fetchPinnedMessageIds(guildId, threadId)
|
||||
}
|
||||
|
||||
async function changeGuildId(newGuildId: string | null) {
|
||||
if (newGuildId === "000000000000000000000000") {
|
||||
newGuildId = null
|
||||
|
@ -159,40 +173,99 @@ export function getGuildState() {
|
|||
}
|
||||
guildId = newGuildId;
|
||||
searchState.clearSearch()
|
||||
await changeChannelId(null)
|
||||
await changeChannelId(null, null)
|
||||
categories = await fetchCategoriesChannelsThreads(guildId)
|
||||
console.log("router - changed guildId", guildId);
|
||||
}
|
||||
|
||||
async function changeChannelId(newChannelId: string | null) {
|
||||
if (channelId === newChannelId) {
|
||||
async function changeChannelId(newChannelId: string | null, newChannelMessageId: string | null) {
|
||||
if (channelId === newChannelId && channelMessageId === newChannelMessageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (channelId !== newChannelId) {
|
||||
await changeThreadId(null, null)
|
||||
layoutState.hideChannelPinned()
|
||||
channelPinnedMessagesIds = []
|
||||
}
|
||||
channelId = newChannelId;
|
||||
await changeThreadId(null)
|
||||
|
||||
if (channelMessageId !== newChannelMessageId) {
|
||||
channelMessageId = newChannelMessageId
|
||||
}
|
||||
|
||||
if (newChannelId) {
|
||||
channelMessagesIds = await fetchMessageIds(guildId, newChannelId)
|
||||
channelMessageId = channelMessagesIds.length > 0 ? channelMessagesIds[-1] : null // last message
|
||||
channelPinnedMessagesIds = await fetchPinnedMessageIds(guildId, newChannelId)
|
||||
if (newChannelMessageId === "last") {
|
||||
channelMessagesIds = await fetchMessageIds(guildId, newChannelId, "last", newChannelMessageId, 50)
|
||||
}
|
||||
else if (newChannelMessageId === "first") {
|
||||
channelMessagesIds = await fetchMessageIds(guildId, newChannelId, "first", newChannelMessageId, 50)
|
||||
}
|
||||
else {
|
||||
channelMessagesIds = await fetchMessageIds(guildId, newChannelId, "around", newChannelMessageId, 100)
|
||||
}
|
||||
if (channelMessageId === null) {
|
||||
channelMessageId = channelMessagesIds[channelMessagesIds.length - 1] // last message
|
||||
}
|
||||
}
|
||||
else {
|
||||
channelMessagesIds = []
|
||||
channelMessageId = null
|
||||
channelPinnedMessagesIds = []
|
||||
}
|
||||
console.log("router - changed channelId", channelId);
|
||||
console.log("router - changed channelId", channelId, "messageId", channelMessageId);
|
||||
}
|
||||
|
||||
async function changeThreadId(newThreadId: string | null) {
|
||||
if (threadId === newThreadId) {
|
||||
async function loadChannelMessageIdsBefore() {
|
||||
if (!channelId) {
|
||||
return
|
||||
}
|
||||
if (channelMessagesIds.length === 0) {
|
||||
return
|
||||
}
|
||||
const soonestMessageId = channelMessagesIds[0]
|
||||
if (soonestMessageId === "first") {
|
||||
return
|
||||
}
|
||||
const oldCount = channelMessagesIds.length
|
||||
const newMessageIds = await fetchMessageIds(guildId, channelId, "before", soonestMessageId, 5)
|
||||
channelMessagesIds = [...newMessageIds, ...channelMessagesIds ]
|
||||
console.warn("router - loadChannelMessageIdsBefore - loaded", channelMessagesIds.length - oldCount, "new messages before", soonestMessageId);
|
||||
}
|
||||
async function loadChannelMessageIdsAfter() {
|
||||
if (!channelId) {
|
||||
return
|
||||
}
|
||||
if (channelMessagesIds.length === 0) {
|
||||
return
|
||||
}
|
||||
const latestMessageId = channelMessagesIds[channelMessagesIds.length - 1]
|
||||
let oldCount = channelMessagesIds.length
|
||||
const newMessageIds = await fetchMessageIds(guildId, channelId, "after", latestMessageId, 5)
|
||||
channelMessagesIds = [...channelMessagesIds, ...newMessageIds]
|
||||
console.log("router - loadChannelMessageIdsAfter - loaded", channelMessagesIds.length - oldCount, "new messages after", latestMessageId);
|
||||
}
|
||||
|
||||
async function changeThreadId(newThreadId: string | null, newThreadMessageId: string | null) {
|
||||
if (threadId === newThreadId && threadMessageId === newThreadMessageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadId !== newThreadId) {
|
||||
layoutState.hideThreadPinned()
|
||||
threadPinnedMessagesIds = []
|
||||
}
|
||||
threadId = newThreadId;
|
||||
|
||||
if (threadMessageId !== newThreadMessageId) {
|
||||
threadMessageId = newThreadMessageId
|
||||
}
|
||||
|
||||
if (newThreadId && guildId) {
|
||||
threadMessagesIds = await fetchMessageIds(guildId, newThreadId)
|
||||
threadMessageId = threadMessagesIds.length > 0 ? threadMessagesIds[-1] : null // last message
|
||||
threadPinnedMessagesIds = await fetchPinnedMessageIds(guildId, newThreadId)
|
||||
threadMessagesIds = await fetchMessageIds(guildId, newThreadId, "last", newThreadMessageId)
|
||||
if (threadMessageId === null) {
|
||||
threadMessageId = threadMessagesIds[threadMessagesIds.length - 1] // last message
|
||||
}
|
||||
|
||||
layoutState.showThread()
|
||||
}
|
||||
|
@ -206,43 +279,6 @@ export function getGuildState() {
|
|||
console.log("router - changed threadId", threadId);
|
||||
}
|
||||
|
||||
async function changeChannelMessageId(newMessageId: string | null) {
|
||||
let proposedChange
|
||||
if (newMessageId === null) {
|
||||
proposedChange = null
|
||||
}
|
||||
else if (channelMessagesIds.includes(newMessageId)) {
|
||||
proposedChange = newMessageId
|
||||
}
|
||||
else {
|
||||
console.error("router - changeChannelMessageId - message not found", newMessageId)
|
||||
proposedChange = null
|
||||
}
|
||||
|
||||
if (proposedChange !== channelMessageId) {
|
||||
channelMessageId = proposedChange
|
||||
console.log("router - changed channelMessageId", channelMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
async function changeThreadMessageId(newMessageId: string | null) {
|
||||
let proposedChange
|
||||
if (newMessageId === null) {
|
||||
proposedChange = null
|
||||
}
|
||||
else if (threadMessagesIds.includes(newMessageId)) {
|
||||
proposedChange = newMessageId
|
||||
}
|
||||
else {
|
||||
console.error("router - changeThreadMessageId - message not found", newMessageId)
|
||||
proposedChange = null
|
||||
}
|
||||
|
||||
if (proposedChange !== threadMessageId) {
|
||||
threadMessageId = proposedChange
|
||||
console.log("router - changed threadMessageId", threadMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to guildId and channelOrThreadId (will be automatically detected if it's a channel or thread id)
|
||||
|
@ -251,10 +287,10 @@ export function getGuildState() {
|
|||
async function comboSetGuildChannel(guildId: string, channelOrThreadId: string) {
|
||||
await changeGuildId(guildId)
|
||||
if (isChannel(channelOrThreadId)) {
|
||||
await changeChannelId(channelOrThreadId)
|
||||
await changeChannelId(channelOrThreadId, null)
|
||||
}
|
||||
else if (isThread(channelOrThreadId)) {
|
||||
await changeThreadId(channelOrThreadId)
|
||||
await changeThreadId(channelOrThreadId, null)
|
||||
}
|
||||
else {
|
||||
console.warn("router - comboSetGuildChannel - channel or thread not exported", channelOrThreadId)
|
||||
|
@ -266,12 +302,12 @@ export function getGuildState() {
|
|||
async function comboSetGuildChannelMessage(guildId: string, channelOrThreadId: string, messageId: string) {
|
||||
await changeGuildId(guildId)
|
||||
if (isChannel(channelOrThreadId)) {
|
||||
await changeChannelId(channelOrThreadId)
|
||||
await changeChannelMessageId(messageId)
|
||||
await changeChannelId(channelOrThreadId, messageId)
|
||||
// await changeChannelMessageId(messageId)
|
||||
}
|
||||
else if (isThread(channelOrThreadId)) {
|
||||
await changeThreadId(channelOrThreadId)
|
||||
await changeThreadMessageId(messageId)
|
||||
await changeThreadId(channelOrThreadId, messageId)
|
||||
// await changeThreadMessageId(messageId)
|
||||
}
|
||||
else {
|
||||
console.warn("router - comboSetGuildChannelMessage - channel or thread not exported", channelOrThreadId)
|
||||
|
@ -321,16 +357,18 @@ export function getGuildState() {
|
|||
get threadPinnedMessagesIds() {
|
||||
return threadPinnedMessagesIds;
|
||||
},
|
||||
loadChannelMessageIdsBefore,
|
||||
loadChannelMessageIdsAfter,
|
||||
changeGuildId,
|
||||
changeChannelId,
|
||||
changeThreadId,
|
||||
changeChannelMessageId,
|
||||
changeThreadMessageId,
|
||||
comboSetGuildChannel,
|
||||
comboSetGuildChannelMessage,
|
||||
getUrlState,
|
||||
pushState,
|
||||
replaceState,
|
||||
fetchChannelPinnedMessagesIds,
|
||||
fetchThreadPinnedMessagesIds,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -353,10 +391,10 @@ export function channelOrThreadIdToName(channelId: string) {
|
|||
|
||||
async function restoreGuildState(state) {
|
||||
await guildState.changeGuildId(state.guild);
|
||||
await guildState.changeChannelId(state.channel);
|
||||
await guildState.changeThreadId(state.thread);
|
||||
await guildState.changeChannelMessageId(state.channelmessage);
|
||||
await guildState.changeThreadMessageId(state.threadmessage);
|
||||
await guildState.changeChannelId(state.channel, state.channelmessage);
|
||||
await guildState.changeThreadId(state.thread, state.threadmessage);
|
||||
// await guildState.changeChannelMessageId(state.channelmessage);
|
||||
// await guildState.changeThreadMessageId(state.threadmessage);
|
||||
|
||||
await searchState.setSearchPrompt(state.search)
|
||||
await searchState.search(guildState.guildId)
|
||||
|
@ -388,13 +426,15 @@ window.addEventListener("popstate", async (e) => {
|
|||
})
|
||||
|
||||
|
||||
export function changeMessageId(channelOrThreadId: string, messageId: string) {
|
||||
export async function changeMessageId(channelOrThreadId: string, messageId: string) {
|
||||
if (isChannel(channelOrThreadId)) {
|
||||
guildState.changeChannelMessageId(messageId)
|
||||
await guildState.changeChannelId(channelOrThreadId, messageId);
|
||||
// guildState.changeChannelMessageId(messageId)
|
||||
guildState.pushState()
|
||||
}
|
||||
else if (isThread(channelOrThreadId)) {
|
||||
guildState.changeThreadMessageId(messageId)
|
||||
await guildState.changeThreadId(channelOrThreadId, messageId);
|
||||
// guildState.changeThreadMessageId(messageId)
|
||||
guildState.pushState()
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { getSearchState } from "../../lib/search/searchState.svelte";
|
||||
import { getGuildState } from "./guildState.svelte";
|
||||
|
||||
const searchState = getSearchState();
|
||||
const guildState = getGuildState();
|
||||
|
||||
let mobilesidepanelshown = $state(false);
|
||||
let searchManuallyHidden = $state(false);
|
||||
|
@ -78,11 +80,35 @@ export function getLayoutState() {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleChannelPinned() {
|
||||
channelpinnedshown = !channelpinnedshown;
|
||||
async function toggleChannelPinned() {
|
||||
if (channelpinnedshown) {
|
||||
hideChannelPinned()
|
||||
}
|
||||
else {
|
||||
await showChannelPinned()
|
||||
}
|
||||
}
|
||||
function toggleThreadPinned() {
|
||||
threadpinnedshown = !threadpinnedshown;
|
||||
function hideChannelPinned() {
|
||||
channelpinnedshown = false;
|
||||
}
|
||||
async function showChannelPinned() {
|
||||
channelpinnedshown = true;
|
||||
await guildState.fetchChannelPinnedMessagesIds()
|
||||
}
|
||||
async function toggleThreadPinned() {
|
||||
if (threadpinnedshown) {
|
||||
hideThreadPinned()
|
||||
}
|
||||
else {
|
||||
await showThreadPinned()
|
||||
}
|
||||
}
|
||||
function hideThreadPinned() {
|
||||
threadpinnedshown = false;
|
||||
}
|
||||
async function showThreadPinned() {
|
||||
threadpinnedshown = true;
|
||||
await guildState.fetchThreadPinnedMessagesIds()
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -133,7 +159,11 @@ export function getLayoutState() {
|
|||
hideSettingsSideMenu,
|
||||
toggleSettingsSideMenu,
|
||||
toggleChannelPinned,
|
||||
hideChannelPinned,
|
||||
showChannelPinned,
|
||||
toggleThreadPinned,
|
||||
hideThreadPinned,
|
||||
showThreadPinned,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,19 +3,35 @@
|
|||
import { getGuildState } from "../js/stores/guildState.svelte";
|
||||
import { getLayoutState } from "../js/stores/layoutState.svelte";
|
||||
import DateSeparator from "./DateSeparator.svelte";
|
||||
import InfiniteScroll from "./InfiniteScroll.svelte";
|
||||
import InfiniteScroll2 from "./InfiniteScroll2.svelte";
|
||||
import ChannelStart from "./message/ChannelStart.svelte";
|
||||
import Message from "./message/Message.svelte";
|
||||
|
||||
const guildState = getGuildState()
|
||||
const layoutState = getLayoutState()
|
||||
|
||||
let apiGuildId = $derived(guildState.guildId ? guildState.guildId : "000000000000000000000000")
|
||||
let apiChannelId = $derived(guildState.channelId)
|
||||
|
||||
|
||||
export async function fetchMessageIds(direction: "before" | "after" | "around" | "first" | "last", messageId: string | null = null, limit: number) {
|
||||
try {
|
||||
let response = await fetch(`/api/message-ids-paginated?guild_id=${encodeURIComponent(apiGuildId)}&channel_id=${encodeURIComponent(apiChannelId)}&direction=${direction}&message_id=${encodeURIComponent(messageId)}&limit=${limit}`)
|
||||
let messageIds = await response.json()
|
||||
return messageIds
|
||||
}
|
||||
catch (e) {
|
||||
console.error("api - Failed to fetch message ids", e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
{#snippet renderMessageSnippet(index, message, previousMessage)}
|
||||
{#if index === 0}
|
||||
<!-- {#if index === 0}
|
||||
<ChannelStart channelName={message.channelName} isThread={false} messageAuthor={message.author} />
|
||||
{/if}
|
||||
{/if} -->
|
||||
|
||||
{#if isDateDifferent(previousMessage, message)}
|
||||
<DateSeparator messageId={message._id} />
|
||||
|
@ -26,13 +42,58 @@
|
|||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet channelStartSnippet(index, message, previousMessage)}
|
||||
<ChannelStart channelName={message.channelName} isThread={false} messageAuthor={message.author} />
|
||||
{/snippet}
|
||||
|
||||
{#snippet channelEndSnippet(index, message, previousMessage)}
|
||||
<div data-messageid="last">
|
||||
this is the end of the channel
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet renderMessageSnippet2(message, previousMessage)}
|
||||
<div data-messageid={message._id}>
|
||||
{#if message._id === "first"}
|
||||
<!-- <ChannelStart channelName={message.channelName} isThread={false} messageAuthor={message.author} /> -->
|
||||
<div>channel start</div>
|
||||
{:else if message._id === "last"}
|
||||
<div>channel end</div>
|
||||
{:else}
|
||||
{#if isDateDifferent(previousMessage, message)}
|
||||
<DateSeparator messageId={message._id} />
|
||||
{/if}
|
||||
<Message message={message} previousMessage={previousMessage} />
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<div class="channel-wrapper" class:threadshown={layoutState.threadshown}>
|
||||
<div class="channel" >
|
||||
<!-- TODO: support change of selectedMessageId without rerender -->
|
||||
{#key guildState.channelMessageId}
|
||||
<InfiniteScroll debugname="channel" ids={guildState.channelMessagesIds} guildId={guildState.guildId} selectedMessageId={guildState.channelMessageId} renderMessageSnippet={renderMessageSnippet} bottomAligned={true} />
|
||||
{/key}
|
||||
{#if guildState.channelId !== null}
|
||||
<!-- TODO: support change of selectedMessageId without rerender -->
|
||||
{#key guildState.channelMessageId}
|
||||
<!-- <InfiniteScroll
|
||||
debugname="channel"
|
||||
ids={guildState.channelMessagesIds}
|
||||
loadBefore={guildState.loadChannelMessageIdsBefore}
|
||||
loadAfter={guildState.loadChannelMessageIdsAfter}
|
||||
guildId={guildState.guildId}
|
||||
selectedMessageId={guildState.channelMessageId}
|
||||
renderMessageSnippet={renderMessageSnippet}
|
||||
channelStartSnippet={channelStartSnippet}
|
||||
channelEndSnippet={channelEndSnippet}
|
||||
bottomAligned={true}
|
||||
/> -->
|
||||
{#key apiChannelId}
|
||||
<InfiniteScroll2
|
||||
fetchMessageIds={fetchMessageIds}
|
||||
guildId={apiGuildId}
|
||||
snippetMessage={renderMessageSnippet2}
|
||||
/>
|
||||
{/key}
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
287
newfrontend/src/lib/InfiniteScroll-old.svelte
Normal file
287
newfrontend/src/lib/InfiniteScroll-old.svelte
Normal file
|
@ -0,0 +1,287 @@
|
|||
<script lang="ts">
|
||||
import { untrack } from "svelte";
|
||||
import type { Message } from "../js/interfaces";
|
||||
// --------------------------
|
||||
// THIS CODE IS NOT FINISHED
|
||||
// TODO: needs backend pagination support to optimize loading
|
||||
// --------------------------
|
||||
import { messsageIdsToMessages } from "../js/messages";
|
||||
|
||||
interface MyProps {
|
||||
ids: string[]
|
||||
guildId: string
|
||||
selectedMessageId: string | null
|
||||
bottomAligned: boolean
|
||||
debugname: string
|
||||
loadBefore: () => Promise<void>
|
||||
loadAfter: () => Promise<void>
|
||||
renderMessageSnippet: (index: number, message: Message, previousMessage: Message) => void
|
||||
channelStartSnippet: (index: number, message: Message, previousMessage: Message) => void
|
||||
channelEndSnippet: (index: number, message: Message, previousMessage: Message) => void
|
||||
}
|
||||
let { ids, guildId, selectedMessageId = null, renderMessageSnippet, channelStartSnippet, channelEndSnippet, bottomAligned, debugname="", loadBefore, loadAfter}: MyProps = $props();
|
||||
|
||||
let maxMessages = 12000
|
||||
let loadIncrement = 30
|
||||
let startLoadingPixels = 500
|
||||
let scrollContainer: HTMLDivElement
|
||||
|
||||
let messages: Message[] = $state([])
|
||||
let lowestLoadedIndex: number | null = $state(null)
|
||||
let highestLoadedIndex: number | null = $state(null)
|
||||
let loadedIds: string[] = $state([])
|
||||
let topReached = $derived(loadedIds.includes("first"))
|
||||
let bottomReached = $derived(loadedIds.includes("last"))
|
||||
let watchScroll: boolean = $state(false)
|
||||
|
||||
|
||||
console.warn(`scroller[${debugname}] - RELOADED`, ids)
|
||||
|
||||
let isInitialLoad = true
|
||||
|
||||
async function refetchMessages(loadedIds: string[]) {
|
||||
messages = await messsageIdsToMessages(guildId, loadedIds)
|
||||
}
|
||||
|
||||
async function idsChanged() {
|
||||
console.log(`scroller[${debugname}] - ids changed - selectedMessageId ${selectedMessageId} ids length ${ids.length}`)
|
||||
if (ids.length === 0) {
|
||||
console.log(`scroller[${debugname}] - no messages to load`)
|
||||
messages = []
|
||||
return
|
||||
}
|
||||
watchScroll = false
|
||||
// let centerIndex = Math.round(ids.length / 2)
|
||||
|
||||
let centerIndex
|
||||
if (selectedMessageId) {
|
||||
centerIndex = ids.indexOf(selectedMessageId)
|
||||
console.log(`scroller[${debugname}] - selected message index ${centerIndex}`)
|
||||
}
|
||||
else {
|
||||
console.log(`scroller[${debugname}] - no selected message`)
|
||||
}
|
||||
|
||||
if (centerIndex == null || centerIndex < 0) {
|
||||
centerIndex = Math.round(ids.length - 1)
|
||||
}
|
||||
|
||||
// highestLoadedIndex = centerIndex
|
||||
// lowestLoadedIndex = centerIndex
|
||||
// loadedIds = [ids[centerIndex]]
|
||||
// for (let i = 1; i < maxMessages; i++) { // TODO: rewrite using slice
|
||||
// if (centerIndex + i < ids.length) {
|
||||
// highestLoadedIndex = centerIndex + i
|
||||
// loadedIds = [...loadedIds, ids[highestLoadedIndex]]
|
||||
// }
|
||||
// if (centerIndex - i >= 0) {
|
||||
// lowestLoadedIndex = centerIndex - i
|
||||
// loadedIds = [ids[lowestLoadedIndex], ...loadedIds, ]
|
||||
// }
|
||||
// }
|
||||
// loadedIds = [...ids]
|
||||
loadedIds = ids
|
||||
|
||||
await refetchMessages(loadedIds)
|
||||
if (isInitialLoad) {
|
||||
isInitialLoad = false
|
||||
setTimeout(async() => {
|
||||
if (!scrollContainer) { // did we destroy the component before the timeout happened?
|
||||
return
|
||||
}
|
||||
|
||||
// scroll to selected message
|
||||
if (selectedMessageId) {
|
||||
let failedToScroll = true
|
||||
// find selected message index
|
||||
if (loadedIds.includes(selectedMessageId)) {
|
||||
let selectedMessageElement = scrollContainer.querySelector(`[data-messageid="${selectedMessageId}"]`)
|
||||
if (selectedMessageElement) {
|
||||
selectedMessageElement.scrollIntoView({ block: "start", behavior: "instant" })
|
||||
console.log(`scroller[${debugname}] - selected message scrolled into view`)
|
||||
failedToScroll = false
|
||||
}
|
||||
}
|
||||
|
||||
if (failedToScroll) {
|
||||
if (ids.includes(selectedMessageId)) {
|
||||
console.warn(`scroller[${debugname}] - selected message not found in loaded messages BUT found in all messages`)
|
||||
}
|
||||
else {
|
||||
console.warn(`scroller[${debugname}] - selected message not found in loaded messages and in all messages`)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
||||
console.log(`scroller[${debugname}] - no selected message to scroll to`)
|
||||
}
|
||||
watchScroll = true
|
||||
}, 0)
|
||||
}
|
||||
|
||||
|
||||
// console.log(`scroller[${debugname}] - loadedIds`, loadedIds)
|
||||
}
|
||||
|
||||
async function loadBeforeWrapper() {
|
||||
console.log(`scroller[${debugname}] - loadBeforeWrapper before`, loadedIds.length)
|
||||
console.log(`scroller[${debugname}] - loadBefore`, loadBefore)
|
||||
await loadBefore()
|
||||
// for (let i = 1; i < loadIncrement; i++) { // TODO: rewrite using slice
|
||||
// if (!topReached) {
|
||||
// // // add lowest
|
||||
// // lowestLoadedIndex = lowestLoadedIndex - 1
|
||||
// // loadedIds = [ids[lowestLoadedIndex], ...loadedIds]
|
||||
|
||||
// // // remove highest
|
||||
// // if (loadedIds.length > maxMessages) {
|
||||
// // highestLoadedIndex = highestLoadedIndex - 1
|
||||
// // loadedIds = loadedIds.slice(0, -1)
|
||||
// // }
|
||||
// }
|
||||
// else {
|
||||
// console.log(`scroller[${debugname}] - top of the page - No more messages to load.`)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// await refetchMessages(loadedIds)
|
||||
console.log(`scroller[${debugname}] - loadBeforeWrapper after`, loadedIds.length)
|
||||
watchScroll = true
|
||||
|
||||
}
|
||||
|
||||
async function loadAfterWrapper() {
|
||||
console.log(`scroller[${debugname}] - loadAfterWrapper before`, loadedIds.length)
|
||||
console.log(`scroller[${debugname}] - loadAfter`, loadAfter)
|
||||
await loadAfter()
|
||||
// for (let i = 1; i < loadIncrement; i++) { // TODO: rewrite using slice
|
||||
// if (!bottomReached) {
|
||||
// // // add highest
|
||||
// // highestLoadedIndex = highestLoadedIndex + 1
|
||||
// // loadedIds = [...loadedIds, ids[highestLoadedIndex]]
|
||||
|
||||
// // // remove lowest
|
||||
// // if (loadedIds.length > maxMessages) {
|
||||
// // lowestLoadedIndex = lowestLoadedIndex + 1
|
||||
// // loadedIds = loadedIds.slice(1)
|
||||
// // }
|
||||
// }
|
||||
// else {
|
||||
// console.log(`scroller[${debugname}] - bottom of the page - No more messages to load.`)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
console.log(`scroller[${debugname}] - loadAfterWrapper after`, loadedIds.length)
|
||||
|
||||
// await refetchMessages(loadedIds)
|
||||
watchScroll = true
|
||||
}
|
||||
|
||||
|
||||
// https://github.com/sveltejs/svelte/issues/9248#issuecomment-1732246774
|
||||
function explicitEffect(fn, depsFn) {
|
||||
$effect(() => {
|
||||
depsFn();
|
||||
untrack(fn);
|
||||
});
|
||||
}
|
||||
// run idsChanged() only if ids change
|
||||
explicitEffect(
|
||||
() => {
|
||||
idsChanged()
|
||||
},
|
||||
() => [ids]
|
||||
);
|
||||
|
||||
async function handleScroll(event) {
|
||||
if (!watchScroll)
|
||||
return
|
||||
if (messages.length === 0)
|
||||
return
|
||||
|
||||
// console.log(`scroller[${debugname}] - handleScroll`, JSON.parse(JSON.stringify(loadedIds)))
|
||||
// console.log(`scroller[${debugname}] - handleScroll`, loadedIds.length)
|
||||
|
||||
const element = event.target
|
||||
let margin = startLoadingPixels
|
||||
// console.log(`scroller[${debugname}] - scrollTop ${element.scrollTop}`)
|
||||
if (!topReached && element.scrollTop < margin) {
|
||||
watchScroll = false
|
||||
console.log(`scroller[${debugname}] - reached top of the page`)
|
||||
await loadBeforeWrapper()
|
||||
}
|
||||
if (!bottomReached && element.scrollHeight - element.scrollTop <= element.clientHeight + margin) {
|
||||
watchScroll = false
|
||||
console.log(`scroller[${debugname}] - reached bottom of the page`)
|
||||
await loadAfterWrapper()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="scroll-container" class:bottomaligned={bottomAligned} onscroll={handleScroll} bind:this={scrollContainer}>
|
||||
{#if messages && messages.length > 0}
|
||||
{#if topReached}
|
||||
{@render channelStartSnippet(null, messages[0], null)}
|
||||
{/if}
|
||||
{#each messages as message, index (message._id)}
|
||||
{@render renderMessageSnippet(lowestLoadedIndex + index, message, messages[index - 1])}
|
||||
{/each}
|
||||
{#if bottomReached}
|
||||
{@render channelEndSnippet(null, null, null)}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.scroll-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Needed for bottom aligment */
|
||||
/* can't use justify-content: flex-end, because that would break scroll */
|
||||
/* https://stackoverflow.com/a/37515194 */
|
||||
.scroll-container.bottomaligned {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
/* align messages to the bottom if there are not enough messages to fill the container height */
|
||||
:global(.scroll-container.bottomaligned > :first-child) {
|
||||
margin-top: auto !important;
|
||||
}
|
||||
/* - */
|
||||
|
||||
.scroll-container::-webkit-scrollbar-track {
|
||||
background-color: #2b2d31;
|
||||
}
|
||||
.scroll-container::-webkit-scrollbar-corner {
|
||||
background-color: #646464;
|
||||
}
|
||||
|
||||
.scroll-container::-webkit-resizer {
|
||||
background-color: #666;
|
||||
}
|
||||
.scroll-container::-webkit-scrollbar-track-piece {
|
||||
background-color:#313338;
|
||||
}
|
||||
.scroll-container::-webkit-scrollbar {
|
||||
height: 3px;
|
||||
width: 14px;
|
||||
}
|
||||
.scroll-container::-webkit-scrollbar-thumb {
|
||||
height: 50px;
|
||||
background-color: #1a1b1e;
|
||||
|
||||
width: 5px;
|
||||
border-radius: 10px;
|
||||
|
||||
/*left+right scrollbar padding magix*/
|
||||
background-clip: padding-box;
|
||||
border: 3px solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
</style>
|
|
@ -13,11 +13,15 @@
|
|||
selectedMessageId: string | null
|
||||
bottomAligned: boolean
|
||||
debugname: string
|
||||
loadBefore: () => Promise<void>
|
||||
loadAfter: () => Promise<void>
|
||||
renderMessageSnippet: (index: number, message: Message, previousMessage: Message) => void
|
||||
channelStartSnippet: (index: number, message: Message, previousMessage: Message) => void
|
||||
channelEndSnippet: (index: number, message: Message, previousMessage: Message) => void
|
||||
}
|
||||
let { ids, guildId, selectedMessageId = null, renderMessageSnippet, bottomAligned, debugname=""}: MyProps = $props();
|
||||
let { ids, guildId, selectedMessageId = null, renderMessageSnippet, channelStartSnippet, channelEndSnippet, bottomAligned, debugname="", loadBefore, loadAfter}: MyProps = $props();
|
||||
|
||||
let maxMessages = 120
|
||||
let maxMessages = 12000
|
||||
let loadIncrement = 30
|
||||
let startLoadingPixels = 500
|
||||
let scrollContainer: HTMLDivElement
|
||||
|
@ -26,10 +30,14 @@
|
|||
let lowestLoadedIndex: number | null = $state(null)
|
||||
let highestLoadedIndex: number | null = $state(null)
|
||||
let loadedIds: string[] = $state([])
|
||||
let topReached = $derived(loadedIds.includes("first"))
|
||||
let bottomReached = $derived(loadedIds.includes("last"))
|
||||
let watchScroll: boolean = $state(false)
|
||||
|
||||
let topLoaded = $derived(lowestLoadedIndex === 0)
|
||||
// let bottomLoaded = $derived(highestLoadedIndex === ids.length - 1) // not used
|
||||
|
||||
console.warn(`scroller[${debugname}] - RELOADED`, ids)
|
||||
|
||||
let isInitialLoad = true
|
||||
|
||||
async function refetchMessages(loadedIds: string[]) {
|
||||
messages = await messsageIdsToMessages(guildId, loadedIds)
|
||||
|
@ -58,102 +66,116 @@
|
|||
centerIndex = Math.round(ids.length - 1)
|
||||
}
|
||||
|
||||
highestLoadedIndex = centerIndex
|
||||
lowestLoadedIndex = centerIndex
|
||||
loadedIds = [ids[centerIndex]]
|
||||
for (let i = 1; i < maxMessages; i++) { // TODO: rewrite using slice
|
||||
if (centerIndex + i < ids.length) {
|
||||
highestLoadedIndex = centerIndex + i
|
||||
loadedIds = [...loadedIds, ids[highestLoadedIndex]]
|
||||
}
|
||||
if (centerIndex - i >= 0) {
|
||||
lowestLoadedIndex = centerIndex - i
|
||||
loadedIds = [ids[lowestLoadedIndex], ...loadedIds, ]
|
||||
}
|
||||
}
|
||||
// highestLoadedIndex = centerIndex
|
||||
// lowestLoadedIndex = centerIndex
|
||||
// loadedIds = [ids[centerIndex]]
|
||||
// for (let i = 1; i < maxMessages; i++) { // TODO: rewrite using slice
|
||||
// if (centerIndex + i < ids.length) {
|
||||
// highestLoadedIndex = centerIndex + i
|
||||
// loadedIds = [...loadedIds, ids[highestLoadedIndex]]
|
||||
// }
|
||||
// if (centerIndex - i >= 0) {
|
||||
// lowestLoadedIndex = centerIndex - i
|
||||
// loadedIds = [ids[lowestLoadedIndex], ...loadedIds, ]
|
||||
// }
|
||||
// }
|
||||
// loadedIds = [...ids]
|
||||
loadedIds = ids
|
||||
|
||||
await refetchMessages(loadedIds)
|
||||
setTimeout(async() => {
|
||||
if (!scrollContainer) { // did we destroy the component before the timeout happened?
|
||||
return
|
||||
}
|
||||
|
||||
// scroll to selected message
|
||||
if (selectedMessageId) {
|
||||
let failedToScroll = true
|
||||
// find selected message index
|
||||
if (loadedIds.includes(selectedMessageId)) {
|
||||
let selectedMessageElement = scrollContainer.querySelector(`[data-messageid="${selectedMessageId}"]`)
|
||||
if (selectedMessageElement) {
|
||||
selectedMessageElement.scrollIntoView({ block: "start", behavior: "instant" })
|
||||
console.log(`scroller[${debugname}] - selected message scrolled into view`)
|
||||
failedToScroll = false
|
||||
}
|
||||
if (isInitialLoad) {
|
||||
isInitialLoad = false
|
||||
setTimeout(async() => {
|
||||
if (!scrollContainer) { // did we destroy the component before the timeout happened?
|
||||
return
|
||||
}
|
||||
|
||||
if (failedToScroll) {
|
||||
if (ids.includes(selectedMessageId)) {
|
||||
console.warn(`scroller[${debugname}] - selected message not found in loaded messages BUT found in all messages`)
|
||||
// scroll to selected message
|
||||
if (selectedMessageId) {
|
||||
let failedToScroll = true
|
||||
// find selected message index
|
||||
if (loadedIds.includes(selectedMessageId)) {
|
||||
let selectedMessageElement = scrollContainer.querySelector(`[data-messageid="${selectedMessageId}"]`)
|
||||
if (selectedMessageElement) {
|
||||
selectedMessageElement.scrollIntoView({ block: "start", behavior: "instant" })
|
||||
console.log(`scroller[${debugname}] - selected message scrolled into view`)
|
||||
failedToScroll = false
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.warn(`scroller[${debugname}] - selected message not found in loaded messages and in all messages`)
|
||||
|
||||
if (failedToScroll) {
|
||||
if (ids.includes(selectedMessageId)) {
|
||||
console.warn(`scroller[${debugname}] - selected message not found in loaded messages BUT found in all messages`)
|
||||
}
|
||||
else {
|
||||
console.warn(`scroller[${debugname}] - selected message not found in loaded messages and in all messages`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
||||
console.log(`scroller[${debugname}] - no selected message to scroll to`)
|
||||
}
|
||||
watchScroll = true
|
||||
}, 0)
|
||||
else {
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
||||
console.log(`scroller[${debugname}] - no selected message to scroll to`)
|
||||
}
|
||||
watchScroll = true
|
||||
}, 0)
|
||||
}
|
||||
|
||||
|
||||
// console.log(`scroller[${debugname}] - loadedIds`, loadedIds)
|
||||
}
|
||||
|
||||
async function loadLowUnloadHigh() {
|
||||
for (let i = 1; i < loadIncrement; i++) { // TODO: rewrite using slice
|
||||
if (lowestLoadedIndex - 1 >= 0) {
|
||||
// add lowest
|
||||
lowestLoadedIndex = lowestLoadedIndex - 1
|
||||
loadedIds = [ids[lowestLoadedIndex], ...loadedIds]
|
||||
async function loadBeforeWrapper() {
|
||||
console.log(`scroller[${debugname}] - loadBeforeWrapper before`, loadedIds.length)
|
||||
console.log(`scroller[${debugname}] - loadBefore`, loadBefore)
|
||||
await loadBefore()
|
||||
// for (let i = 1; i < loadIncrement; i++) { // TODO: rewrite using slice
|
||||
// if (!topReached) {
|
||||
// // // add lowest
|
||||
// // lowestLoadedIndex = lowestLoadedIndex - 1
|
||||
// // loadedIds = [ids[lowestLoadedIndex], ...loadedIds]
|
||||
|
||||
// remove highest
|
||||
if (loadedIds.length > maxMessages) {
|
||||
highestLoadedIndex = highestLoadedIndex - 1
|
||||
loadedIds = loadedIds.slice(0, -1)
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`scroller[${debugname}] - top of the page - No more messages to load.`)
|
||||
break
|
||||
}
|
||||
}
|
||||
console.log(`scroller[${debugname}] - loadLowUnloadHigh`)
|
||||
// // // remove highest
|
||||
// // if (loadedIds.length > maxMessages) {
|
||||
// // highestLoadedIndex = highestLoadedIndex - 1
|
||||
// // loadedIds = loadedIds.slice(0, -1)
|
||||
// // }
|
||||
// }
|
||||
// else {
|
||||
// console.log(`scroller[${debugname}] - top of the page - No more messages to load.`)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
await refetchMessages(loadedIds)
|
||||
// await refetchMessages(loadedIds)
|
||||
console.log(`scroller[${debugname}] - loadBeforeWrapper after`, loadedIds.length)
|
||||
watchScroll = true
|
||||
|
||||
}
|
||||
|
||||
async function loadHighUnloadLow() {
|
||||
for (let i = 1; i < loadIncrement; i++) { // TODO: rewrite using slice
|
||||
if (highestLoadedIndex + 1 < ids.length) {
|
||||
// add highest
|
||||
highestLoadedIndex = highestLoadedIndex + 1
|
||||
loadedIds = [...loadedIds, ids[highestLoadedIndex]]
|
||||
async function loadAfterWrapper() {
|
||||
console.log(`scroller[${debugname}] - loadAfterWrapper before`, loadedIds.length)
|
||||
console.log(`scroller[${debugname}] - loadAfter`, loadAfter)
|
||||
await loadAfter()
|
||||
// for (let i = 1; i < loadIncrement; i++) { // TODO: rewrite using slice
|
||||
// if (!bottomReached) {
|
||||
// // // add highest
|
||||
// // highestLoadedIndex = highestLoadedIndex + 1
|
||||
// // loadedIds = [...loadedIds, ids[highestLoadedIndex]]
|
||||
|
||||
// remove lowest
|
||||
if (loadedIds.length > maxMessages) {
|
||||
lowestLoadedIndex = lowestLoadedIndex + 1
|
||||
loadedIds = loadedIds.slice(1)
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`scroller[${debugname}] - bottom of the page - No more messages to load.`)
|
||||
break
|
||||
}
|
||||
}
|
||||
console.log(`scroller[${debugname}] - loadHighUnloadLow`)
|
||||
// // // remove lowest
|
||||
// // if (loadedIds.length > maxMessages) {
|
||||
// // lowestLoadedIndex = lowestLoadedIndex + 1
|
||||
// // loadedIds = loadedIds.slice(1)
|
||||
// // }
|
||||
// }
|
||||
// else {
|
||||
// console.log(`scroller[${debugname}] - bottom of the page - No more messages to load.`)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
console.log(`scroller[${debugname}] - loadAfterWrapper after`, loadedIds.length)
|
||||
|
||||
await refetchMessages(loadedIds)
|
||||
// await refetchMessages(loadedIds)
|
||||
watchScroll = true
|
||||
}
|
||||
|
||||
|
@ -179,26 +201,36 @@
|
|||
if (messages.length === 0)
|
||||
return
|
||||
|
||||
// console.log(`scroller[${debugname}] - handleScroll`, JSON.parse(JSON.stringify(loadedIds)))
|
||||
// console.log(`scroller[${debugname}] - handleScroll`, loadedIds.length)
|
||||
|
||||
const element = event.target
|
||||
let margin = startLoadingPixels
|
||||
if (element.scrollTop < margin) {
|
||||
// console.log(`scroller[${debugname}] - scrollTop ${element.scrollTop}`)
|
||||
if (!topReached && element.scrollTop < margin) {
|
||||
watchScroll = false
|
||||
console.log(`scroller[${debugname}] - reached top of the page`)
|
||||
loadLowUnloadHigh()
|
||||
await loadBeforeWrapper()
|
||||
}
|
||||
if (element.scrollHeight - element.scrollTop <= element.clientHeight + margin) {
|
||||
if (!bottomReached && element.scrollHeight - element.scrollTop <= element.clientHeight + margin) {
|
||||
watchScroll = false
|
||||
console.log(`scroller[${debugname}] - reached bottom of the page`)
|
||||
loadHighUnloadLow()
|
||||
await loadAfterWrapper()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="scroll-container" class:bottomaligned={bottomAligned} onscroll={handleScroll} bind:this={scrollContainer}>
|
||||
{#if messages}
|
||||
{#if messages && messages.length > 0}
|
||||
{#if topReached}
|
||||
{@render channelStartSnippet(null, messages[0], null)}
|
||||
{/if}
|
||||
{#each messages as message, index (message._id)}
|
||||
{@render renderMessageSnippet(lowestLoadedIndex + index, message, messages[index - 1])}
|
||||
{/each}
|
||||
{#if bottomReached}
|
||||
{@render channelEndSnippet(null, null, null)}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
238
newfrontend/src/lib/InfiniteScroll2.svelte
Normal file
238
newfrontend/src/lib/InfiniteScroll2.svelte
Normal file
|
@ -0,0 +1,238 @@
|
|||
<script lang="ts">
|
||||
import { onMount, tick, type Snippet } from "svelte";
|
||||
import { messsageIdsToMessages } from "../js/messages";
|
||||
|
||||
interface MyProps {
|
||||
fetchMessageIds: (direction: "before" | "after" | "around" , messageId: string, limit: number) => Promise<string[]>
|
||||
guildId: string
|
||||
snippetMessage: Snippet
|
||||
}
|
||||
|
||||
let { fetchMessageIds, guildId, snippetMessage}: MyProps = $props();
|
||||
|
||||
let SHOWDEBUG = true
|
||||
|
||||
// message id can be 24 length strings or be "first" or "last"
|
||||
let ids = $state<string[]>([])
|
||||
let renderedIds = $state<string[]>([])
|
||||
let messages = $state<any[]>([])
|
||||
let topReached = $derived(renderedIds.includes("first"))
|
||||
let bottomReached = $derived(renderedIds.includes("last"))
|
||||
let earliestRenderedId = $state<string | null>(null)
|
||||
// const renderedCount = 50
|
||||
|
||||
let scrollContainerHeight = $state(1000)
|
||||
let scrollableHeight = $state(2000)
|
||||
let maxRenderedCount = $derived(Math.max(100, Math.ceil(scrollContainerHeight / 10)))
|
||||
let marginPixels = $derived((scrollableHeight - scrollContainerHeight) / 8)
|
||||
|
||||
async function fetchIdsBefore(messageId: string) {
|
||||
const newMessageIds = await fetchMessageIds("before", messageId, maxRenderedCount)
|
||||
return [...newMessageIds, ...ids]
|
||||
}
|
||||
|
||||
async function fetchIdsAfter(messageId: string) {
|
||||
const newMessageIds = await fetchMessageIds("after", messageId, maxRenderedCount)
|
||||
return [...ids, ...newMessageIds]
|
||||
}
|
||||
|
||||
async function fetchIdsAround(messageId: string) {
|
||||
const newMessageIds = await fetchMessageIds("around", messageId, maxRenderedCount)
|
||||
return newMessageIds
|
||||
}
|
||||
|
||||
|
||||
function calculateRenderedIdsAround(messageId: string) {
|
||||
if (!ids.includes(messageId)) {
|
||||
console.warn(`messageId ${messageId} not found in ids`)
|
||||
return
|
||||
}
|
||||
|
||||
const countToRender = Math.min(maxRenderedCount, ids.length)
|
||||
let suggestedToRender = [messageId]
|
||||
let renderedCount = 1
|
||||
for (let i = 1; i < countToRender; i++) {
|
||||
const before = ids[ids.indexOf(messageId) - i]
|
||||
const after = ids[ids.indexOf(messageId) + i]
|
||||
if (before) {
|
||||
suggestedToRender.unshift(before)
|
||||
renderedCount++
|
||||
}
|
||||
if (after) {
|
||||
suggestedToRender.push(after)
|
||||
renderedCount++
|
||||
}
|
||||
if (renderedCount >= countToRender) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return suggestedToRender
|
||||
}
|
||||
|
||||
let watchScroll = $state(true)
|
||||
async function handleScroll(event) {
|
||||
scrollableHeight = event.target.scrollHeight
|
||||
if (ids.length === 0) {
|
||||
return
|
||||
}
|
||||
if (!watchScroll) {
|
||||
return
|
||||
}
|
||||
|
||||
const element = event.target
|
||||
const scrollTop = element.scrollTop
|
||||
const scrollBottom = element.scrollHeight - element.clientHeight - scrollTop
|
||||
|
||||
if (!topReached && scrollTop < marginPixels) {
|
||||
watchScroll = false
|
||||
// save position of first rendered message
|
||||
const id = renderedIds[0]
|
||||
const firstRenderedMessage = element.querySelector(`[data-messageid="${id}"]`)
|
||||
|
||||
// let position
|
||||
// if (firstRenderedMessage) {
|
||||
// position = firstRenderedMessage.getBoundingClientRect()
|
||||
// }
|
||||
// else {
|
||||
// console.warn(`firstRenderedMessage ${id} not found`)
|
||||
// }
|
||||
|
||||
|
||||
if (ids[0] === renderedIds[0]) { // if not cached
|
||||
const newIds = await fetchIdsBefore(ids[0])
|
||||
ids = newIds
|
||||
}
|
||||
|
||||
renderedIds = calculateRenderedIdsAround(id)
|
||||
messages = await messsageIdsToMessages(guildId, renderedIds)
|
||||
// renderedIds = newIds
|
||||
await tick(); // wait for render
|
||||
|
||||
// restore scroll position
|
||||
// if (position) {
|
||||
// const newFirstRenderedMessage = element.querySelector(`[data-messageid="${id}"]`)
|
||||
// if (newFirstRenderedMessage) {
|
||||
// const newPosition = newFirstRenderedMessage.getBoundingClientRect()
|
||||
// element.scrollTop = newPosition.top - position.top + scrollTop
|
||||
// }
|
||||
// else {
|
||||
// console.warn(`newFirstRenderedMessage ${id} not found`)
|
||||
// }
|
||||
// }
|
||||
|
||||
watchScroll = true
|
||||
}
|
||||
else if (!bottomReached && scrollBottom < marginPixels) {
|
||||
watchScroll = false
|
||||
const id = renderedIds[renderedIds.length - 1]
|
||||
|
||||
if (ids[ids.length - 1] === renderedIds[renderedIds.length - 1]) { // if not cached
|
||||
const newIds = await fetchIdsAfter(ids[ids.length - 1])
|
||||
ids = newIds
|
||||
}
|
||||
renderedIds = calculateRenderedIdsAround(id)
|
||||
messages = await messsageIdsToMessages(guildId, renderedIds)
|
||||
// renderedIds = newIds
|
||||
await tick(); // wait for render
|
||||
watchScroll = true
|
||||
}
|
||||
}
|
||||
|
||||
let scrollContainer: HTMLDivElement
|
||||
onMount(async () => {
|
||||
const newMessageIds = await fetchMessageIds("around", "last", maxRenderedCount)
|
||||
ids = newMessageIds
|
||||
renderedIds = calculateRenderedIdsAround("last")
|
||||
messages = await messsageIdsToMessages(guildId, renderedIds)
|
||||
await tick();
|
||||
// const tout = setTimeout(async() => {
|
||||
if (scrollContainer) {
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
||||
console.log("scrolling to bottom")
|
||||
}
|
||||
else {
|
||||
console.warn("scrollContainer not found")
|
||||
}
|
||||
// }, 0)
|
||||
|
||||
|
||||
// const inter1 = setInterval(() => {
|
||||
// if (scrollContainer) {
|
||||
// const scrollTop = scrollContainer.scrollTop
|
||||
// if (!topReached && scrollTop < 10) {
|
||||
// scrollContainer.scrollTop = 150
|
||||
// console.log("fixing scroll position")
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }, 1000)
|
||||
|
||||
// return () => {
|
||||
// clearInterval(inter1)
|
||||
// }
|
||||
|
||||
// return () => {
|
||||
// clearTimeout(tout)
|
||||
// }
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<div class="wrapper">
|
||||
{#if SHOWDEBUG}
|
||||
<small class="debug-container">rendered {renderedIds.length} / {ids.length}, topReached {topReached} ({renderedIds[0]}), bottomReached {bottomReached} ({renderedIds[renderedIds.length - 1]}), container height {scrollContainerHeight}px, maxRenderedCount {maxRenderedCount}, scrollableHeight {scrollableHeight}px, marginPixels {marginPixels}px</small>
|
||||
{/if}
|
||||
<div class="scroll-container" onscroll={handleScroll} bind:this={scrollContainer} bind:clientHeight={scrollContainerHeight}>
|
||||
{#each messages as message (message._id)}
|
||||
<div class="message" data-messageid={message._id}>
|
||||
<!-- <div>{message._id}</div> -->
|
||||
{@render snippetMessage(message, message)}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.message {
|
||||
min-height: 30px;
|
||||
}
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.scroll-container::-webkit-scrollbar-track {
|
||||
background-color: #2b2d31;
|
||||
}
|
||||
.scroll-container::-webkit-scrollbar-corner {
|
||||
background-color: #646464;
|
||||
}
|
||||
|
||||
.scroll-container::-webkit-resizer {
|
||||
background-color: #666;
|
||||
}
|
||||
.scroll-container::-webkit-scrollbar-track-piece {
|
||||
background-color:#313338;
|
||||
}
|
||||
.scroll-container::-webkit-scrollbar {
|
||||
height: 3px;
|
||||
width: 14px;
|
||||
}
|
||||
.scroll-container::-webkit-scrollbar-thumb {
|
||||
height: 50px;
|
||||
background-color: #1a1b1e;
|
||||
|
||||
width: 5px;
|
||||
border-radius: 10px;
|
||||
|
||||
/*left+right scrollbar padding magix*/
|
||||
background-clip: padding-box;
|
||||
border: 3px solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
</style>
|
|
@ -4,6 +4,7 @@
|
|||
import { getLayoutState } from "../js/stores/layoutState.svelte";
|
||||
import DateSeparator from "./DateSeparator.svelte";
|
||||
import InfiniteScroll from "./InfiniteScroll.svelte";
|
||||
import InfiniteScroll2 from "./InfiniteScroll2.svelte";
|
||||
import Pinned from "./Pinned.svelte";
|
||||
import Icon from "./icons/Icon.svelte";
|
||||
import ChannelIcon from "./menuchannels/ChannelIcon.svelte";
|
||||
|
@ -17,12 +18,27 @@
|
|||
|
||||
const guildState = getGuildState()
|
||||
const layoutState = getLayoutState()
|
||||
|
||||
let apiGuildId = $derived(guildState.guildId ? guildState.guildId : "000000000000000000000000")
|
||||
let apiChannelId = $derived(guildState.threadId)
|
||||
|
||||
export async function fetchMessageIds(direction: "before" | "after" | "around" | "first" | "last", messageId: string | null = null, limit: number) {
|
||||
try {
|
||||
let response = await fetch(`/api/message-ids-paginated?guild_id=${encodeURIComponent(apiGuildId)}&channel_id=${encodeURIComponent(apiChannelId)}&direction=${direction}&message_id=${encodeURIComponent(messageId)}&limit=${limit}`)
|
||||
let messageIds = await response.json()
|
||||
return messageIds
|
||||
}
|
||||
catch (e) {
|
||||
console.error("api - Failed to fetch message ids", e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#snippet renderMessageSnippet(index, message, previousMessage)}
|
||||
{#if index === 0}
|
||||
<!-- {#if index === 0}
|
||||
<ChannelStart channelName={message.channelName} isThread={true} messageAuthor={message.author} />
|
||||
{/if}
|
||||
{/if} -->
|
||||
|
||||
{#if isDateDifferent(previousMessage, message)}
|
||||
<DateSeparator messageId={message._id} />
|
||||
|
@ -33,6 +49,22 @@
|
|||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet renderMessageSnippet2(message, previousMessage)}
|
||||
<div data-messageid={message._id}>
|
||||
{#if message._id === "first"}
|
||||
<!-- <ChannelStart channelName={message.channelName} isThread={true} messageAuthor={message.author} /> -->
|
||||
<div>thread start</div>
|
||||
{:else if message._id === "last"}
|
||||
<div>thread end</div>
|
||||
{:else}
|
||||
{#if isDateDifferent(previousMessage, message)}
|
||||
<DateSeparator messageId={message._id} />
|
||||
{/if}
|
||||
<Message message={message} previousMessage={previousMessage} />
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
|
||||
<div class="thread-wrapper">
|
||||
<div class="header-main">
|
||||
|
@ -62,7 +94,20 @@
|
|||
<div class="thread">
|
||||
<!-- TODO: support change of threadMessageId without rerender -->
|
||||
{#key guildState.threadMessageId}
|
||||
<InfiniteScroll debugname="thread" ids={guildState.threadMessagesIds} guildId={guildState.guildId} selectedMessageId={guildState.threadMessageId} renderMessageSnippet={renderMessageSnippet} bottomAligned={true} />
|
||||
<!-- <InfiniteScroll
|
||||
debugname="thread"
|
||||
ids={guildState.threadMessagesIds}
|
||||
guildId={guildState.guildId}
|
||||
selectedMessageId={guildState.threadMessageId}
|
||||
renderMessageSnippet={renderMessageSnippet}
|
||||
channelStartSnippet={channelStartSnippet}
|
||||
channelEndSnippet={channelEndSnippet}
|
||||
bottomAligned={true} /> -->
|
||||
<InfiniteScroll2
|
||||
fetchMessageIds={fetchMessageIds}
|
||||
guildId={apiGuildId}
|
||||
snippetMessage={renderMessageSnippet2}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
async function toggle() {
|
||||
isOpen = !isOpen
|
||||
if (isOpen) {
|
||||
await guildState.changeChannelId(channel._id)
|
||||
if (guildState.channelId !== channel._id) {
|
||||
await guildState.changeChannelId(channel._id, "last")
|
||||
}
|
||||
await guildState.pushState()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,8 +35,12 @@
|
|||
|
||||
async function changeThread(guildId: string, channelId: string, threadId: string) {
|
||||
await guildState.changeGuildId(guildId)
|
||||
await guildState.changeChannelId(channelId)
|
||||
await guildState.changeThreadId(threadId)
|
||||
if (guildState.channelId !== channelId) {
|
||||
await guildState.changeChannelId(channelId, "last")
|
||||
}
|
||||
if (guildState.threadId !== threadId) {
|
||||
await guildState.changeThreadId(threadId, "last")
|
||||
}
|
||||
await guildState.pushState()
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</script>
|
||||
|
||||
{#if isThread}
|
||||
<div class="wrapper">
|
||||
<div class="wrapper" data-messageid="first">
|
||||
<div class="thread-icon">
|
||||
<Icon name="channeltype/thread" width={30} />
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
|||
<div class="subtitle">Started by <span class="subtitle-person"><MessageAuthorName author={messageAuthor} on:click={() => viewUserState.setUser(messageAuthor)} /></span></div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="wrapper">
|
||||
<div class="wrapper" data-messageid="first">
|
||||
<div class="channel-icon">
|
||||
<Icon name="channeltype/channel" width={42} />
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { findChannel, findThread, getGuildState, isChannel } from '../../js/stores/guildState.svelte';
|
||||
import InfiniteScroll from '../InfiniteScroll.svelte';
|
||||
import InfiniteScroll from '../InfiniteScroll-old.svelte';
|
||||
import Icon from '../icons/Icon.svelte';
|
||||
import ChannelIcon from '../menuchannels/ChannelIcon.svelte';
|
||||
import Message from '../message/Message.svelte';
|
||||
|
@ -24,10 +24,10 @@
|
|||
{#if threadObj}
|
||||
{@const parentChannelObj = findChannel(threadObj.categoryId)}
|
||||
<div class="channelthread-name-wrapper">
|
||||
<button class="thread-name" onclick={()=>guildState.changeThreadId(threadObj._id)}>
|
||||
<button class="thread-name" onclick={()=>guildState.changeThreadId(threadObj._id, null)}>
|
||||
<ChannelIcon channel={threadObj} width={16} />{threadObj.name}
|
||||
</button>
|
||||
<button class="channel-name-small" onclick={()=>guildState.changeChannelId(parentChannelObj._id)}>
|
||||
<button class="channel-name-small" onclick={()=>guildState.changeChannelId(parentChannelObj._id, null)}>
|
||||
<ChannelIcon channel={parentChannelObj} width={12} />{parentChannelObj.name}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -35,9 +35,9 @@
|
|||
<div class="channelthread-name-wrapper">
|
||||
<button class="channelthread-name" onclick={()=>{
|
||||
if (isChannel(channelObj._id)) {
|
||||
guildState.changeChannelId(channelObj._id)
|
||||
guildState.changeChannelId(channelObj._id, null)
|
||||
} else {
|
||||
guildState.changeThreadId(channelObj._id)
|
||||
guildState.changeThreadId(channelObj._id, null)
|
||||
}
|
||||
}}>
|
||||
<ChannelIcon channel={channelObj} width={16} />{channelObj.name}
|
||||
|
@ -75,7 +75,14 @@
|
|||
<div class="header-txt">{addCommas(searchState.searchResultsIds.length)} Results</div>
|
||||
</div>
|
||||
{#key searchState.submittedSearchPrompt}
|
||||
<InfiniteScroll debugname="search" ids={searchState.searchResultsIds} guildId={guildState.guildId} selectedMessageId={searchState.searchResultsIds[0]} renderMessageSnippet={renderMessageSnippet} bottomAligned={false} />
|
||||
<InfiniteScroll
|
||||
debugname="search"
|
||||
ids={searchState.searchResultsIds}
|
||||
guildId={guildState.guildId}
|
||||
selectedMessageId={searchState.searchResultsIds[0]}
|
||||
renderMessageSnippet={renderMessageSnippet}
|
||||
bottomAligned={false}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue