greatly improve performance
This commit is contained in:
parent
10c851c679
commit
e3e1b9ddad
7 changed files with 64 additions and 29 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
frontend-new/
|
||||||
.env
|
.env
|
||||||
|
|
|
@ -86,7 +86,7 @@ body {
|
||||||
line-height: 1.75;
|
line-height: 1.75;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
|
|
||||||
width: var(--viewportWidth);
|
width: var(--viewportWidth);
|
||||||
height: var(--viewportHeight);
|
height: var(--viewportHeight);
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { CornerUpLeftIcon, MoreVerticalIcon } from "svelte-feather-icons";
|
import { CornerUpLeftIcon, MoreVerticalIcon } from "svelte-feather-icons";
|
||||||
import { getItem } from "../storage";
|
|
||||||
import { overlayStore, OverlayType, setMessageInputEvent, userInfoStore } from "../stores";
|
import { overlayStore, OverlayType, setMessageInputEvent, userInfoStore } from "../stores";
|
||||||
|
|
||||||
export let message;
|
export let message;
|
||||||
export let clumped = false;
|
|
||||||
|
|
||||||
const reply = () => {
|
const reply = () => {
|
||||||
let replyString = "";
|
let replyString = "";
|
||||||
|
@ -61,10 +59,6 @@
|
||||||
margin-right: var(--space-xs);
|
margin-right: var(--space-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-hidden {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-message {
|
.edit-message {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -85,8 +79,12 @@
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message.clumped .author {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.message:hover .date,
|
.message:hover .date,
|
||||||
.message:hover .author-hidden {
|
.message.clumped:hover .author {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,15 +99,15 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="message" class:clumped class:pinged={ userInfoStore.value && message.content.includes(`@${userInfoStore.value.username}`) }>
|
<div class="message" class:clumped={ message._clumped } class:pinged={ message._mentions }>
|
||||||
<span class="author" class:author-hidden={ clumped }>{ message.author_username }</span>
|
<span class="author">{ message.author_username }</span>
|
||||||
<span class="message-content" class:pending={ message._isPending }>{ message.content }</span>
|
<span class="message-content" class:pending={ message._isPending }>{ message.content }</span>
|
||||||
|
|
||||||
<span class="date">{ new Intl.DateTimeFormat(getItem("ui:locale"), { hour: "numeric", minute: "numeric" }).format(new Date(parseInt(message.created_at))) }</span>
|
<span class="date">{ message._createdAtTimeString }</span>
|
||||||
<button class="icon-button edit-message" on:click="{ reply }" aria-label="Reply to Message">
|
<button class="icon-button edit-message" on:click="{ reply }" aria-label="Reply to Message">
|
||||||
<CornerUpLeftIcon />
|
<CornerUpLeftIcon />
|
||||||
</button>
|
</button>
|
||||||
{#if userInfoStore.value && (message.author_id === userInfoStore.value.id || userInfoStore.value.is_superuser)}
|
{#if message._editable}
|
||||||
<button class="icon-button edit-message" on:click="{ () => overlayStore.push(OverlayType.EditMessage, { message }) }" aria-label="Edit Message">
|
<button class="icon-button edit-message" on:click="{ () => overlayStore.push(OverlayType.EditMessage, { message }) }" aria-label="Edit Message">
|
||||||
<MoreVerticalIcon />
|
<MoreVerticalIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -83,17 +83,13 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="messages-container" on:scroll={ onScroll } bind:this={ scrollTarget }>
|
<div class="messages-container" on:scroll={ onScroll } bind:this={ scrollTarget }>
|
||||||
{#each $messages as message, i (message.id)}
|
{#each $messages as message (message.id)}
|
||||||
{@const previousMessage = $messages[i - 1]}
|
{#if message._showDateMarkerAbove}
|
||||||
{@const previousDate = previousMessage ? new Date(parseInt(previousMessage.created_at)) : null}
|
|
||||||
{@const currentDate = new Date(parseInt(message.created_at))}
|
|
||||||
|
|
||||||
{#if previousDate && previousDate.toLocaleDateString() !== currentDate.toLocaleDateString()}
|
|
||||||
<div class="time-separator">
|
<div class="time-separator">
|
||||||
<span>{ new Intl.DateTimeFormat(getItem("ui:locale"), { month: "long", day: "numeric", year: "numeric" }).format(currentDate) }</span>
|
<span>{ new Intl.DateTimeFormat(getItem("ui:locale"), { month: "long", day: "numeric", year: "numeric" }).format(message._createdAtDate) }</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Message message={message} clumped={ previousDate && (currentDate.getTime() - previousDate.getTime()) <= 100 * 1000 } />
|
<Message message={message} />
|
||||||
{/each}
|
{/each}
|
||||||
<div bind:this={ scrollAnchor } />
|
<div bind:this={ scrollAnchor } />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -141,12 +141,39 @@ class MessageStore extends Store {
|
||||||
this.didDoInitialLoad = false;
|
this.didDoInitialLoad = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_recomputeMessages() {
|
||||||
|
this.value = this.value.map((e, i) => this._processMessage(e, this.value[i - 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
_processMessage(message, previous=null) {
|
||||||
|
message._createdAtDate = new Date(parseInt(message.created_at));
|
||||||
|
message._createdAtTimeString = new Intl.DateTimeFormat(getItem("ui:locale"), { hour: "numeric", minute: "numeric" }).format(message._createdAtDate);
|
||||||
|
message._createdAtDateString = message._createdAtDate.toLocaleDateString();
|
||||||
|
message._mentions = false;
|
||||||
|
message._editable = false;
|
||||||
|
message._clumped = false;
|
||||||
|
message._showDateMarkerAbove = false;
|
||||||
|
if (userInfoStore.value && message.content.includes("@" + userInfoStore.value.username)) {
|
||||||
|
message._mentions = true;
|
||||||
|
}
|
||||||
|
if (userInfoStore.value && (message.author_id === userInfoStore.value.id || userInfoStore.value.is_superuser)) {
|
||||||
|
message._editable = true;
|
||||||
|
}
|
||||||
|
if (previous && (message._createdAtDate.getTime() - previous._createdAtDate.getTime()) <= 100 * 1000) {
|
||||||
|
message._clumped = true;
|
||||||
|
}
|
||||||
|
if (previous && (previous._createdAtDateString !== message._createdAtDateString)) {
|
||||||
|
message._showDateMarkerAbove = true;
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
setMessage(id, message) {
|
setMessage(id, message) {
|
||||||
const index = this.value.findIndex(e => e.id === id);
|
const index = this.value.findIndex(e => e.id === id);
|
||||||
if (index === -1)
|
if (index === -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.value[index] = message;
|
this.value[index] = this._processMessage(message, this.value[index - 1]);
|
||||||
|
|
||||||
this.updated();
|
this.updated();
|
||||||
}
|
}
|
||||||
|
@ -155,13 +182,13 @@ class MessageStore extends Store {
|
||||||
if (message.optimistic_id) {
|
if (message.optimistic_id) {
|
||||||
const index = this.value.findIndex(e => e.id === message.optimistic_id);
|
const index = this.value.findIndex(e => e.id === message.optimistic_id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.value[index] = message;
|
this.value[index] = this._processMessage(message, this.value[index - 1]);
|
||||||
this.updated();
|
this.updated();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.value.push(message);
|
this.value.push(this._processMessage(message, this.value[this.value.length - 1]));
|
||||||
// only dispatch update if collectOldMessages didn't
|
// only dispatch update if collectOldMessages didn't
|
||||||
if (!this.collectOldMessages()) {
|
if (!this.collectOldMessages()) {
|
||||||
this.updated();
|
this.updated();
|
||||||
|
@ -183,7 +210,7 @@ class MessageStore extends Store {
|
||||||
if (index === -1)
|
if (index === -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.value[index] = message;
|
this.value[index] = this._processMessage(message, this.value[index - 1]);
|
||||||
|
|
||||||
this.updated();
|
this.updated();
|
||||||
}
|
}
|
||||||
|
@ -194,6 +221,7 @@ class MessageStore extends Store {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.value.splice(index, 1);
|
this.value.splice(index, 1);
|
||||||
|
this._recomputeMessages();
|
||||||
|
|
||||||
this.updated();
|
this.updated();
|
||||||
}
|
}
|
||||||
|
@ -231,6 +259,11 @@ class MessageStore extends Store {
|
||||||
if (beforeCommitToStore)
|
if (beforeCommitToStore)
|
||||||
beforeCommitToStore(res.json);
|
beforeCommitToStore(res.json);
|
||||||
res.json.reverse();
|
res.json.reverse();
|
||||||
|
for (let i = 0; i < res.json.length; i++) {
|
||||||
|
const message = res.json[i];
|
||||||
|
const previous = res.json[i - 1];
|
||||||
|
res.json[i] = this._processMessage(message, previous);
|
||||||
|
}
|
||||||
this.value = res.json.concat(this.value);
|
this.value = res.json.concat(this.value);
|
||||||
this.updated();
|
this.updated();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export const getMessageById = "SELECT messages.id, messages.content, messages.channel_id, messages.created_at, messages.author_id, users.username AS author_username FROM messages JOIN users ON messages.author_id = users.id WHERE messages.id = $1";
|
export const getMessageById = "SELECT messages.id, messages.content, messages.channel_id, messages.created_at, messages.author_id, users.username AS author_username FROM messages JOIN users ON messages.author_id = users.id WHERE messages.id = $1";
|
||||||
export const getMessagesByChannelFirstPage = "SELECT messages.id, messages.content, messages.channel_id, messages.created_at, messages.author_id, users.username AS author_username FROM messages JOIN users ON messages.author_id = users.id WHERE messages.channel_id = $1 ORDER BY id DESC LIMIT 50";
|
export const getMessagesByChannelFirstPage = (limit: number) => `SELECT messages.id, messages.content, messages.channel_id, messages.created_at, messages.author_id, users.username AS author_username FROM messages JOIN users ON messages.author_id = users.id WHERE messages.channel_id = $1 ORDER BY id DESC LIMIT ${limit}`;
|
||||||
export const getMessagesByChannelPage = "SELECT messages.id, messages.content, messages.channel_id, messages.created_at, messages.author_id, users.username AS author_username FROM messages JOIN users ON messages.author_id = users.id WHERE messages.id < $1 AND messages.channel_id = $2 ORDER BY id DESC LIMIT 50";
|
export const getMessagesByChannelPage = (limit: number) => `SELECT messages.id, messages.content, messages.channel_id, messages.created_at, messages.author_id, users.username AS author_username FROM messages JOIN users ON messages.author_id = users.id WHERE messages.id < $1 AND messages.channel_id = $2 ORDER BY id DESC LIMIT ${limit}`;
|
||||||
|
|
|
@ -228,22 +228,29 @@ router.get(
|
||||||
"/:id/messages",
|
"/:id/messages",
|
||||||
authenticateRoute(),
|
authenticateRoute(),
|
||||||
param("id").isNumeric(),
|
param("id").isNumeric(),
|
||||||
|
param("count").optional().isInt({ min: 10, max: 50 }),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const validationErrors = validationResult(req);
|
const validationErrors = validationResult(req);
|
||||||
if (!validationErrors.isEmpty()) {
|
if (!validationErrors.isEmpty()) {
|
||||||
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
|
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { before } = req.query;
|
const { before, count } = req.query;
|
||||||
|
|
||||||
|
let limit = typeof count === "string" ? parseInt(count || "25") : 25;
|
||||||
|
if (limit === NaN) {
|
||||||
|
return res.status(400).json({ ...errors.INVALID_DATA });
|
||||||
|
}
|
||||||
|
|
||||||
const channelId = parseInt(req.params.id);
|
const channelId = parseInt(req.params.id);
|
||||||
|
|
||||||
let finalRows = [];
|
let finalRows = [];
|
||||||
|
|
||||||
if (before) {
|
if (before) {
|
||||||
const result = await query(getMessagesByChannelPage, [before, channelId]);
|
const result = await query(getMessagesByChannelPage(limit), [before, channelId]);
|
||||||
finalRows = result ? result.rows : [];
|
finalRows = result ? result.rows : [];
|
||||||
} else {
|
} else {
|
||||||
const result = await query(getMessagesByChannelFirstPage, [channelId]);
|
const result = await query(getMessagesByChannelFirstPage(limit), [channelId]);
|
||||||
finalRows = result ? result.rows : [];
|
finalRows = result ? result.rows : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue