eslint autofix
This commit is contained in:
@@ -72,6 +72,7 @@ export default defineConfig([
|
||||
{
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@/no-trailing-spaces": "warn",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
|
||||
+3
-3
@@ -18,9 +18,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<AuthGate
|
||||
reconnect={handleReconnect}
|
||||
{showServerSettings}
|
||||
<AuthGate
|
||||
reconnect={handleReconnect}
|
||||
{showServerSettings}
|
||||
onToggleServerSettings={handleToggleServerSettings}
|
||||
{reconnectKey}
|
||||
>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<i class="fas fa-circle-notch fa-spin" style="font-size: 3rem; color: var(--brand); margin-bottom: 20px;"></i>
|
||||
<h1>Connecting to SpacetimeDB...</h1>
|
||||
<p style="color: var(--text-muted); margin-top: 8px; margin-bottom: 24px;">Establishing a secure connection to the chat server.</p>
|
||||
|
||||
|
||||
<div style="background-color: var(--background-tertiary); padding: 16px; border-radius: 8px; margin-bottom: 24px; text-align: left; font-size: 0.8rem; border: 1px solid var(--background-modifier-accent); display: flex; flex-direction: column; gap: 12px;">
|
||||
<div class="connection-detail">
|
||||
<div style="color: var(--text-muted); font-weight: 800; text-transform: uppercase; font-size: 0.65rem; margin-bottom: 4px; letter-spacing: 0.05em;">Host</div>
|
||||
@@ -41,7 +41,7 @@
|
||||
</div>
|
||||
|
||||
{#if onCancel}
|
||||
<button
|
||||
<button
|
||||
onclick={onCancel}
|
||||
class="btn-secondary"
|
||||
style="width: 100%;"
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</div>
|
||||
{:else if shouldShowContent}
|
||||
{#key reconnectKey}
|
||||
<SpacetimeProvider
|
||||
<SpacetimeProvider
|
||||
onReconnectTrigger={reconnect}
|
||||
onCancel={() => {
|
||||
userWantsToConnect = false;
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import type { ChatService } from "../services/chat.svelte";
|
||||
import type * as Types from "../../module_bindings/types";
|
||||
|
||||
let {
|
||||
user,
|
||||
size = "medium",
|
||||
let {
|
||||
user,
|
||||
size = "medium",
|
||||
class: className = "",
|
||||
isTalking = false
|
||||
}: {
|
||||
user: Types.User | null | undefined,
|
||||
isTalking = false
|
||||
}: {
|
||||
user: Types.User | null | undefined,
|
||||
size?: "tiny" | "small" | "medium" | "large",
|
||||
class?: string,
|
||||
isTalking?: boolean
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const saved = localStorage.getItem("recent_emojis");
|
||||
if (saved) {
|
||||
try {
|
||||
recentEmojis = JSON.parse(saved).map((id: any) =>
|
||||
recentEmojis = JSON.parse(saved).map((id: any) =>
|
||||
typeof id === 'string' && id.startsWith('custom:') ? BigInt(id.split(':')[1]) : id
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -32,20 +32,20 @@
|
||||
let newRecent = [emoji, ...recentEmojis.filter(e => e !== emoji)];
|
||||
newRecent = newRecent.slice(0, 7);
|
||||
recentEmojis = newRecent;
|
||||
localStorage.setItem("recent_emojis", JSON.stringify(newRecent.map(e =>
|
||||
localStorage.setItem("recent_emojis", JSON.stringify(newRecent.map(e =>
|
||||
typeof e === 'bigint' ? `custom:${e.toString()}` : e
|
||||
)));
|
||||
}
|
||||
|
||||
const filteredEmojis = $derived.by(() => {
|
||||
const lowerSearch = searchTerm.toLowerCase();
|
||||
|
||||
const standard = EMOJIS.filter(e =>
|
||||
e.name.toLowerCase().includes(lowerSearch) ||
|
||||
|
||||
const standard = EMOJIS.filter(e =>
|
||||
e.name.toLowerCase().includes(lowerSearch) ||
|
||||
e.char.includes(lowerSearch)
|
||||
);
|
||||
|
||||
const custom = chat.customEmojis.filter(e =>
|
||||
const custom = chat.customEmojis.filter(e =>
|
||||
e.name.toLowerCase().includes(lowerSearch)
|
||||
);
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
{#each categories as cat}
|
||||
{@const catStandard = EMOJIS.filter(e => e.category === cat.id)}
|
||||
{@const catCustom = cat.id === 'custom' ? chat.customEmojis : []}
|
||||
|
||||
|
||||
{#if catStandard.length > 0 || catCustom.length > 0}
|
||||
<div class="picker-section" id="cat-{cat.id}">
|
||||
<div class="section-title">{cat.name}</div>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import type { ChatService } from "../services/chat.svelte";
|
||||
import { optimizeImage } from "../utils";
|
||||
|
||||
let {
|
||||
activeChannelId,
|
||||
activeThreadId,
|
||||
isFullyAuthenticated
|
||||
let {
|
||||
activeChannelId,
|
||||
activeThreadId,
|
||||
isFullyAuthenticated
|
||||
} = $props<{
|
||||
activeChannelId: bigint | null,
|
||||
activeThreadId: bigint | null,
|
||||
@@ -27,7 +27,7 @@
|
||||
// We only care about messageText changes here
|
||||
const text = messageText;
|
||||
const channelId = activeChannelId;
|
||||
|
||||
|
||||
if (text.trim().length > 0 && channelId) {
|
||||
// Start typing if not already
|
||||
if (!untrack(() => isTyping)) {
|
||||
@@ -91,7 +91,7 @@
|
||||
// Fallback to raw if optimization fails
|
||||
const buffer = await file.arrayBuffer();
|
||||
const data = new Uint8Array(buffer);
|
||||
|
||||
|
||||
let fileName = file.name || "image.png";
|
||||
if (fileName === "image.png" || fileName === "blob") {
|
||||
const now = new Date();
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
const parts = tooltip.targetKey.split(":");
|
||||
const msgId = BigInt(parts[0]);
|
||||
const emojiKey = parts.slice(1).join(":");
|
||||
const exists = chat.messageReactions.some(r =>
|
||||
const exists = chat.messageReactions.some(r =>
|
||||
r.messageId === msgId && (r.emoji || `custom:${r.customEmojiId}`) === emojiKey
|
||||
);
|
||||
if (!exists) {
|
||||
@@ -51,7 +51,7 @@
|
||||
const maxShow = 3;
|
||||
const displayed = usernames.slice(0, maxShow);
|
||||
const remaining = usernames.length - maxShow;
|
||||
|
||||
|
||||
let emojiName = "";
|
||||
if (group[0].emoji) {
|
||||
emojiName = EMOJIS.find(e => e.char === group[0].emoji)?.name || "emoji";
|
||||
@@ -215,12 +215,12 @@
|
||||
<i class="far fa-smile"></i>
|
||||
</button>
|
||||
{#if activePickerMessageId === msg.id && pickerPos}
|
||||
<div
|
||||
<div
|
||||
use:portal
|
||||
class="emoji-picker-popover"
|
||||
class="emoji-picker-popover"
|
||||
style="position: fixed; left: {pickerPos.x}px; top: {pickerPos.y}px; transform: translateX(-100%) {pickerPos.placement === 'top' ? 'translateY(-100%)' : ''}; z-index: 1000;"
|
||||
>
|
||||
<EmojiPicker
|
||||
<EmojiPicker
|
||||
onSelect={(emoji, customId) => {
|
||||
chat.handleToggleReaction(msg.id, emoji, customId);
|
||||
activePickerMessageId = null;
|
||||
@@ -313,7 +313,7 @@
|
||||
{@const hasReacted = group.some(r => r.identity.isEqual(chat.identity))}
|
||||
{@const emoji = group[0].emoji}
|
||||
{@const customEmojiId = group[0].customEmojiId}
|
||||
<button
|
||||
<button
|
||||
class="reaction-badge {hasReacted ? 'active' : ''}"
|
||||
onclick={() => {
|
||||
chat.handleToggleReaction(msg.id, emoji, customEmojiId);
|
||||
@@ -354,7 +354,7 @@
|
||||
</div>
|
||||
|
||||
{#if tooltip.visible}
|
||||
<div
|
||||
<div
|
||||
use:portal
|
||||
class="message-reaction-tooltip"
|
||||
style="left: {tooltip.x}px; top: {tooltip.y}px;"
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
// User removed their avatar
|
||||
chat.handleSetAvatar(undefined);
|
||||
}
|
||||
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -126,8 +126,8 @@
|
||||
<div class="settings-sidebar">
|
||||
<div class="sidebar-header">User Settings</div>
|
||||
{#each categories as cat}
|
||||
<button
|
||||
class="sidebar-item {activeCategory === cat.id ? 'active' : ''}"
|
||||
<button
|
||||
class="sidebar-item {activeCategory === cat.id ? 'active' : ''}"
|
||||
onclick={() => activeCategory = cat.id}
|
||||
>
|
||||
<i class={cat.icon}></i>
|
||||
@@ -192,7 +192,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="avatar-actions">
|
||||
<button class="btn-secondary small" onclick={() => {
|
||||
avatarPreview = null;
|
||||
@@ -232,9 +232,9 @@
|
||||
<input type="file" accept="image/*" onchange={onEmojiFileChange} style="display: none;" />
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-success"
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-success"
|
||||
style="align-self: flex-end; height: 38px; padding: 0 20px;"
|
||||
disabled={!newEmojiFile || !newEmojiName.trim() || isEmojiUploading}
|
||||
>
|
||||
@@ -277,23 +277,23 @@
|
||||
<div class="voice-sensitivity-box">
|
||||
<div class="label-with-action">
|
||||
<label for="voice-threshold-slider">Input Sensitivity</label>
|
||||
<button
|
||||
class="test-mic-btn {webrtc.isTestingMic ? 'active' : ''}"
|
||||
<button
|
||||
class="test-mic-btn {webrtc.isTestingMic ? 'active' : ''}"
|
||||
onclick={() => webrtc.toggleMicTest()}
|
||||
>
|
||||
<i class="fas {webrtc.isTestingMic ? 'fa-stop' : 'fa-microphone'}"></i>
|
||||
{webrtc.isTestingMic ? "Stop Testing" : "Test Mic"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="voice-meter-container">
|
||||
<div class="voice-meter-bg">
|
||||
<div
|
||||
class="voice-meter-fill"
|
||||
<div
|
||||
class="voice-meter-fill"
|
||||
style="width: {normalizedLevel}%; background-color: {webrtc.currentInputLevel > webrtc.voiceThreshold ? 'var(--status-positive)' : 'var(--brand)'}; opacity: {webrtc.currentInputLevel > 0 ? 1 : 0};"
|
||||
></div>
|
||||
<div
|
||||
class="voice-threshold-line"
|
||||
<div
|
||||
class="voice-threshold-line"
|
||||
style="left: {normalizedThreshold}%;"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
$effect(() => {
|
||||
const text = threadMessageText;
|
||||
const channelId = activeChannelId;
|
||||
|
||||
|
||||
if (text.trim().length > 0 && channelId) {
|
||||
// Start typing if not already
|
||||
if (!untrack(() => isTyping)) {
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
const parts = tooltip.targetKey.split(":");
|
||||
const msgId = BigInt(parts[0]);
|
||||
const emojiKey = parts.slice(1).join(":");
|
||||
const exists = chat.messageReactions.some(r =>
|
||||
const exists = chat.messageReactions.some(r =>
|
||||
r.messageId === msgId && (r.emoji || `custom:${r.customEmojiId}`) === emojiKey
|
||||
);
|
||||
if (!exists) {
|
||||
@@ -62,7 +62,7 @@
|
||||
const maxShow = 3;
|
||||
const displayed = usernames.slice(0, maxShow);
|
||||
const remaining = usernames.length - maxShow;
|
||||
|
||||
|
||||
let emojiName = "";
|
||||
if (group[0].emoji) {
|
||||
emojiName = EMOJIS.find(e => e.char === group[0].emoji)?.name || "emoji";
|
||||
@@ -299,12 +299,12 @@ $effect(() => {
|
||||
<i class="far fa-smile"></i>
|
||||
</button>
|
||||
{#if activePickerMessageId === msg.id && pickerPos}
|
||||
<div
|
||||
<div
|
||||
use:portal
|
||||
class="emoji-picker-popover"
|
||||
class="emoji-picker-popover"
|
||||
style="position: fixed; left: {pickerPos.x}px; top: {pickerPos.y}px; transform: {pickerPos.placement === 'top' ? 'translateY(-100%)' : ''}; z-index: 1000;"
|
||||
>
|
||||
<EmojiPicker
|
||||
<EmojiPicker
|
||||
onSelect={(emoji, customId) => {
|
||||
chat.handleToggleReaction(msg.id, emoji, customId);
|
||||
activePickerMessageId = null;
|
||||
@@ -327,7 +327,7 @@ $effect(() => {
|
||||
</div>
|
||||
|
||||
{#if tooltip.visible}
|
||||
<div
|
||||
<div
|
||||
use:portal
|
||||
class="message-reaction-tooltip"
|
||||
style="left: {tooltip.x}px; top: {tooltip.y}px;"
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
import { getContext } from "svelte";
|
||||
import type { ChatService } from "../services/chat.svelte";
|
||||
|
||||
let {
|
||||
activeThreadId,
|
||||
setActiveThreadId,
|
||||
let {
|
||||
activeThreadId,
|
||||
setActiveThreadId,
|
||||
pendingThreadParentMessageId,
|
||||
setPendingThreadParentMessageId,
|
||||
activeChannelId,
|
||||
activeServer,
|
||||
isFullyAuthenticated,
|
||||
users,
|
||||
identity,
|
||||
allThreads,
|
||||
activeChannelId,
|
||||
activeServer,
|
||||
isFullyAuthenticated,
|
||||
users,
|
||||
identity,
|
||||
allThreads,
|
||||
allMessages,
|
||||
allImages
|
||||
}: {
|
||||
@@ -80,8 +80,8 @@
|
||||
{threadName}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="close-btn"
|
||||
<button
|
||||
class="close-btn"
|
||||
onclick={handleClose}
|
||||
aria-label="Close thread view"
|
||||
>
|
||||
@@ -106,7 +106,7 @@
|
||||
{threadMessages}
|
||||
{users}
|
||||
{identity}
|
||||
images={allImages}
|
||||
images={allImages}
|
||||
/>
|
||||
|
||||
<div class="typing-indicator" style="margin-left: 12px; margin-top: 4px;">
|
||||
|
||||
@@ -43,7 +43,7 @@ export class ChatService {
|
||||
for (const user of usersWithAvatars) {
|
||||
const avatarId = user.avatarId!;
|
||||
const idStr = avatarId.toString();
|
||||
|
||||
|
||||
// Skip if already in memory
|
||||
if (this.#avatarUrls.has(idStr)) continue;
|
||||
|
||||
@@ -55,7 +55,7 @@ export class ChatService {
|
||||
|
||||
async #loadAvatar(id: bigint) {
|
||||
const idStr = id.toString();
|
||||
|
||||
|
||||
// 1. Check persistent cache
|
||||
const cachedUrl = await imageCache.get(id);
|
||||
if (cachedUrl) {
|
||||
@@ -409,13 +409,13 @@ export class ChatService {
|
||||
if (!this.activeChannelId) return [];
|
||||
const myId = this.identity;
|
||||
const activeChannelId = this.activeChannelId;
|
||||
|
||||
|
||||
return this.typingActivity
|
||||
.filter((ta) => {
|
||||
const isSameChannel = ta.channelId === activeChannelId;
|
||||
const isNotMe = myId ? !ta.identity.isEqual(myId) : true;
|
||||
const currentlyTyping = ta.isTyping;
|
||||
|
||||
|
||||
return isSameChannel && isNotMe && currentlyTyping;
|
||||
})
|
||||
.map((ta) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ export class ImageCacheService {
|
||||
|
||||
async get(id: bigint): Promise<string | null> {
|
||||
const idStr = id.toString();
|
||||
|
||||
|
||||
// 1. Check in-memory cache
|
||||
if (this.#inMemoryCache.has(idStr)) {
|
||||
return this.#inMemoryCache.get(idStr)!;
|
||||
@@ -33,7 +33,7 @@ export class ImageCacheService {
|
||||
|
||||
// 2. Check IndexedDB
|
||||
if (!this.#db) await this.#initDB();
|
||||
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const transaction = this.#db!.transaction("images", "readonly");
|
||||
const store = transaction.objectStore("images");
|
||||
@@ -63,7 +63,7 @@ export class ImageCacheService {
|
||||
|
||||
// Save to IndexedDB
|
||||
if (!this.#db) await this.#initDB();
|
||||
|
||||
|
||||
const transaction = this.#db!.transaction("images", "readwrite");
|
||||
const store = transaction.objectStore("images");
|
||||
store.put({ blob }, idStr);
|
||||
@@ -75,7 +75,7 @@ export class ImageCacheService {
|
||||
async getBlob(id: bigint): Promise<Blob | null> {
|
||||
const idStr = id.toString();
|
||||
if (!this.#db) await this.#initDB();
|
||||
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const transaction = this.#db!.transaction("images", "readonly");
|
||||
const store = transaction.objectStore("images");
|
||||
|
||||
@@ -94,16 +94,16 @@ export class MessagingService {
|
||||
// 4. Surgical Image & Reaction Sync
|
||||
// We only sync images and reactions for messages that are currently in our server-scoped view
|
||||
// This uses the index on message_id for both tables
|
||||
const visibleMsgSubquery = threadId
|
||||
const visibleMsgSubquery = threadId
|
||||
? `(SELECT id FROM message WHERE thread_id = ${threadId} OR id = (SELECT parent_message_id FROM thread WHERE id = ${threadId}))`
|
||||
: `(SELECT id FROM message WHERE channel_id = ${channelId} AND thread_id IS NULL ORDER BY sent DESC LIMIT ${limit})`;
|
||||
|
||||
queries.push(`SELECT * FROM message_image WHERE message_id IN ${visibleMsgSubquery}`);
|
||||
queries.push(`SELECT * FROM message_reaction WHERE message_id IN ${visibleMsgSubquery}`);
|
||||
|
||||
|
||||
// Image Sync: Message Images + User Avatars
|
||||
const userSubquery = `(SELECT identity FROM user WHERE online = true OR identity IN (SELECT identity FROM server_member WHERE server_id = ${serverId}) OR identity IN (SELECT sender FROM message WHERE id IN ${visibleMsgSubquery}))`;
|
||||
|
||||
|
||||
queries.push(`
|
||||
SELECT * FROM image WHERE
|
||||
id IN (SELECT image_id FROM message_image WHERE message_id IN ${visibleMsgSubquery}) OR
|
||||
@@ -114,7 +114,7 @@ export class MessagingService {
|
||||
// Only pull users who are: Online OR Members of this server OR Senders/Reactors/Typers of visible messages
|
||||
const reactorSubquery = `(SELECT identity FROM message_reaction WHERE message_id IN ${visibleMsgSubquery})`;
|
||||
const typerSubquery = `(SELECT identity FROM typing_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId}))`;
|
||||
|
||||
|
||||
queries.push(`
|
||||
SELECT * FROM user WHERE
|
||||
online = true OR
|
||||
|
||||
@@ -13,7 +13,7 @@ export class UIService {
|
||||
showDiscoveryModal = $state(false);
|
||||
authError = $state("");
|
||||
viewingImageId = $state<bigint | null>(null);
|
||||
|
||||
|
||||
// Track collapsed state of embeds by key: `${messageId}-${index}`
|
||||
embedCollapsedStates = new SvelteMap<string, boolean>();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user