fix: resolve all ESLint errors for CI
- Add MinimizableNode interface export to minimizable.ts - Fix each block keys in multiple components - Fix unused imports and variables - Replace `any` types with proper TypeScript types - Add eslint-disable comments for intentional @html usage - Update ESLint config to handle .svelte.ts files - Fix useless escape sequences in regex patterns - Use $derived instead of $effect for simple derivations
- commit
- c40b6f524e97ded62aa8e4b4942c7141c8293eb3
- author
- Kit · · 2025-12-15 07:50 UTC
- parent
- 676e4cd
- signature
- SSH Unverified
SHA256:3RGv7q1rcg0JUU2CjMAhwZcyGvcCDmFuxQQT8Z18wLA· arch-ssh This key is not trusted for the committer email
20 files · +86 -122
M eslint.config.js +10 -0
| @@ -30,5 +30,15 @@ export default ts.config( | |||
| 30 | 30 | parser: ts.parser | |
| 31 | 31 | } | |
| 32 | 32 | } | |
| 33 | + | }, | |
| 34 | + | { | |
| 35 | + | // Svelte 5 .svelte.ts files use runes - need svelte-eslint-parser | |
| 36 | + | files: ['**/*.svelte.ts', '**/*.svelte.js'], | |
| 37 | + | languageOptions: { | |
| 38 | + | parser: svelte.parser, | |
| 39 | + | parserOptions: { | |
| 40 | + | parser: ts.parser | |
| 41 | + | } | |
| 42 | + | } | |
| 33 | 43 | } | |
| 34 | 44 | ); | |
| @@ -30,5 +30,15 @@ export default ts.config( | |||||
| 30 | parser: ts.parser | 30 | parser: ts.parser | ||
| 31 | } | 31 | } | ||
| 32 | } | 32 | } | ||
| 33 | + | }, | |||
| 34 | + | { | |||
| 35 | + | // Svelte 5 .svelte.ts files use runes - need svelte-eslint-parser | |||
| 36 | + | files: ['**/*.svelte.ts', '**/*.svelte.js'], | |||
| 37 | + | languageOptions: { | |||
| 38 | + | parser: svelte.parser, | |||
| 39 | + | parserOptions: { | |||
| 40 | + | parser: ts.parser | |||
| 41 | + | } | |||
| 42 | + | } | |||
| 33 | } | 43 | } | ||
| 34 | ); | 44 | ); | ||
M src/lib/actions/minimizable.ts +7 -0
| @@ -5,6 +5,13 @@ interface MinimizableParameters { | |||
| 5 | 5 | onMinimize?: (isMinimized: boolean) => void; | |
| 6 | 6 | } | |
| 7 | 7 | ||
| 8 | + | export interface MinimizableNode extends HTMLDivElement { | |
| 9 | + | minimize: () => void; | |
| 10 | + | restore: () => void; | |
| 11 | + | toggleMinimize: () => void; | |
| 12 | + | isMinimized: () => boolean; | |
| 13 | + | } | |
| 14 | + | ||
| 8 | 15 | interface MinimizableAttributes { | |
| 9 | 16 | onminimize?: (e: CustomEvent<{ isMinimized: boolean }>) => void; | |
| 10 | 17 | } | |
| @@ -5,6 +5,13 @@ interface MinimizableParameters { | |||||
| 5 | onMinimize?: (isMinimized: boolean) => void; | 5 | onMinimize?: (isMinimized: boolean) => void; | ||
| 6 | } | 6 | } | ||
| 7 | 7 | ||||
| 8 | + | export interface MinimizableNode extends HTMLDivElement { | |||
| 9 | + | minimize: () => void; | |||
| 10 | + | restore: () => void; | |||
| 11 | + | toggleMinimize: () => void; | |||
| 12 | + | isMinimized: () => boolean; | |||
| 13 | + | } | |||
| 14 | + | ||||
| 8 | interface MinimizableAttributes { | 15 | interface MinimizableAttributes { | ||
| 9 | onminimize?: (e: CustomEvent<{ isMinimized: boolean }>) => void; | 16 | onminimize?: (e: CustomEvent<{ isMinimized: boolean }>) => void; | ||
| 10 | } | 17 | } | ||
M src/lib/components/aim-login.svelte +11 -12
| @@ -2,7 +2,6 @@ | |||
| 2 | 2 | <script lang="ts"> | |
| 3 | 3 | import { draggable } from '$lib/actions/draggable'; | |
| 4 | 4 | import type { RegisterResponse, RegisterResponseError } from '$lib/types/payloads'; | |
| 5 | - | import type { User, SafeUser } from '$lib/types/chat'; | |
| 6 | 5 | import { invalidateAll } from '$app/navigation'; | |
| 7 | 6 | import { chatState } from '$lib/states/chat.svelte'; | |
| 8 | 7 | import { createSafeUser } from '$lib/types/chat'; | |
| @@ -11,16 +10,10 @@ | |||
| 11 | 10 | import { | |
| 12 | 11 | loginSchema, | |
| 13 | 12 | createRegistrationSchema, | |
| 14 | - | validatePasswordStrength, | |
| 15 | 13 | DEFAULT_PASSWORD_CONSTRAINTS, | |
| 16 | 14 | type PasswordConstraints | |
| 17 | 15 | } from '../validation/password'; | |
| 18 | - | ||
| 19 | - | interface $$Props { | |
| 20 | - | showAuth: boolean; | |
| 21 | - | onLoginSuccess?: (user: SafeUser | null) => void; | |
| 22 | - | activeTab?: 'signin' | 'signup'; | |
| 23 | - | } | |
| 16 | + | import { z } from 'zod'; | |
| 24 | 17 | ||
| 25 | 18 | let { showAuth = $bindable(), onLoginSuccess, activeTab: initialTab = 'signin' } = $props(); | |
| 26 | 19 | ||
| @@ -128,8 +121,11 @@ | |||
| 128 | 121 | try { | |
| 129 | 122 | loginSchema.parse({ username: siUsername.trim(), password: siPassword.trim() }); | |
| 130 | 123 | return null; | |
| 131 | - | } catch (err: any) { | |
| 132 | - | return err.errors?.[0]?.message || 'Invalid input'; | |
| 124 | + | } catch (err) { | |
| 125 | + | if (err instanceof z.ZodError) { | |
| 126 | + | return err.issues?.[0]?.message || 'Invalid input'; | |
| 127 | + | } | |
| 128 | + | return 'Invalid input'; | |
| 133 | 129 | } | |
| 134 | 130 | } | |
| 135 | 131 | ||
| @@ -144,8 +140,11 @@ | |||
| 144 | 140 | turnstileToken: suTurnstileToken | |
| 145 | 141 | }); | |
| 146 | 142 | return null; | |
| 147 | - | } catch (err: any) { | |
| 148 | - | return err.errors?.[0]?.message || 'Invalid input'; | |
| 143 | + | } catch (err) { | |
| 144 | + | if (err instanceof z.ZodError) { | |
| 145 | + | return err.issues?.[0]?.message || 'Invalid input'; | |
| 146 | + | } | |
| 147 | + | return 'Invalid input'; | |
| 149 | 148 | } | |
| 150 | 149 | } | |
| 151 | 150 | ||
| @@ -2,7 +2,6 @@ | |||||
| 2 | <script lang="ts"> | 2 | <script lang="ts"> | ||
| 3 | import { draggable } from '$lib/actions/draggable'; | 3 | import { draggable } from '$lib/actions/draggable'; | ||
| 4 | import type { RegisterResponse, RegisterResponseError } from '$lib/types/payloads'; | 4 | import type { RegisterResponse, RegisterResponseError } from '$lib/types/payloads'; | ||
| 5 | - | import type { User, SafeUser } from '$lib/types/chat'; | |||
| 6 | import { invalidateAll } from '$app/navigation'; | 5 | import { invalidateAll } from '$app/navigation'; | ||
| 7 | import { chatState } from '$lib/states/chat.svelte'; | 6 | import { chatState } from '$lib/states/chat.svelte'; | ||
| 8 | import { createSafeUser } from '$lib/types/chat'; | 7 | import { createSafeUser } from '$lib/types/chat'; | ||
| @@ -11,16 +10,10 @@ | |||||
| 11 | import { | 10 | import { | ||
| 12 | loginSchema, | 11 | loginSchema, | ||
| 13 | createRegistrationSchema, | 12 | createRegistrationSchema, | ||
| 14 | - | validatePasswordStrength, | |||
| 15 | DEFAULT_PASSWORD_CONSTRAINTS, | 13 | DEFAULT_PASSWORD_CONSTRAINTS, | ||
| 16 | type PasswordConstraints | 14 | type PasswordConstraints | ||
| 17 | } from '../validation/password'; | 15 | } from '../validation/password'; | ||
| 18 | - | 16 | + | import { z } from 'zod'; | |
| 19 | - | interface $$Props { | |||
| 20 | - | showAuth: boolean; | |||
| 21 | - | onLoginSuccess?: (user: SafeUser | null) => void; | |||
| 22 | - | activeTab?: 'signin' | 'signup'; | |||
| 23 | - | } | |||
| 24 | 17 | ||||
| 25 | let { showAuth = $bindable(), onLoginSuccess, activeTab: initialTab = 'signin' } = $props(); | 18 | let { showAuth = $bindable(), onLoginSuccess, activeTab: initialTab = 'signin' } = $props(); | ||
| 26 | 19 | ||||
| @@ -128,8 +121,11 @@ | |||||
| 128 | try { | 121 | try { | ||
| 129 | loginSchema.parse({ username: siUsername.trim(), password: siPassword.trim() }); | 122 | loginSchema.parse({ username: siUsername.trim(), password: siPassword.trim() }); | ||
| 130 | return null; | 123 | return null; | ||
| 131 | - | } catch (err: any) { | 124 | + | } catch (err) { |
| 132 | - | return err.errors?.[0]?.message || 'Invalid input'; | 125 | + | if (err instanceof z.ZodError) { |
| 126 | + | return err.issues?.[0]?.message || 'Invalid input'; | |||
| 127 | + | } | |||
| 128 | + | return 'Invalid input'; | |||
| 133 | } | 129 | } | ||
| 134 | } | 130 | } | ||
| 135 | 131 | ||||
| @@ -144,8 +140,11 @@ | |||||
| 144 | turnstileToken: suTurnstileToken | 140 | turnstileToken: suTurnstileToken | ||
| 145 | }); | 141 | }); | ||
| 146 | return null; | 142 | return null; | ||
| 147 | - | } catch (err: any) { | 143 | + | } catch (err) { |
| 148 | - | return err.errors?.[0]?.message || 'Invalid input'; | 144 | + | if (err instanceof z.ZodError) { |
| 145 | + | return err.issues?.[0]?.message || 'Invalid input'; | |||
| 146 | + | } | |||
| 147 | + | return 'Invalid input'; | |||
| 149 | } | 148 | } | ||
| 150 | } | 149 | } | ||
| 151 | 150 | ||||
M src/lib/components/chat-room.svelte +10 -47
| @@ -1,19 +1,12 @@ | |||
| 1 | 1 | <script lang="ts"> | |
| 2 | 2 | import { chatState } from '../states/chat.svelte'; | |
| 3 | - | import type { | |
| 4 | - | MessageType, | |
| 5 | - | User, | |
| 6 | - | ChatRoom, | |
| 7 | - | Message, | |
| 8 | - | EnrichedMessage, | |
| 9 | - | SafeUser | |
| 10 | - | } from '../types/chat'; | |
| 3 | + | import type { User, Message, EnrichedMessage, SafeUser } from '../types/chat'; | |
| 11 | 4 | import { onMount } from 'svelte'; | |
| 12 | 5 | import { browser } from '$app/environment'; | |
| 13 | 6 | import { draggable } from '$lib/actions/draggable'; | |
| 14 | 7 | import { resizable } from '$lib/actions/resizable'; | |
| 15 | 8 | import { maximizable } from '$lib/actions/maximizable'; | |
| 16 | - | import { minimizable } from '$lib/actions/minimizable'; | |
| 9 | + | import { minimizable, type MinimizableNode } from '$lib/actions/minimizable'; | |
| 17 | 10 | import LoadingButton from './ui/button-loading.svelte'; | |
| 18 | 11 | import Tooltip from './ui/tooltip.svelte'; | |
| 19 | 12 | import LoadingDots from './ui/loading-dots.svelte'; | |
| @@ -49,7 +42,6 @@ | |||
| 49 | 42 | ...initialTextStyle, | |
| 50 | 43 | color: initialTextStyle.color || '#000000' // Ensure we always have a valid color | |
| 51 | 44 | }); | |
| 52 | - | let showFormattingToolbar = $state(true); | |
| 53 | 45 | ||
| 54 | 46 | // Rate limiting state | |
| 55 | 47 | let cooldownEndTime = $state<number | null>(null); | |
| @@ -297,39 +289,6 @@ | |||
| 297 | 289 | } | |
| 298 | 290 | } | |
| 299 | 291 | ||
| 300 | - | function formatLastSeen(lastSeen: number | null | undefined) { | |
| 301 | - | if (!lastSeen) return 'Never'; | |
| 302 | - | ||
| 303 | - | const now = Date.now(); | |
| 304 | - | const diff = now - lastSeen; | |
| 305 | - | ||
| 306 | - | // Less than a minute | |
| 307 | - | if (diff < 60000) { | |
| 308 | - | return 'Just now'; | |
| 309 | - | } | |
| 310 | - | ||
| 311 | - | // Less than an hour | |
| 312 | - | if (diff < 3600000) { | |
| 313 | - | const minutes = Math.floor(diff / 60000); | |
| 314 | - | return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; | |
| 315 | - | } | |
| 316 | - | ||
| 317 | - | // Less than a day | |
| 318 | - | if (diff < 86400000) { | |
| 319 | - | const hours = Math.floor(diff / 3600000); | |
| 320 | - | return `${hours} hour${hours > 1 ? 's' : ''} ago`; | |
| 321 | - | } | |
| 322 | - | ||
| 323 | - | // Less than a week | |
| 324 | - | if (diff < 604800000) { | |
| 325 | - | const days = Math.floor(diff / 86400000); | |
| 326 | - | return `${days} day${days > 1 ? 's' : ''} ago`; | |
| 327 | - | } | |
| 328 | - | ||
| 329 | - | // More than a week | |
| 330 | - | return new Date(lastSeen).toLocaleDateString(); | |
| 331 | - | } | |
| 332 | - | ||
| 333 | 292 | function handleDragMove(event: CustomEvent<{ x: number; y: number }>) { | |
| 334 | 293 | windowX = event.detail.x; | |
| 335 | 294 | windowY = event.detail.y; | |
| @@ -344,7 +303,7 @@ | |||
| 344 | 303 | if (node) { | |
| 345 | 304 | // If minimized, restore first | |
| 346 | 305 | if (isMinimized) { | |
| 347 | - | const minimizableNode = node as any; | |
| 306 | + | const minimizableNode = node as unknown as MinimizableNode; | |
| 348 | 307 | if (minimizableNode?.toggleMinimize) { | |
| 349 | 308 | minimizableNode.toggleMinimize(); | |
| 350 | 309 | } | |
| @@ -509,7 +468,9 @@ | |||
| 509 | 468 | aria-label="Minimize" | |
| 510 | 469 | onclick={(e) => { | |
| 511 | 470 | e.stopPropagation(); | |
| 512 | - | const node = (e.currentTarget as HTMLElement).closest('.window') as any; | |
| 471 | + | const node = (e.currentTarget as HTMLElement).closest( | |
| 472 | + | '.window' | |
| 473 | + | ) as MinimizableNode | null; | |
| 513 | 474 | if (node?.toggleMinimize) node.toggleMinimize(); | |
| 514 | 475 | }} | |
| 515 | 476 | ></button> | |
| @@ -526,7 +487,9 @@ | |||
| 526 | 487 | aria-label="Minimize" | |
| 527 | 488 | onclick={(e) => { | |
| 528 | 489 | e.stopPropagation(); | |
| 529 | - | const node = (e.currentTarget as HTMLElement).closest('.window') as any; | |
| 490 | + | const node = (e.currentTarget as HTMLElement).closest( | |
| 491 | + | '.window' | |
| 492 | + | ) as MinimizableNode | null; | |
| 530 | 493 | if (node?.toggleMinimize) node.toggleMinimize(); | |
| 531 | 494 | }} | |
| 532 | 495 | ></button> | |
| @@ -695,7 +658,7 @@ | |||
| 695 | 658 | <p style="margin: 0 0 0.3rem 0;"> | |
| 696 | 659 | <strong>{totalUsers} membre{totalUsers > 1 ? 's' : ''}</strong> | |
| 697 | 660 | </p> | |
| 698 | - | {#each onlineUsers as user} | |
| 661 | + | {#each onlineUsers as user (user.id)} | |
| 699 | 662 | <div class="user" class:offline={user.status === 'offline'}> | |
| 700 | 663 | <span class="status-icon">{getStatusIcon(user.status)}</span> | |
| 701 | 664 | <div class="user-info"> | |
| @@ -1,19 +1,12 @@ | |||||
| 1 | <script lang="ts"> | 1 | <script lang="ts"> | ||
| 2 | import { chatState } from '../states/chat.svelte'; | 2 | import { chatState } from '../states/chat.svelte'; | ||
| 3 | - | import type { | 3 | + | import type { User, Message, EnrichedMessage, SafeUser } from '../types/chat'; |
| 4 | - | MessageType, | |||
| 5 | - | User, | |||
| 6 | - | ChatRoom, | |||
| 7 | - | Message, | |||
| 8 | - | EnrichedMessage, | |||
| 9 | - | SafeUser | |||
| 10 | - | } from '../types/chat'; | |||
| 11 | import { onMount } from 'svelte'; | 4 | import { onMount } from 'svelte'; | ||
| 12 | import { browser } from '$app/environment'; | 5 | import { browser } from '$app/environment'; | ||
| 13 | import { draggable } from '$lib/actions/draggable'; | 6 | import { draggable } from '$lib/actions/draggable'; | ||
| 14 | import { resizable } from '$lib/actions/resizable'; | 7 | import { resizable } from '$lib/actions/resizable'; | ||
| 15 | import { maximizable } from '$lib/actions/maximizable'; | 8 | import { maximizable } from '$lib/actions/maximizable'; | ||
| 16 | - | import { minimizable } from '$lib/actions/minimizable'; | 9 | + | import { minimizable, type MinimizableNode } from '$lib/actions/minimizable'; |
| 17 | import LoadingButton from './ui/button-loading.svelte'; | 10 | import LoadingButton from './ui/button-loading.svelte'; | ||
| 18 | import Tooltip from './ui/tooltip.svelte'; | 11 | import Tooltip from './ui/tooltip.svelte'; | ||
| 19 | import LoadingDots from './ui/loading-dots.svelte'; | 12 | import LoadingDots from './ui/loading-dots.svelte'; | ||
| @@ -49,7 +42,6 @@ | |||||
| 49 | ...initialTextStyle, | 42 | ...initialTextStyle, | ||
| 50 | color: initialTextStyle.color || '#000000' // Ensure we always have a valid color | 43 | color: initialTextStyle.color || '#000000' // Ensure we always have a valid color | ||
| 51 | }); | 44 | }); | ||
| 52 | - | let showFormattingToolbar = $state(true); | |||
| 53 | 45 | ||||
| 54 | // Rate limiting state | 46 | // Rate limiting state | ||
| 55 | let cooldownEndTime = $state<number | null>(null); | 47 | let cooldownEndTime = $state<number | null>(null); | ||
| @@ -297,39 +289,6 @@ | |||||
| 297 | } | 289 | } | ||
| 298 | } | 290 | } | ||
| 299 | 291 | ||||
| 300 | - | function formatLastSeen(lastSeen: number | null | undefined) { | |||
| 301 | - | if (!lastSeen) return 'Never'; | |||
| 302 | - | ||||
| 303 | - | const now = Date.now(); | |||
| 304 | - | const diff = now - lastSeen; | |||
| 305 | - | ||||
| 306 | - | // Less than a minute | |||
| 307 | - | if (diff < 60000) { | |||
| 308 | - | return 'Just now'; | |||
| 309 | - | } | |||
| 310 | - | ||||
| 311 | - | // Less than an hour | |||
| 312 | - | if (diff < 3600000) { | |||
| 313 | - | const minutes = Math.floor(diff / 60000); | |||
| 314 | - | return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; | |||
| 315 | - | } | |||
| 316 | - | ||||
| 317 | - | // Less than a day | |||
| 318 | - | if (diff < 86400000) { | |||
| 319 | - | const hours = Math.floor(diff / 3600000); | |||
| 320 | - | return `${hours} hour${hours > 1 ? 's' : ''} ago`; | |||
| 321 | - | } | |||
| 322 | - | ||||
| 323 | - | // Less than a week | |||
| 324 | - | if (diff < 604800000) { | |||
| 325 | - | const days = Math.floor(diff / 86400000); | |||
| 326 | - | return `${days} day${days > 1 ? 's' : ''} ago`; | |||
| 327 | - | } | |||
| 328 | - | ||||
| 329 | - | // More than a week | |||
| 330 | - | return new Date(lastSeen).toLocaleDateString(); | |||
| 331 | - | } | |||
| 332 | - | ||||
| 333 | function handleDragMove(event: CustomEvent<{ x: number; y: number }>) { | 292 | function handleDragMove(event: CustomEvent<{ x: number; y: number }>) { | ||
| 334 | windowX = event.detail.x; | 293 | windowX = event.detail.x; | ||
| 335 | windowY = event.detail.y; | 294 | windowY = event.detail.y; | ||
| @@ -344,7 +303,7 @@ | |||||
| 344 | if (node) { | 303 | if (node) { | ||
| 345 | // If minimized, restore first | 304 | // If minimized, restore first | ||
| 346 | if (isMinimized) { | 305 | if (isMinimized) { | ||
| 347 | - | const minimizableNode = node as any; | 306 | + | const minimizableNode = node as unknown as MinimizableNode; |
| 348 | if (minimizableNode?.toggleMinimize) { | 307 | if (minimizableNode?.toggleMinimize) { | ||
| 349 | minimizableNode.toggleMinimize(); | 308 | minimizableNode.toggleMinimize(); | ||
| 350 | } | 309 | } | ||
| @@ -509,7 +468,9 @@ | |||||
| 509 | aria-label="Minimize" | 468 | aria-label="Minimize" | ||
| 510 | onclick={(e) => { | 469 | onclick={(e) => { | ||
| 511 | e.stopPropagation(); | 470 | e.stopPropagation(); | ||
| 512 | - | const node = (e.currentTarget as HTMLElement).closest('.window') as any; | 471 | + | const node = (e.currentTarget as HTMLElement).closest( |
| 472 | + | '.window' | |||
| 473 | + | ) as MinimizableNode | null; | |||
| 513 | if (node?.toggleMinimize) node.toggleMinimize(); | 474 | if (node?.toggleMinimize) node.toggleMinimize(); | ||
| 514 | }} | 475 | }} | ||
| 515 | ></button> | 476 | ></button> | ||
| @@ -526,7 +487,9 @@ | |||||
| 526 | aria-label="Minimize" | 487 | aria-label="Minimize" | ||
| 527 | onclick={(e) => { | 488 | onclick={(e) => { | ||
| 528 | e.stopPropagation(); | 489 | e.stopPropagation(); | ||
| 529 | - | const node = (e.currentTarget as HTMLElement).closest('.window') as any; | 490 | + | const node = (e.currentTarget as HTMLElement).closest( |
| 491 | + | '.window' | |||
| 492 | + | ) as MinimizableNode | null; | |||
| 530 | if (node?.toggleMinimize) node.toggleMinimize(); | 493 | if (node?.toggleMinimize) node.toggleMinimize(); | ||
| 531 | }} | 494 | }} | ||
| 532 | ></button> | 495 | ></button> | ||
| @@ -695,7 +658,7 @@ | |||||
| 695 | <p style="margin: 0 0 0.3rem 0;"> | 658 | <p style="margin: 0 0 0.3rem 0;"> | ||
| 696 | <strong>{totalUsers} membre{totalUsers > 1 ? 's' : ''}</strong> | 659 | <strong>{totalUsers} membre{totalUsers > 1 ? 's' : ''}</strong> | ||
| 697 | </p> | 660 | </p> | ||
| 698 | - | {#each onlineUsers as user} | 661 | + | {#each onlineUsers as user (user.id)} |
| 699 | <div class="user" class:offline={user.status === 'offline'}> | 662 | <div class="user" class:offline={user.status === 'offline'}> | ||
| 700 | <span class="status-icon">{getStatusIcon(user.status)}</span> | 663 | <span class="status-icon">{getStatusIcon(user.status)}</span> | ||
| 701 | <div class="user-info"> | 664 | <div class="user-info"> | ||
M src/lib/components/color-picker-256.svelte +5 -4
| @@ -7,7 +7,7 @@ | |||
| 7 | 7 | selectedGradient = $bindable(undefined), | |
| 8 | 8 | onColorChange, | |
| 9 | 9 | onGradientChange, | |
| 10 | - | compact = true | |
| 10 | + | compact: _compact = true | |
| 11 | 11 | } = $props<{ | |
| 12 | 12 | selectedColor?: string; | |
| 13 | 13 | selectedGradient?: string[]; | |
| @@ -15,6 +15,7 @@ | |||
| 15 | 15 | onGradientChange?: (gradient: string[]) => void; | |
| 16 | 16 | compact?: boolean; | |
| 17 | 17 | }>(); | |
| 18 | + | void _compact; // Reserved for future use | |
| 18 | 19 | ||
| 19 | 20 | // State | |
| 20 | 21 | let showPicker = $state(false); | |
| @@ -142,7 +143,7 @@ | |||
| 142 | 143 | <!-- Gradient controls --> | |
| 143 | 144 | <div class="gradient-controls"> | |
| 144 | 145 | <div class="gradient-slots"> | |
| 145 | - | {#each gradientColors as color, index} | |
| 146 | + | {#each gradientColors as color, index (index)} | |
| 146 | 147 | <button | |
| 147 | 148 | class="gradient-slot" | |
| 148 | 149 | class:active={activeGradientSlot === index} | |
| @@ -186,7 +187,7 @@ | |||
| 186 | 187 | <div class="color-section"> | |
| 187 | 188 | <div class="section-title">Classic Colors</div> | |
| 188 | 189 | <div class="color-row"> | |
| 189 | - | {#each classicColors as color} | |
| 190 | + | {#each classicColors as color (color)} | |
| 190 | 191 | <button | |
| 191 | 192 | class="color-swatch classic-swatch" | |
| 192 | 193 | style="--swatch-color: {color}; background-color: {color} !important;" | |
| @@ -201,7 +202,7 @@ | |||
| 201 | 202 | <div class="color-section"> | |
| 202 | 203 | <div class="section-title">Full Palette</div> | |
| 203 | 204 | <div class="color-grid"> | |
| 204 | - | {#each colors256 as color} | |
| 205 | + | {#each colors256 as color (color)} | |
| 205 | 206 | <button | |
| 206 | 207 | class="color-swatch" | |
| 207 | 208 | style="--swatch-color: {color}; background-color: {color} !important;" | |
| @@ -7,7 +7,7 @@ | |||||
| 7 | selectedGradient = $bindable(undefined), | 7 | selectedGradient = $bindable(undefined), | ||
| 8 | onColorChange, | 8 | onColorChange, | ||
| 9 | onGradientChange, | 9 | onGradientChange, | ||
| 10 | - | compact = true | 10 | + | compact: _compact = true |
| 11 | } = $props<{ | 11 | } = $props<{ | ||
| 12 | selectedColor?: string; | 12 | selectedColor?: string; | ||
| 13 | selectedGradient?: string[]; | 13 | selectedGradient?: string[]; | ||
| @@ -15,6 +15,7 @@ | |||||
| 15 | onGradientChange?: (gradient: string[]) => void; | 15 | onGradientChange?: (gradient: string[]) => void; | ||
| 16 | compact?: boolean; | 16 | compact?: boolean; | ||
| 17 | }>(); | 17 | }>(); | ||
| 18 | + | void _compact; // Reserved for future use | |||
| 18 | 19 | ||||
| 19 | // State | 20 | // State | ||
| 20 | let showPicker = $state(false); | 21 | let showPicker = $state(false); | ||
| @@ -142,7 +143,7 @@ | |||||
| 142 | <!-- Gradient controls --> | 143 | <!-- Gradient controls --> | ||
| 143 | <div class="gradient-controls"> | 144 | <div class="gradient-controls"> | ||
| 144 | <div class="gradient-slots"> | 145 | <div class="gradient-slots"> | ||
| 145 | - | {#each gradientColors as color, index} | 146 | + | {#each gradientColors as color, index (index)} |
| 146 | <button | 147 | <button | ||
| 147 | class="gradient-slot" | 148 | class="gradient-slot" | ||
| 148 | class:active={activeGradientSlot === index} | 149 | class:active={activeGradientSlot === index} | ||
| @@ -186,7 +187,7 @@ | |||||
| 186 | <div class="color-section"> | 187 | <div class="color-section"> | ||
| 187 | <div class="section-title">Classic Colors</div> | 188 | <div class="section-title">Classic Colors</div> | ||
| 188 | <div class="color-row"> | 189 | <div class="color-row"> | ||
| 189 | - | {#each classicColors as color} | 190 | + | {#each classicColors as color (color)} |
| 190 | <button | 191 | <button | ||
| 191 | class="color-swatch classic-swatch" | 192 | class="color-swatch classic-swatch" | ||
| 192 | style="--swatch-color: {color}; background-color: {color} !important;" | 193 | style="--swatch-color: {color}; background-color: {color} !important;" | ||
| @@ -201,7 +202,7 @@ | |||||
| 201 | <div class="color-section"> | 202 | <div class="color-section"> | ||
| 202 | <div class="section-title">Full Palette</div> | 203 | <div class="section-title">Full Palette</div> | ||
| 203 | <div class="color-grid"> | 204 | <div class="color-grid"> | ||
| 204 | - | {#each colors256 as color} | 205 | + | {#each colors256 as color (color)} |
| 205 | <button | 206 | <button | ||
| 206 | class="color-swatch" | 207 | class="color-swatch" | ||
| 207 | style="--swatch-color: {color}; background-color: {color} !important;" | 208 | style="--swatch-color: {color}; background-color: {color} !important;" | ||
M src/lib/components/formatted-message.svelte +4 -2
| @@ -1,9 +1,8 @@ | |||
| 1 | 1 | <script lang="ts"> | |
| 2 | - | import { onMount } from 'svelte'; | |
| 3 | 2 | import type { EnrichedMessage } from '../types/chat'; | |
| 4 | 3 | import type { TextStyle } from '../types/text-formatting'; | |
| 5 | 4 | import { formatText, createGradientText } from '../utils/text-formatter'; | |
| 6 | - | import { DEFAULT_TEXT_STYLE, generateCSSStyle, RETRO_FONTS } from '../types/text-formatting'; | |
| 5 | + | import { DEFAULT_TEXT_STYLE } from '../types/text-formatting'; | |
| 7 | 6 | ||
| 8 | 7 | // Props | |
| 9 | 8 | let { message, allowFormatting = true } = $props<{ | |
| @@ -175,16 +174,19 @@ | |||
| 175 | 174 | })()} | |
| 176 | 175 | {#if parsedStyle?.gradient && Array.isArray(parsedStyle.gradient) && parsedStyle.gradient.length > 1} | |
| 177 | 176 | <!-- Gradient messages are already wrapped properly --> | |
| 177 | + | <!-- eslint-disable-next-line svelte/no-at-html-tags -- formattedContent is sanitized by createGradientText --> | |
| 178 | 178 | {@html formattedContent} | |
| 179 | 179 | {:else} | |
| 180 | 180 | <!-- Regular formatted messages --> | |
| 181 | 181 | <span class={getMessageClasses()} style={getMessageStyles()}> | |
| 182 | + | <!-- eslint-disable-next-line svelte/no-at-html-tags -- formattedContent is sanitized by formatText --> | |
| 182 | 183 | {@html formattedContent} | |
| 183 | 184 | </span> | |
| 184 | 185 | {/if} | |
| 185 | 186 | {:else} | |
| 186 | 187 | <!-- Plain messages --> | |
| 187 | 188 | <span class={getMessageClasses()}> | |
| 189 | + | <!-- eslint-disable-next-line svelte/no-at-html-tags -- formattedContent is sanitized by escapeHtml --> | |
| 188 | 190 | {@html formattedContent} | |
| 189 | 191 | </span> | |
| 190 | 192 | {/if} | |
| @@ -1,9 +1,8 @@ | |||||
| 1 | <script lang="ts"> | 1 | <script lang="ts"> | ||
| 2 | - | import { onMount } from 'svelte'; | |||
| 3 | import type { EnrichedMessage } from '../types/chat'; | 2 | import type { EnrichedMessage } from '../types/chat'; | ||
| 4 | import type { TextStyle } from '../types/text-formatting'; | 3 | import type { TextStyle } from '../types/text-formatting'; | ||
| 5 | import { formatText, createGradientText } from '../utils/text-formatter'; | 4 | import { formatText, createGradientText } from '../utils/text-formatter'; | ||
| 6 | - | import { DEFAULT_TEXT_STYLE, generateCSSStyle, RETRO_FONTS } from '../types/text-formatting'; | 5 | + | import { DEFAULT_TEXT_STYLE } from '../types/text-formatting'; |
| 7 | 6 | ||||
| 8 | // Props | 7 | // Props | ||
| 9 | let { message, allowFormatting = true } = $props<{ | 8 | let { message, allowFormatting = true } = $props<{ | ||
| @@ -175,16 +174,19 @@ | |||||
| 175 | })()} | 174 | })()} | ||
| 176 | {#if parsedStyle?.gradient && Array.isArray(parsedStyle.gradient) && parsedStyle.gradient.length > 1} | 175 | {#if parsedStyle?.gradient && Array.isArray(parsedStyle.gradient) && parsedStyle.gradient.length > 1} | ||
| 177 | <!-- Gradient messages are already wrapped properly --> | 176 | <!-- Gradient messages are already wrapped properly --> | ||
| 177 | + | <!-- eslint-disable-next-line svelte/no-at-html-tags -- formattedContent is sanitized by createGradientText --> | |||
| 178 | {@html formattedContent} | 178 | {@html formattedContent} | ||
| 179 | {:else} | 179 | {:else} | ||
| 180 | <!-- Regular formatted messages --> | 180 | <!-- Regular formatted messages --> | ||
| 181 | <span class={getMessageClasses()} style={getMessageStyles()}> | 181 | <span class={getMessageClasses()} style={getMessageStyles()}> | ||
| 182 | + | <!-- eslint-disable-next-line svelte/no-at-html-tags -- formattedContent is sanitized by formatText --> | |||
| 182 | {@html formattedContent} | 183 | {@html formattedContent} | ||
| 183 | </span> | 184 | </span> | ||
| 184 | {/if} | 185 | {/if} | ||
| 185 | {:else} | 186 | {:else} | ||
| 186 | <!-- Plain messages --> | 187 | <!-- Plain messages --> | ||
| 187 | <span class={getMessageClasses()}> | 188 | <span class={getMessageClasses()}> | ||
| 189 | + | <!-- eslint-disable-next-line svelte/no-at-html-tags -- formattedContent is sanitized by escapeHtml --> | |||
| 188 | {@html formattedContent} | 190 | {@html formattedContent} | ||
| 189 | </span> | 191 | </span> | ||
| 190 | {/if} | 192 | {/if} | ||
M src/lib/components/password-strength-indicator.svelte +8 -6
| @@ -5,13 +5,15 @@ | |||
| 5 | 5 | DEFAULT_PASSWORD_CONSTRAINTS | |
| 6 | 6 | } from '../validation/password'; | |
| 7 | 7 | ||
| 8 | - | interface $$Props { | |
| 8 | + | let { | |
| 9 | + | password = '', | |
| 10 | + | constraints = DEFAULT_PASSWORD_CONSTRAINTS, | |
| 11 | + | showDetails = true | |
| 12 | + | } = $props<{ | |
| 9 | 13 | password: string; | |
| 10 | 14 | constraints?: PasswordConstraints; | |
| 11 | 15 | showDetails?: boolean; | |
| 12 | - | } | |
| 13 | - | ||
| 14 | - | let { password = '', constraints = DEFAULT_PASSWORD_CONSTRAINTS, showDetails = true } = $props(); | |
| 16 | + | }>(); | |
| 15 | 17 | ||
| 16 | 18 | // Reactive validation result | |
| 17 | 19 | let validation = $derived(validatePasswordStrength(password, constraints)); | |
| @@ -43,7 +45,7 @@ | |||
| 43 | 45 | {#if showDetails} | |
| 44 | 46 | <!-- Requirements checklist --> | |
| 45 | 47 | <div class="requirements-list"> | |
| 46 | - | {#each validation.requirements as requirement} | |
| 48 | + | {#each validation.requirements as requirement (requirement.message)} | |
| 47 | 49 | <div class="requirement {requirement.met ? 'met' : 'unmet'}"> | |
| 48 | 50 | <span class="requirement-icon"> | |
| 49 | 51 | {requirement.met ? '✓' : '✗'} | |
| @@ -56,7 +58,7 @@ | |||
| 56 | 58 | <!-- Error messages --> | |
| 57 | 59 | {#if validation.errors.length > 0} | |
| 58 | 60 | <div class="error-messages"> | |
| 59 | - | {#each validation.errors as error} | |
| 61 | + | {#each validation.errors as error (error)} | |
| 60 | 62 | <div class="error-message">{error}</div> | |
| 61 | 63 | {/each} | |
| 62 | 64 | </div> | |
| @@ -5,13 +5,15 @@ | |||||
| 5 | DEFAULT_PASSWORD_CONSTRAINTS | 5 | DEFAULT_PASSWORD_CONSTRAINTS | ||
| 6 | } from '../validation/password'; | 6 | } from '../validation/password'; | ||
| 7 | 7 | ||||
| 8 | - | interface $$Props { | 8 | + | let { |
| 9 | + | password = '', | |||
| 10 | + | constraints = DEFAULT_PASSWORD_CONSTRAINTS, | |||
| 11 | + | showDetails = true | |||
| 12 | + | } = $props<{ | |||
| 9 | password: string; | 13 | password: string; | ||
| 10 | constraints?: PasswordConstraints; | 14 | constraints?: PasswordConstraints; | ||
| 11 | showDetails?: boolean; | 15 | showDetails?: boolean; | ||
| 12 | - | } | 16 | + | }>(); |
| 13 | - | ||||
| 14 | - | let { password = '', constraints = DEFAULT_PASSWORD_CONSTRAINTS, showDetails = true } = $props(); | |||
| 15 | 17 | ||||
| 16 | // Reactive validation result | 18 | // Reactive validation result | ||
| 17 | let validation = $derived(validatePasswordStrength(password, constraints)); | 19 | let validation = $derived(validatePasswordStrength(password, constraints)); | ||
| @@ -43,7 +45,7 @@ | |||||
| 43 | {#if showDetails} | 45 | {#if showDetails} | ||
| 44 | <!-- Requirements checklist --> | 46 | <!-- Requirements checklist --> | ||
| 45 | <div class="requirements-list"> | 47 | <div class="requirements-list"> | ||
| 46 | - | {#each validation.requirements as requirement} | 48 | + | {#each validation.requirements as requirement (requirement.message)} |
| 47 | <div class="requirement {requirement.met ? 'met' : 'unmet'}"> | 49 | <div class="requirement {requirement.met ? 'met' : 'unmet'}"> | ||
| 48 | <span class="requirement-icon"> | 50 | <span class="requirement-icon"> | ||
| 49 | {requirement.met ? '✓' : '✗'} | 51 | {requirement.met ? '✓' : '✗'} | ||
| @@ -56,7 +58,7 @@ | |||||
| 56 | <!-- Error messages --> | 58 | <!-- Error messages --> | ||
| 57 | {#if validation.errors.length > 0} | 59 | {#if validation.errors.length > 0} | ||
| 58 | <div class="error-messages"> | 60 | <div class="error-messages"> | ||
| 59 | - | {#each validation.errors as error} | 61 | + | {#each validation.errors as error (error)} |
| 60 | <div class="error-message">{error}</div> | 62 | <div class="error-message">{error}</div> | ||
| 61 | {/each} | 63 | {/each} | ||
| 62 | </div> | 64 | </div> | ||
M src/lib/components/text-formatting-toolbar.svelte +5 -12
| @@ -1,8 +1,6 @@ | |||
| 1 | 1 | <script lang="ts"> | |
| 2 | 2 | import { | |
| 3 | 3 | RETRO_FONTS, | |
| 4 | - | CLASSIC_COLORS, | |
| 5 | - | GRADIENT_PRESETS, | |
| 6 | 4 | DEFAULT_TEXT_STYLE, | |
| 7 | 5 | type TextStyle, | |
| 8 | 6 | type FontFamily | |
| @@ -12,10 +10,10 @@ | |||
| 12 | 10 | // Props | |
| 13 | 11 | let { | |
| 14 | 12 | style = $bindable(DEFAULT_TEXT_STYLE), | |
| 15 | - | onPreviewText, | |
| 13 | + | onPreviewText: _onPreviewText, | |
| 16 | 14 | compact = true, | |
| 17 | 15 | showFontSelector = false, | |
| 18 | - | showGradients = true | |
| 16 | + | showGradients: _showGradients = true | |
| 19 | 17 | } = $props<{ | |
| 20 | 18 | style?: TextStyle; | |
| 21 | 19 | onPreviewText?: (text: string) => void; | |
| @@ -23,6 +21,8 @@ | |||
| 23 | 21 | showFontSelector?: boolean; | |
| 24 | 22 | showGradients?: boolean; | |
| 25 | 23 | }>(); | |
| 24 | + | void _showGradients; // Reserved for future use | |
| 25 | + | void _onPreviewText; // Reserved for future use | |
| 26 | 26 | ||
| 27 | 27 | // State | |
| 28 | 28 | let currentColor = $state(style.color || '#000000'); | |
| @@ -75,13 +75,6 @@ | |||
| 75 | 75 | } | |
| 76 | 76 | } | |
| 77 | 77 | ||
| 78 | - | // Preview text change | |
| 79 | - | function previewTextChanged() { | |
| 80 | - | if (onPreviewText) { | |
| 81 | - | onPreviewText('Sample formatted text'); | |
| 82 | - | } | |
| 83 | - | } | |
| 84 | - | ||
| 85 | 78 | // Update internal state when style prop changes | |
| 86 | 79 | $effect(() => { | |
| 87 | 80 | currentColor = style.color || '#000000'; | |
| @@ -99,7 +92,7 @@ | |||
| 99 | 92 | onchange={(e) => selectFont((e.target as HTMLSelectElement).value as FontFamily)} | |
| 100 | 93 | title="Font Family" | |
| 101 | 94 | > | |
| 102 | - | {#each Object.entries(RETRO_FONTS) as [key, font]} | |
| 95 | + | {#each Object.entries(RETRO_FONTS) as [key, font] (key)} | |
| 103 | 96 | <option value={key} style="font-family: {font.stack};">{font.name}</option> | |
| 104 | 97 | {/each} | |
| 105 | 98 | </select> | |
| @@ -1,8 +1,6 @@ | |||||
| 1 | <script lang="ts"> | 1 | <script lang="ts"> | ||
| 2 | import { | 2 | import { | ||
| 3 | RETRO_FONTS, | 3 | RETRO_FONTS, | ||
| 4 | - | CLASSIC_COLORS, | |||
| 5 | - | GRADIENT_PRESETS, | |||
| 6 | DEFAULT_TEXT_STYLE, | 4 | DEFAULT_TEXT_STYLE, | ||
| 7 | type TextStyle, | 5 | type TextStyle, | ||
| 8 | type FontFamily | 6 | type FontFamily | ||
| @@ -12,10 +10,10 @@ | |||||
| 12 | // Props | 10 | // Props | ||
| 13 | let { | 11 | let { | ||
| 14 | style = $bindable(DEFAULT_TEXT_STYLE), | 12 | style = $bindable(DEFAULT_TEXT_STYLE), | ||
| 15 | - | onPreviewText, | 13 | + | onPreviewText: _onPreviewText, |
| 16 | compact = true, | 14 | compact = true, | ||
| 17 | showFontSelector = false, | 15 | showFontSelector = false, | ||
| 18 | - | showGradients = true | 16 | + | showGradients: _showGradients = true |
| 19 | } = $props<{ | 17 | } = $props<{ | ||
| 20 | style?: TextStyle; | 18 | style?: TextStyle; | ||
| 21 | onPreviewText?: (text: string) => void; | 19 | onPreviewText?: (text: string) => void; | ||
| @@ -23,6 +21,8 @@ | |||||
| 23 | showFontSelector?: boolean; | 21 | showFontSelector?: boolean; | ||
| 24 | showGradients?: boolean; | 22 | showGradients?: boolean; | ||
| 25 | }>(); | 23 | }>(); | ||
| 24 | + | void _showGradients; // Reserved for future use | |||
| 25 | + | void _onPreviewText; // Reserved for future use | |||
| 26 | 26 | ||||
| 27 | // State | 27 | // State | ||
| 28 | let currentColor = $state(style.color || '#000000'); | 28 | let currentColor = $state(style.color || '#000000'); | ||
| @@ -75,13 +75,6 @@ | |||||
| 75 | } | 75 | } | ||
| 76 | } | 76 | } | ||
| 77 | 77 | ||||
| 78 | - | // Preview text change | |||
| 79 | - | function previewTextChanged() { | |||
| 80 | - | if (onPreviewText) { | |||
| 81 | - | onPreviewText('Sample formatted text'); | |||
| 82 | - | } | |||
| 83 | - | } | |||
| 84 | - | ||||
| 85 | // Update internal state when style prop changes | 78 | // Update internal state when style prop changes | ||
| 86 | $effect(() => { | 79 | $effect(() => { | ||
| 87 | currentColor = style.color || '#000000'; | 80 | currentColor = style.color || '#000000'; | ||
| @@ -99,7 +92,7 @@ | |||||
| 99 | onchange={(e) => selectFont((e.target as HTMLSelectElement).value as FontFamily)} | 92 | onchange={(e) => selectFont((e.target as HTMLSelectElement).value as FontFamily)} | ||
| 100 | title="Font Family" | 93 | title="Font Family" | ||
| 101 | > | 94 | > | ||
| 102 | - | {#each Object.entries(RETRO_FONTS) as [key, font]} | 95 | + | {#each Object.entries(RETRO_FONTS) as [key, font] (key)} |
| 103 | <option value={key} style="font-family: {font.stack};">{font.name}</option> | 96 | <option value={key} style="font-family: {font.stack};">{font.name}</option> | ||
| 104 | {/each} | 97 | {/each} | ||
| 105 | </select> | 98 | </select> | ||
M src/lib/components/text-style-selector.svelte +2 -2
| @@ -1,7 +1,6 @@ | |||
| 1 | 1 | <script lang="ts"> | |
| 2 | 2 | import { | |
| 3 | 3 | RETRO_FONTS, | |
| 4 | - | CLASSIC_COLORS, | |
| 5 | 4 | DEFAULT_TEXT_STYLE, | |
| 6 | 5 | type TextStyle, | |
| 7 | 6 | type FontFamily, | |
| @@ -106,7 +105,7 @@ | |||
| 106 | 105 | bind:value={style.fontFamily} | |
| 107 | 106 | onchange={(e) => selectFont((e.target as HTMLSelectElement).value as FontFamily)} | |
| 108 | 107 | > | |
| 109 | - | {#each Object.entries(RETRO_FONTS) as [key, font]} | |
| 108 | + | {#each Object.entries(RETRO_FONTS) as [key, font] (key)} | |
| 110 | 109 | <option value={key} style="font-family: {font.stack}"> | |
| 111 | 110 | {font.name} | |
| 112 | 111 | </option> | |
| @@ -185,6 +184,7 @@ | |||
| 185 | 184 | {#if isProcessing} | |
| 186 | 185 | <span class="loading">Processing...</span> | |
| 187 | 186 | {:else} | |
| 187 | + | <!-- eslint-disable-next-line svelte/no-at-html-tags -- formattedPreview is sanitized by formatText/createGradientText --> | |
| 188 | 188 | {@html formattedPreview} | |
| 189 | 189 | {/if} | |
| 190 | 190 | </div> | |
| @@ -1,7 +1,6 @@ | |||||
| 1 | <script lang="ts"> | 1 | <script lang="ts"> | ||
| 2 | import { | 2 | import { | ||
| 3 | RETRO_FONTS, | 3 | RETRO_FONTS, | ||
| 4 | - | CLASSIC_COLORS, | |||
| 5 | DEFAULT_TEXT_STYLE, | 4 | DEFAULT_TEXT_STYLE, | ||
| 6 | type TextStyle, | 5 | type TextStyle, | ||
| 7 | type FontFamily, | 6 | type FontFamily, | ||
| @@ -106,7 +105,7 @@ | |||||
| 106 | bind:value={style.fontFamily} | 105 | bind:value={style.fontFamily} | ||
| 107 | onchange={(e) => selectFont((e.target as HTMLSelectElement).value as FontFamily)} | 106 | onchange={(e) => selectFont((e.target as HTMLSelectElement).value as FontFamily)} | ||
| 108 | > | 107 | > | ||
| 109 | - | {#each Object.entries(RETRO_FONTS) as [key, font]} | 108 | + | {#each Object.entries(RETRO_FONTS) as [key, font] (key)} |
| 110 | <option value={key} style="font-family: {font.stack}"> | 109 | <option value={key} style="font-family: {font.stack}"> | ||
| 111 | {font.name} | 110 | {font.name} | ||
| 112 | </option> | 111 | </option> | ||
| @@ -185,6 +184,7 @@ | |||||
| 185 | {#if isProcessing} | 184 | {#if isProcessing} | ||
| 186 | <span class="loading">Processing...</span> | 185 | <span class="loading">Processing...</span> | ||
| 187 | {:else} | 186 | {:else} | ||
| 187 | + | <!-- eslint-disable-next-line svelte/no-at-html-tags -- formattedPreview is sanitized by formatText/createGradientText --> | |||
| 188 | {@html formattedPreview} | 188 | {@html formattedPreview} | ||
| 189 | {/if} | 189 | {/if} | ||
| 190 | </div> | 190 | </div> | ||
M src/lib/components/turnstile.svelte +1 -1
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | import { onMount } from 'svelte'; | |
| 3 | 3 | import { fade } from 'svelte/transition'; | |
| 4 | 4 | import { PUBLIC_TURNSTILE_SITE_KEY } from '$env/static/public'; | |
| 5 | - | import type { Turnstile, TurnstileOptions } from '$lib/types/turnstile'; | |
| 5 | + | import type { TurnstileOptions } from '$lib/types/turnstile'; | |
| 6 | 6 | ||
| 7 | 7 | let isVerified = $state(false); | |
| 8 | 8 | ||
| @@ -2,7 +2,7 @@ | |||||
| 2 | import { onMount } from 'svelte'; | 2 | import { onMount } from 'svelte'; | ||
| 3 | import { fade } from 'svelte/transition'; | 3 | import { fade } from 'svelte/transition'; | ||
| 4 | import { PUBLIC_TURNSTILE_SITE_KEY } from '$env/static/public'; | 4 | import { PUBLIC_TURNSTILE_SITE_KEY } from '$env/static/public'; | ||
| 5 | - | import type { Turnstile, TurnstileOptions } from '$lib/types/turnstile'; | 5 | + | import type { TurnstileOptions } from '$lib/types/turnstile'; |
| 6 | 6 | ||||
| 7 | let isVerified = $state(false); | 7 | let isVerified = $state(false); | ||
| 8 | 8 | ||||
M src/lib/components/ui/tooltip.svelte +1 -0
| @@ -1,4 +1,5 @@ | |||
| 1 | 1 | <script lang="ts"> | |
| 2 | + | // eslint-disable-next-line @typescript-eslint/no-unused-vars -- $$Props is used for Svelte type inference | |
| 2 | 3 | type $$Props = { | |
| 3 | 4 | data: { | |
| 4 | 5 | text: string; | |
| @@ -1,4 +1,5 @@ | |||||
| 1 | <script lang="ts"> | 1 | <script lang="ts"> | ||
| 2 | + | // eslint-disable-next-line @typescript-eslint/no-unused-vars -- $$Props is used for Svelte type inference | |||
| 2 | type $$Props = { | 3 | type $$Props = { | ||
| 3 | data: { | 4 | data: { | ||
| 4 | text: string; | 5 | text: string; | ||
M src/lib/components/user-preferences-modal.svelte +3 -6
| @@ -18,12 +18,9 @@ | |||
| 18 | 18 | let preferences = $state<UserTextPreferences>({ ...DEFAULT_USER_PREFERENCES }); | |
| 19 | 19 | let currentStyle = $state<TextStyle>({ ...DEFAULT_TEXT_STYLE }); | |
| 20 | 20 | let isSaving = $state(false); | |
| 21 | - | let hasChanges = $state(false); | |
| 22 | - | ||
| 23 | - | // Track changes | |
| 24 | - | $effect(() => { | |
| 25 | - | hasChanges = JSON.stringify(preferences) !== JSON.stringify(DEFAULT_USER_PREFERENCES); | |
| 26 | - | }); | |
| 21 | + | let hasChanges = $derived( | |
| 22 | + | JSON.stringify(preferences) !== JSON.stringify(DEFAULT_USER_PREFERENCES) | |
| 23 | + | ); | |
| 27 | 24 | ||
| 28 | 25 | // Update preferences when style changes | |
| 29 | 26 | $effect(() => { | |
| @@ -18,12 +18,9 @@ | |||||
| 18 | let preferences = $state<UserTextPreferences>({ ...DEFAULT_USER_PREFERENCES }); | 18 | let preferences = $state<UserTextPreferences>({ ...DEFAULT_USER_PREFERENCES }); | ||
| 19 | let currentStyle = $state<TextStyle>({ ...DEFAULT_TEXT_STYLE }); | 19 | let currentStyle = $state<TextStyle>({ ...DEFAULT_TEXT_STYLE }); | ||
| 20 | let isSaving = $state(false); | 20 | let isSaving = $state(false); | ||
| 21 | - | let hasChanges = $state(false); | 21 | + | let hasChanges = $derived( |
| 22 | - | 22 | + | JSON.stringify(preferences) !== JSON.stringify(DEFAULT_USER_PREFERENCES) | |
| 23 | - | // Track changes | 23 | + | ); |
| 24 | - | $effect(() => { | |||
| 25 | - | hasChanges = JSON.stringify(preferences) !== JSON.stringify(DEFAULT_USER_PREFERENCES); | |||
| 26 | - | }); | |||
| 27 | 24 | ||||
| 28 | // Update preferences when style changes | 25 | // Update preferences when style changes | ||
| 29 | $effect(() => { | 26 | $effect(() => { | ||
M src/lib/states/chat.svelte.ts +6 -7
| @@ -5,6 +5,7 @@ import type { | |||
| 5 | 5 | SendMessageResponse, | |
| 6 | 6 | GetMessagesResponse | |
| 7 | 7 | } from '../types/payloads'; | |
| 8 | + | import type { TextStyle } from '../types/text-formatting'; | |
| 8 | 9 | import { invalidate } from '$app/navigation'; | |
| 9 | 10 | import { env } from '$env/dynamic/public'; | |
| 10 | 11 | ||
| @@ -157,6 +158,7 @@ class ChatState { | |||
| 157 | 158 | ||
| 158 | 159 | console.debug('Fetched room buddy list:', data.buddyList); | |
| 159 | 160 | // Merge fetched buddy list with existing cached users to preserve user details even if offline | |
| 161 | + | // eslint-disable-next-line svelte/prefer-svelte-reactivity -- temporary Map for merging, not reactive state | |
| 160 | 162 | const mergedUsers = new Map<string, (typeof data.buddyList)[0]>(); | |
| 161 | 163 | // Add existing cached users | |
| 162 | 164 | Object.values(this.userCache).forEach((user) => mergedUsers.set(user.id, user)); | |
| @@ -429,6 +431,7 @@ class ChatState { | |||
| 429 | 431 | async initializeMessages() { | |
| 430 | 432 | try { | |
| 431 | 433 | // Build the URL with appropriate parameters | |
| 434 | + | // eslint-disable-next-line svelte/prefer-svelte-reactivity -- URLSearchParams for URL building, not reactive state | |
| 432 | 435 | const params = new URLSearchParams(); | |
| 433 | 436 | if (!this.currentUser) { | |
| 434 | 437 | params.append('public', 'true'); | |
| @@ -498,7 +501,7 @@ class ChatState { | |||
| 498 | 501 | async sendMessage( | |
| 499 | 502 | content: string, | |
| 500 | 503 | type: Message['type'] = 'chat', | |
| 501 | - | textStyle?: any | |
| 504 | + | textStyle?: TextStyle | |
| 502 | 505 | ): Promise<SendMessageResponse> { | |
| 503 | 506 | const user = this.getCurrentUser(); | |
| 504 | 507 | if (!user) { | |
| @@ -514,14 +517,10 @@ class ChatState { | |||
| 514 | 517 | content, | |
| 515 | 518 | type, | |
| 516 | 519 | userId: user.id, | |
| 517 | - | chatRoomId: DEFAULT_CHAT_ROOM_ID | |
| 520 | + | chatRoomId: DEFAULT_CHAT_ROOM_ID, | |
| 521 | + | styleData: textStyle ? JSON.stringify(textStyle) : undefined | |
| 518 | 522 | }; | |
| 519 | 523 | ||
| 520 | - | // Add styleData if textStyle is provided | |
| 521 | - | if (textStyle) { | |
| 522 | - | (payload as any).styleData = JSON.stringify(textStyle); | |
| 523 | - | } | |
| 524 | - | ||
| 525 | 524 | console.debug('Sending message with payload:', payload); | |
| 526 | 525 | ||
| 527 | 526 | const response = await fetch('/api/chat/messages', { | |
| @@ -5,6 +5,7 @@ import type { | |||||
| 5 | SendMessageResponse, | 5 | SendMessageResponse, | ||
| 6 | GetMessagesResponse | 6 | GetMessagesResponse | ||
| 7 | } from '../types/payloads'; | 7 | } from '../types/payloads'; | ||
| 8 | + | import type { TextStyle } from '../types/text-formatting'; | |||
| 8 | import { invalidate } from '$app/navigation'; | 9 | import { invalidate } from '$app/navigation'; | ||
| 9 | import { env } from '$env/dynamic/public'; | 10 | import { env } from '$env/dynamic/public'; | ||
| 10 | 11 | ||||
| @@ -157,6 +158,7 @@ class ChatState { | |||||
| 157 | 158 | ||||
| 158 | console.debug('Fetched room buddy list:', data.buddyList); | 159 | console.debug('Fetched room buddy list:', data.buddyList); | ||
| 159 | // Merge fetched buddy list with existing cached users to preserve user details even if offline | 160 | // Merge fetched buddy list with existing cached users to preserve user details even if offline | ||
| 161 | + | // eslint-disable-next-line svelte/prefer-svelte-reactivity -- temporary Map for merging, not reactive state | |||
| 160 | const mergedUsers = new Map<string, (typeof data.buddyList)[0]>(); | 162 | const mergedUsers = new Map<string, (typeof data.buddyList)[0]>(); | ||
| 161 | // Add existing cached users | 163 | // Add existing cached users | ||
| 162 | Object.values(this.userCache).forEach((user) => mergedUsers.set(user.id, user)); | 164 | Object.values(this.userCache).forEach((user) => mergedUsers.set(user.id, user)); | ||
| @@ -429,6 +431,7 @@ class ChatState { | |||||
| 429 | async initializeMessages() { | 431 | async initializeMessages() { | ||
| 430 | try { | 432 | try { | ||
| 431 | // Build the URL with appropriate parameters | 433 | // Build the URL with appropriate parameters | ||
| 434 | + | // eslint-disable-next-line svelte/prefer-svelte-reactivity -- URLSearchParams for URL building, not reactive state | |||
| 432 | const params = new URLSearchParams(); | 435 | const params = new URLSearchParams(); | ||
| 433 | if (!this.currentUser) { | 436 | if (!this.currentUser) { | ||
| 434 | params.append('public', 'true'); | 437 | params.append('public', 'true'); | ||
| @@ -498,7 +501,7 @@ class ChatState { | |||||
| 498 | async sendMessage( | 501 | async sendMessage( | ||
| 499 | content: string, | 502 | content: string, | ||
| 500 | type: Message['type'] = 'chat', | 503 | type: Message['type'] = 'chat', | ||
| 501 | - | textStyle?: any | 504 | + | textStyle?: TextStyle |
| 502 | ): Promise<SendMessageResponse> { | 505 | ): Promise<SendMessageResponse> { | ||
| 503 | const user = this.getCurrentUser(); | 506 | const user = this.getCurrentUser(); | ||
| 504 | if (!user) { | 507 | if (!user) { | ||
| @@ -514,14 +517,10 @@ class ChatState { | |||||
| 514 | content, | 517 | content, | ||
| 515 | type, | 518 | type, | ||
| 516 | userId: user.id, | 519 | userId: user.id, | ||
| 517 | - | chatRoomId: DEFAULT_CHAT_ROOM_ID | 520 | + | chatRoomId: DEFAULT_CHAT_ROOM_ID, |
| 521 | + | styleData: textStyle ? JSON.stringify(textStyle) : undefined | |||
| 518 | }; | 522 | }; | ||
| 519 | 523 | ||||
| 520 | - | // Add styleData if textStyle is provided | |||
| 521 | - | if (textStyle) { | |||
| 522 | - | (payload as any).styleData = JSON.stringify(textStyle); | |||
| 523 | - | } | |||
| 524 | - | ||||
| 525 | console.debug('Sending message with payload:', payload); | 524 | console.debug('Sending message with payload:', payload); | ||
| 526 | 525 | ||||
| 527 | const response = await fetch('/api/chat/messages', { | 526 | const response = await fetch('/api/chat/messages', { | ||
M src/lib/utils/security-tests.ts +3 -3
| @@ -175,7 +175,7 @@ async function runCSSInjectionTests(): Promise<SecurityTestResult[]> { | |||
| 175 | 175 | ||
| 176 | 176 | for (const vector of CSS_INJECTION_VECTORS) { | |
| 177 | 177 | try { | |
| 178 | - | const testStyle = { ...DEFAULT_TEXT_STYLE, color: 'red' as any }; | |
| 178 | + | const testStyle = { ...DEFAULT_TEXT_STYLE, color: 'red' }; | |
| 179 | 179 | const output = await formatText(`Test with style`, testStyle, true); | |
| 180 | 180 | const vulnerabilities = detectVulnerabilities(output); | |
| 181 | 181 | ||
| @@ -324,7 +324,7 @@ export async function quickSecurityCheck(input: string): Promise<boolean> { | |||
| 324 | 324 | const output = await formatText(input, DEFAULT_TEXT_STYLE, true); | |
| 325 | 325 | const vulnerabilities = detectVulnerabilities(output); | |
| 326 | 326 | return vulnerabilities.length === 0; | |
| 327 | - | } catch (error) { | |
| 327 | + | } catch { | |
| 328 | 328 | // Errors on malicious input are acceptable | |
| 329 | 329 | return true; | |
| 330 | 330 | } | |
| @@ -340,7 +340,7 @@ export function testStyleValidation(): boolean { | |||
| 340 | 340 | // Invalid styles (should be sanitized) | |
| 341 | 341 | { fontFamily: 'malicious-font', fontSize: 999, color: 'invalid-color' }, | |
| 342 | 342 | { fontFamily: 'tahoma', fontSize: -10, color: 'red' }, | |
| 343 | - | { fontFamily: 'tahoma', fontSize: 'invalid' as any, color: 'red' } | |
| 343 | + | { fontFamily: 'tahoma', fontSize: 'invalid' as unknown as number, color: 'red' } | |
| 344 | 344 | ]; | |
| 345 | 345 | ||
| 346 | 346 | for (const testCase of testCases) { | |
| @@ -175,7 +175,7 @@ async function runCSSInjectionTests(): Promise<SecurityTestResult[]> { | |||||
| 175 | 175 | ||||
| 176 | for (const vector of CSS_INJECTION_VECTORS) { | 176 | for (const vector of CSS_INJECTION_VECTORS) { | ||
| 177 | try { | 177 | try { | ||
| 178 | - | const testStyle = { ...DEFAULT_TEXT_STYLE, color: 'red' as any }; | 178 | + | const testStyle = { ...DEFAULT_TEXT_STYLE, color: 'red' }; |
| 179 | const output = await formatText(`Test with style`, testStyle, true); | 179 | const output = await formatText(`Test with style`, testStyle, true); | ||
| 180 | const vulnerabilities = detectVulnerabilities(output); | 180 | const vulnerabilities = detectVulnerabilities(output); | ||
| 181 | 181 | ||||
| @@ -324,7 +324,7 @@ export async function quickSecurityCheck(input: string): Promise<boolean> { | |||||
| 324 | const output = await formatText(input, DEFAULT_TEXT_STYLE, true); | 324 | const output = await formatText(input, DEFAULT_TEXT_STYLE, true); | ||
| 325 | const vulnerabilities = detectVulnerabilities(output); | 325 | const vulnerabilities = detectVulnerabilities(output); | ||
| 326 | return vulnerabilities.length === 0; | 326 | return vulnerabilities.length === 0; | ||
| 327 | - | } catch (error) { | 327 | + | } catch { |
| 328 | // Errors on malicious input are acceptable | 328 | // Errors on malicious input are acceptable | ||
| 329 | return true; | 329 | return true; | ||
| 330 | } | 330 | } | ||
| @@ -340,7 +340,7 @@ export function testStyleValidation(): boolean { | |||||
| 340 | // Invalid styles (should be sanitized) | 340 | // Invalid styles (should be sanitized) | ||
| 341 | { fontFamily: 'malicious-font', fontSize: 999, color: 'invalid-color' }, | 341 | { fontFamily: 'malicious-font', fontSize: 999, color: 'invalid-color' }, | ||
| 342 | { fontFamily: 'tahoma', fontSize: -10, color: 'red' }, | 342 | { fontFamily: 'tahoma', fontSize: -10, color: 'red' }, | ||
| 343 | - | { fontFamily: 'tahoma', fontSize: 'invalid' as any, color: 'red' } | 343 | + | { fontFamily: 'tahoma', fontSize: 'invalid' as unknown as number, color: 'red' } |
| 344 | ]; | 344 | ]; | ||
| 345 | 345 | ||||
| 346 | for (const testCase of testCases) { | 346 | for (const testCase of testCases) { | ||
M src/lib/utils/text-formatter.ts +3 -12
| @@ -4,7 +4,7 @@ import remarkRehype from 'remark-rehype'; | |||
| 4 | 4 | import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; | |
| 5 | 5 | import rehypeStringify from 'rehype-stringify'; | |
| 6 | 6 | import type { TextStyle } from '../types/text-formatting'; | |
| 7 | - | import { generateCSSStyle, validateTextStyle } from '../types/text-formatting'; | |
| 7 | + | import { validateTextStyle } from '../types/text-formatting'; | |
| 8 | 8 | ||
| 9 | 9 | // Define allowed HTML tags and attributes for security | |
| 10 | 10 | const ALLOWED_SCHEMA = { | |
| @@ -25,15 +25,6 @@ const ALLOWED_SCHEMA = { | |||
| 25 | 25 | } | |
| 26 | 26 | }; | |
| 27 | 27 | ||
| 28 | - | // Custom rehype plugin to apply text styles | |
| 29 | - | function rehypeApplyStyles(userStyle: TextStyle) { | |
| 30 | - | return function (tree: any) { | |
| 31 | - | // We don't need to apply styles here anymore | |
| 32 | - | // The formatted-message component handles all styling | |
| 33 | - | return tree; | |
| 34 | - | }; | |
| 35 | - | } | |
| 36 | - | ||
| 37 | 28 | // BBCode-style formatting patterns | |
| 38 | 29 | const BB_CODE_PATTERNS: Array<{ | |
| 39 | 30 | pattern: RegExp; | |
| @@ -130,8 +121,8 @@ export async function formatText( | |||
| 130 | 121 | // If text is empty, return empty string | |
| 131 | 122 | if (!text) return ''; | |
| 132 | 123 | ||
| 133 | - | // Validate and apply user style | |
| 134 | - | const validatedStyle = validateTextStyle(userStyle || {}); | |
| 124 | + | // Validate user style (ensures safe values) | |
| 125 | + | validateTextStyle(userStyle || {}); | |
| 135 | 126 | ||
| 136 | 127 | // Preprocess the text for formatting if allowed | |
| 137 | 128 | let processedText = text; | |
| @@ -4,7 +4,7 @@ import remarkRehype from 'remark-rehype'; | |||||
| 4 | import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; | 4 | import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; | ||
| 5 | import rehypeStringify from 'rehype-stringify'; | 5 | import rehypeStringify from 'rehype-stringify'; | ||
| 6 | import type { TextStyle } from '../types/text-formatting'; | 6 | import type { TextStyle } from '../types/text-formatting'; | ||
| 7 | - | import { generateCSSStyle, validateTextStyle } from '../types/text-formatting'; | 7 | + | import { validateTextStyle } from '../types/text-formatting'; |
| 8 | 8 | ||||
| 9 | // Define allowed HTML tags and attributes for security | 9 | // Define allowed HTML tags and attributes for security | ||
| 10 | const ALLOWED_SCHEMA = { | 10 | const ALLOWED_SCHEMA = { | ||
| @@ -25,15 +25,6 @@ const ALLOWED_SCHEMA = { | |||||
| 25 | } | 25 | } | ||
| 26 | }; | 26 | }; | ||
| 27 | 27 | ||||
| 28 | - | // Custom rehype plugin to apply text styles | |||
| 29 | - | function rehypeApplyStyles(userStyle: TextStyle) { | |||
| 30 | - | return function (tree: any) { | |||
| 31 | - | // We don't need to apply styles here anymore | |||
| 32 | - | // The formatted-message component handles all styling | |||
| 33 | - | return tree; | |||
| 34 | - | }; | |||
| 35 | - | } | |||
| 36 | - | ||||
| 37 | // BBCode-style formatting patterns | 28 | // BBCode-style formatting patterns | ||
| 38 | const BB_CODE_PATTERNS: Array<{ | 29 | const BB_CODE_PATTERNS: Array<{ | ||
| 39 | pattern: RegExp; | 30 | pattern: RegExp; | ||
| @@ -130,8 +121,8 @@ export async function formatText( | |||||
| 130 | // If text is empty, return empty string | 121 | // If text is empty, return empty string | ||
| 131 | if (!text) return ''; | 122 | if (!text) return ''; | ||
| 132 | 123 | ||||
| 133 | - | // Validate and apply user style | 124 | + | // Validate user style (ensures safe values) |
| 134 | - | const validatedStyle = validateTextStyle(userStyle || {}); | 125 | + | validateTextStyle(userStyle || {}); |
| 135 | 126 | ||||
| 136 | // Preprocess the text for formatting if allowed | 127 | // Preprocess the text for formatting if allowed | ||
| 137 | let processedText = text; | 128 | let processedText = text; | ||
M src/lib/validation/password.ts +2 -2
| @@ -58,7 +58,7 @@ export function createPasswordSchema( | |||
| 58 | 58 | ||
| 59 | 59 | if (constraints.requireSpecialChars) { | |
| 60 | 60 | schema = schema.regex( | |
| 61 | - | /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/, | |
| 61 | + | /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/, | |
| 62 | 62 | PASSWORD_ERROR_MESSAGES.requireSpecialChars | |
| 63 | 63 | ); | |
| 64 | 64 | } | |
| @@ -169,7 +169,7 @@ export function validatePasswordStrength( | |||
| 169 | 169 | } | |
| 170 | 170 | ||
| 171 | 171 | if (constraints.requireSpecialChars) { | |
| 172 | - | const hasSpecialChars = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password); | |
| 172 | + | const hasSpecialChars = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password); | |
| 173 | 173 | if (!hasSpecialChars) errors.push(PASSWORD_ERROR_MESSAGES.requireSpecialChars); | |
| 174 | 174 | requirements.push({ | |
| 175 | 175 | met: hasSpecialChars, | |
| @@ -58,7 +58,7 @@ export function createPasswordSchema( | |||||
| 58 | 58 | ||||
| 59 | if (constraints.requireSpecialChars) { | 59 | if (constraints.requireSpecialChars) { | ||
| 60 | schema = schema.regex( | 60 | schema = schema.regex( | ||
| 61 | - | /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/, | 61 | + | /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/, |
| 62 | PASSWORD_ERROR_MESSAGES.requireSpecialChars | 62 | PASSWORD_ERROR_MESSAGES.requireSpecialChars | ||
| 63 | ); | 63 | ); | ||
| 64 | } | 64 | } | ||
| @@ -169,7 +169,7 @@ export function validatePasswordStrength( | |||||
| 169 | } | 169 | } | ||
| 170 | 170 | ||||
| 171 | if (constraints.requireSpecialChars) { | 171 | if (constraints.requireSpecialChars) { | ||
| 172 | - | const hasSpecialChars = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password); | 172 | + | const hasSpecialChars = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password); |
| 173 | if (!hasSpecialChars) errors.push(PASSWORD_ERROR_MESSAGES.requireSpecialChars); | 173 | if (!hasSpecialChars) errors.push(PASSWORD_ERROR_MESSAGES.requireSpecialChars); | ||
| 174 | requirements.push({ | 174 | requirements.push({ | ||
| 175 | met: hasSpecialChars, | 175 | met: hasSpecialChars, | ||
M src/routes/+layout.svelte +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | <script lang="ts"> | |
| 2 | 2 | import '../app.css'; | |
| 3 | 3 | import { chatState } from '$lib/states/chat.svelte'; | |
| 4 | - | import { onMount, onDestroy } from 'svelte'; | |
| 4 | + | import { onMount } from 'svelte'; | |
| 5 | 5 | import { browser } from '$app/environment'; | |
| 6 | 6 | ||
| 7 | 7 | let { children, data } = $props(); | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | <script lang="ts"> | 1 | <script lang="ts"> | ||
| 2 | import '../app.css'; | 2 | import '../app.css'; | ||
| 3 | import { chatState } from '$lib/states/chat.svelte'; | 3 | import { chatState } from '$lib/states/chat.svelte'; | ||
| 4 | - | import { onMount, onDestroy } from 'svelte'; | 4 | + | import { onMount } from 'svelte'; |
| 5 | import { browser } from '$app/environment'; | 5 | import { browser } from '$app/environment'; | ||
| 6 | 6 | ||||
| 7 | let { children, data } = $props(); | 7 | let { children, data } = $props(); | ||
M src/routes/+page.server.ts +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | import type { PageServerLoad } from './$types'; | |
| 2 | 2 | import { validateSessionToken } from '$lib/api/session.server'; | |
| 3 | 3 | import db from '$lib/db/db.server'; | |
| 4 | - | import { userTextPreferences, users, messages } from '$lib/db/schema'; | |
| 4 | + | import { messages } from '$lib/db/schema'; | |
| 5 | 5 | import { eq, desc } from 'drizzle-orm'; | |
| 6 | 6 | import { DEFAULT_TEXT_STYLE, type TextStyle } from '$lib/types/text-formatting'; | |
| 7 | 7 | ||
| @@ -1,7 +1,7 @@ | |||||
| 1 | import type { PageServerLoad } from './$types'; | 1 | import type { PageServerLoad } from './$types'; | ||
| 2 | import { validateSessionToken } from '$lib/api/session.server'; | 2 | import { validateSessionToken } from '$lib/api/session.server'; | ||
| 3 | import db from '$lib/db/db.server'; | 3 | import db from '$lib/db/db.server'; | ||
| 4 | - | import { userTextPreferences, users, messages } from '$lib/db/schema'; | 4 | + | import { messages } from '$lib/db/schema'; |
| 5 | import { eq, desc } from 'drizzle-orm'; | 5 | import { eq, desc } from 'drizzle-orm'; | ||
| 6 | import { DEFAULT_TEXT_STYLE, type TextStyle } from '$lib/types/text-formatting'; | 6 | import { DEFAULT_TEXT_STYLE, type TextStyle } from '$lib/types/text-formatting'; | ||
| 7 | 7 | ||||
M src/routes/api/register/+server.ts +3 -3
| @@ -8,7 +8,6 @@ import { createLogger } from '$lib/utils/logger.server'; | |||
| 8 | 8 | import { eq } from 'drizzle-orm/sql'; | |
| 9 | 9 | ||
| 10 | 10 | const log = createLogger('register-server'); | |
| 11 | - | const isDev = process.env.NODE_ENV === 'development'; | |
| 12 | 11 | ||
| 13 | 12 | // In-memory map to track failed captcha attempts per IP | |
| 14 | 13 | const captchaAttempts = new Map<string, { count: number; lastAttempt: number }>(); | |
| @@ -74,8 +73,9 @@ export const POST: RequestHandler = async ({ request }) => { | |||
| 74 | 73 | captchaAnswer, | |
| 75 | 74 | turnstileToken | |
| 76 | 75 | }); | |
| 77 | - | } catch (err: any) { | |
| 78 | - | const errorMessage = err.errors?.[0]?.message || 'Invalid input data'; | |
| 76 | + | } catch (err: unknown) { | |
| 77 | + | const zodError = err as { errors?: Array<{ message?: string }> }; | |
| 78 | + | const errorMessage = zodError.errors?.[0]?.message || 'Invalid input data'; | |
| 79 | 79 | log.warn('Registration validation failed', { error: errorMessage }); | |
| 80 | 80 | return new Response(JSON.stringify({ error: errorMessage } as RegisterResponseError), { | |
| 81 | 81 | status: 400 | |
| @@ -8,7 +8,6 @@ import { createLogger } from '$lib/utils/logger.server'; | |||||
| 8 | import { eq } from 'drizzle-orm/sql'; | 8 | import { eq } from 'drizzle-orm/sql'; | ||
| 9 | 9 | ||||
| 10 | const log = createLogger('register-server'); | 10 | const log = createLogger('register-server'); | ||
| 11 | - | const isDev = process.env.NODE_ENV === 'development'; | |||
| 12 | 11 | ||||
| 13 | // In-memory map to track failed captcha attempts per IP | 12 | // In-memory map to track failed captcha attempts per IP | ||
| 14 | const captchaAttempts = new Map<string, { count: number; lastAttempt: number }>(); | 13 | const captchaAttempts = new Map<string, { count: number; lastAttempt: number }>(); | ||
| @@ -74,8 +73,9 @@ export const POST: RequestHandler = async ({ request }) => { | |||||
| 74 | captchaAnswer, | 73 | captchaAnswer, | ||
| 75 | turnstileToken | 74 | turnstileToken | ||
| 76 | }); | 75 | }); | ||
| 77 | - | } catch (err: any) { | 76 | + | } catch (err: unknown) { |
| 78 | - | const errorMessage = err.errors?.[0]?.message || 'Invalid input data'; | 77 | + | const zodError = err as { errors?: Array<{ message?: string }> }; |
| 78 | + | const errorMessage = zodError.errors?.[0]?.message || 'Invalid input data'; | |||
| 79 | log.warn('Registration validation failed', { error: errorMessage }); | 79 | log.warn('Registration validation failed', { error: errorMessage }); | ||
| 80 | return new Response(JSON.stringify({ error: errorMessage } as RegisterResponseError), { | 80 | return new Response(JSON.stringify({ error: errorMessage } as RegisterResponseError), { | ||
| 81 | status: 400 | 81 | status: 400 | ||
M src/routes/api/sse/+server.ts +0 -1
| @@ -1,6 +1,5 @@ | |||
| 1 | 1 | // At the top of the file, before any code | |
| 2 | 2 | declare global { | |
| 3 | - | // eslint-disable-next-line no-var | |
| 4 | 3 | var __sseIntervalsStarted: boolean; | |
| 5 | 4 | } | |
| 6 | 5 | ||
| @@ -1,6 +1,5 @@ | |||||
| 1 | // At the top of the file, before any code | 1 | // At the top of the file, before any code | ||
| 2 | declare global { | 2 | declare global { | ||
| 3 | - | // eslint-disable-next-line no-var | |||
| 4 | var __sseIntervalsStarted: boolean; | 3 | var __sseIntervalsStarted: boolean; | ||
| 5 | } | 4 | } | ||
| 6 | 5 | ||||