fix: all svelte diagnostics.

This commit is contained in:
Shibo Lyu 2025-03-19 01:48:54 +08:00
parent 4129cac511
commit b96bdf7ff3
10 changed files with 159 additions and 107 deletions

View file

@ -1,6 +1,6 @@
import { derived, readable, type Readable } from 'svelte/store';
import type { BlahChatServerConnection } from './blah/connection/chatServer';
import type { BlahRichText } from './richText';
import type { BlahRichText } from '@blah-im/core/richText';
import { messageFromBlah, type Chat, type Message, type User } from './types';
import { BlahError } from './blah/connection/error';

View file

@ -4,26 +4,26 @@
import { expoOut } from 'svelte/easing';
import { scale } from 'svelte/transition';
interface Props {
class?: $$Props['class'];
children?: import('svelte').Snippet;
[key: string]: any
interface Props extends DropdownMenuContentProps {
class?: string;
}
let { class: className = '', children, ...rest }: Props = $props();
const fullClassName = tw(
'group border-ss-secondary bg-sb-overlay min-w-32 origin-top rounded-lg border p-1 shadow-lg',
className
);
</script>
<DropdownMenu.Content
class={tw(
'group min-w-32 origin-top rounded-lg border border-ss-secondary bg-sb-overlay p-1 shadow-lg',
className
)}
sideOffset={4}
transition={scale}
transitionConfig={{ start: 0.96, duration: 300, easing: expoOut }}
{...rest}
>
{@render children?.()}
<DropdownMenu.Content class={fullClassName} sideOffset={4} forceMount {...rest}>
{#snippet child({ wrapperProps, props, open })}
{#if open}
<div {...wrapperProps}>
<div {...props} transition:scale={{ start: 0.96, duration: 300, easing: expoOut }}>
{@render children?.()}
</div>
</div>
{/if}
{/snippet}
</DropdownMenu.Content>

View file

@ -4,27 +4,27 @@
import InputFrame from '$lib/components/InputFrame.svelte';
import { tw } from '$lib/tw';
interface Props {
delta?: Delta | null;
plainText?: string | undefined;
delta?: Delta;
plainText?: string;
keyboardSubmitMethod?: 'enter' | 'shiftEnter' | undefined;
onKeyboardSubmit?: () => void;
placeholder?: string;
editor: Editor | undefined;
editor?: Editor;
class?: string;
children?: import('svelte').Snippet;
}
let {
delta = $bindable(null),
delta = $bindable(undefined),
plainText = $bindable(undefined),
keyboardSubmitMethod = undefined,
onKeyboardSubmit,
placeholder = '',
editor = $bindable(),
class: className = '',
children
}: Props = $props();
const loadClientComponent = async () => {
if (!browser) return;
@ -38,16 +38,16 @@
<div class="rich-text opacity-50">
<p>{placeholder}</p>
</div>
{:then Input}
<Input
{:then ClientInput}
<ClientInput
bind:delta
bind:plainText
{placeholder}
bind:editor
{keyboardSubmitMethod}
on:keyboardSubmit
{onKeyboardSubmit}
>
{@render children?.()}
</Input>
</ClientInput>
{/await}
</InputFrame>

View file

@ -4,6 +4,7 @@
interface Props {
delta?: Delta;
editor?: Editor;
plainText?: string | undefined;
placeholder?: string;
keyboardSubmitMethod?: 'enter' | 'shiftEnter' | undefined;
@ -13,6 +14,7 @@
let {
delta = $bindable(new Delta()),
editor = $bindable(initEditor()),
plainText = $bindable(undefined),
placeholder = '',
keyboardSubmitMethod = undefined,
@ -20,8 +22,6 @@
children
}: Props = $props();
let editor: Editor = $state(initEditor());
function initEditor() {
const modules = keyboardSubmitMethod
? {

View file

@ -3,58 +3,37 @@ import type { Message, User } from '$lib/types';
import { getRandomUser } from './users';
const messageContents: BlahRichText[] = [
[['更好的例子可能是link和hashtag不應該共存']],
[['理論上mono是可以BIUS的只是可能不太常見']],
[[['這個是一個link', { link: 'https://google.com' }]]],
[['這是一個', ['#hashtag', { hashtag: true }]]],
['更好的例子可能是link和hashtag不應該共存'],
['理論上mono是可以BIUS的只是可能不太常見'],
[['這個是一個link', { link: 'https://google.com' }]],
['這是一個', ['#hashtag', { tag: true }]],
['這是一個', ['link', { link: 'https://google.com' }], '和一個', ['#hashtag', { tag: true }]],
['可以, 反正我都手写了('],
['但我們也可以約定這種entity一定要有plain text fallback'],
['這樣的話我們就可以在不支援的地方用plain text fallback'],
['有可能有僅attribute的run'],
[
[
'這是一個',
['link', { link: 'https://google.com' }],
'和一個',
['#hashtag', { hashtag: true }]
]
'我现在是约定 text piece 一定非空,也就是说空字符串应该是空数组(但能不能真的这么发言要打个问号)'
],
[['可以, 反正我都手写了(']],
[['但我們也可以約定這種entity一定要有plain text fallback']],
[['這樣的話我們就可以在不支援的地方用plain text fallback']],
[['有可能有僅attribute的run']],
['确实合并相邻的 run 就可以 canonicalize'],
['比如我可能不希望往数据库里这么存 json ,还需要考虑检索之类的'],
['是我蠢了'],
['我觉得这个问题是因为我们没有定义好什么是一个 run'],
['目标是如果前端/后端使用和协议不一致的格式存储的话 roundtrip 后一致'],
[
[
'我现在是约定 text piece 一定非空,也就是说空字符串应该是空数组(但能不能真的这么发言要打个问号)'
]
'你们这个 canonicalize 真的靠谱吗,,,感觉哪怕跑了一遍以后也会有多个不同 message 视觉效果相同的情况'
],
[['确实合并相邻的 run 就可以 canonicalize']],
[['比如我可能不希望往数据库里这么存 json ,还需要考虑检索之类的']],
[['是我蠢了']],
[['我觉得这个问题是因为我们没有定义好什么是一个 run']],
[['目标是如果前端/后端使用和协议不一致的格式存储的话 roundtrip 后一致']],
['稍微有点烦('],
[
[
'你们这个 canonicalize 真的靠谱吗,,,感觉哪怕跑了一遍以后也会有多个不同 message 视觉效果相同的情况'
]
],
[['稍微有点烦(']],
[
[
[
'奥运会究竟该如何报道?中国媒体的表现真的这么不堪吗?',
{ link: 'https://www.bilibili.com/video/BV1TZ421L7hj/' }
]
'奥运会究竟该如何报道?中国媒体的表现真的这么不堪吗?',
{ link: 'https://www.bilibili.com/video/BV1TZ421L7hj/' }
],
[
'在视频中,可爸深入分析了中国媒体在巴黎奥运会中的表现,探讨了媒体人员的规模、采访类型、团队构成以及值得称赞与批评的采访案例。文章指出,尽管注册媒体工作者数量庞大,但真正的记者数量相对较少,且面临专业能力不足和流量逻辑冲击等问题。同时,作者强调了媒体在维护国家荣誉和传递奥运精神方面的重要性,呼吁媒体挖掘运动员故事,以建立观众与运动员之间的情感联系。这篇文章为了解当前中国体育媒体的现状和未来发展提供了深刻的见解和反思。'
],
[''],
['---'],
[''],
['非常好的视频。强烈推荐观看。']
'\n在视频中可爸深入分析了中国媒体在巴黎奥运会中的表现探讨了媒体人员的规模、采访类型、团队构成以及值得称赞与批评的采访案例。文章指出尽管注册媒体工作者数量庞大但真正的记者数量相对较少且面临专业能力不足和流量逻辑冲击等问题。同时作者强调了媒体在维护国家荣誉和传递奥运精神方面的重要性呼吁媒体挖掘运动员故事以建立观众与运动员之间的情感联系。这篇文章为了解当前中国体育媒体的现状和未来发展提供了深刻的见解和反思。\n---\n\n非常好的视频。强烈推荐观看。'
],
[['pieces:[], attrs:[] 两者等长。然后判断合并就是 attrs 有没有相邻重复元素']],
['pieces:[], attrs:[] 两者等长。然后判断合并就是 attrs 有没有相邻重复元素'],
[
[
'记้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎得้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎做้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎ ้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎o้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎v้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎e้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎r้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎f้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎l้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎o้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎w้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎ ้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎h้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎i้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎d้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎d้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎e้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎n้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎'
]
'记้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎得้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎做้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎ ้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎o้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎v้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎e้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎r้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎f้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎l้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎o้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎w้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎ ้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎h้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎i้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎d้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎d้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎e้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎n้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎้้้้๎๎๎๎'
]
];

79
src/lib/richText.ts Normal file
View file

@ -0,0 +1,79 @@
import type { BlahRichText, BlahRichTextSpanAttributes } from '@blah-im/core/richText';
import canonicalize from 'canonicalize';
import type { AttributeMap, Delta } from 'typewriter-editor';
function isObjectEmpty(obj: object) {
for (const _ in obj) return false;
return true;
}
function deltaAttributesToBlahRichTextSpanAttributes(
attributes?: AttributeMap
): BlahRichTextSpanAttributes | null {
if (!attributes) return null;
const blahRichTextSpanAttributes: BlahRichTextSpanAttributes = {};
if (attributes.bold) blahRichTextSpanAttributes.b = true;
if (attributes.italic) blahRichTextSpanAttributes.i = true;
if (attributes.code) blahRichTextSpanAttributes.m = true;
if (attributes.link) blahRichTextSpanAttributes.link = attributes.link;
if (attributes.underline) blahRichTextSpanAttributes.u = true;
if (attributes.strikethrough) blahRichTextSpanAttributes.s = true;
return isObjectEmpty(blahRichTextSpanAttributes) ? null : blahRichTextSpanAttributes;
}
export function deltaToBlahRichText(delta: Delta, trim: boolean = true): BlahRichText {
const spans: BlahRichText = [];
let lastText = '';
let lastAttributes: BlahRichTextSpanAttributes | null = null;
let canonicalizedLastAttributes: string = 'null';
function commitSpan(trim?: 'start' | 'end'): boolean {
const trimmedLastText =
trim === 'start' ? lastText.trimStart() : trim === 'end' ? lastText.trimEnd() : lastText;
if (trimmedLastText === '') return false;
spans.push(lastAttributes === null ? trimmedLastText : [trimmedLastText, lastAttributes]);
return true;
}
let isFirstSpan = true;
for (const op of delta.ops) {
// Not sure in what cases op.insert would not be a string, but let's be safe
if (typeof op.insert !== 'string') continue;
const attributes = deltaAttributesToBlahRichTextSpanAttributes(op.attributes);
const canonicalizedAttributes = canonicalize(attributes) ?? 'null';
if (canonicalizedAttributes === canonicalizedLastAttributes) {
lastText += op.insert;
continue;
}
const commited = commitSpan(trim && isFirstSpan ? 'start' : undefined);
if (commited) isFirstSpan = false;
lastText = op.insert;
lastAttributes = attributes;
canonicalizedLastAttributes = canonicalizedAttributes;
}
const lastCommited = commitSpan(trim ? 'end' : undefined);
if (trim && !lastCommited) {
// The last segment is empty, so we need to trim the one before it
let lastSpan = spans.pop();
if (!lastSpan) return spans;
if (typeof lastSpan === 'string') {
lastSpan = lastSpan.trimEnd();
if (lastSpan !== '') spans.push(lastSpan);
} else {
lastSpan[0] = lastSpan[0].trimEnd();
if (lastSpan[0] !== '') spans.push(lastSpan);
}
}
return spans;
}

View file

@ -1,36 +1,29 @@
<script lang="ts">
import { run } from 'svelte/legacy';
import { page } from '$app/stores';
import { page } from '$app/state';
import { BlahChatServerConnection } from '$lib/blah/connection/chatServer';
import { browser } from '$app/environment';
import { chatServerConnectionPool } from '$lib/chatServers';
import ServiceMessage from '$lib/components/ServiceMessage.svelte';
import ChatPage from './ChatPage.svelte';
import { useChat } from '$lib/chat';
let roomId = $derived($page.params.chatId);
let roomId = $derived(page.params.chatId);
let serverEndpoint: string = $state('');
run(() => {
const endpointString = decodeURIComponent($page.params.server);
serverEndpoint = endpointString.startsWith('http')
? endpointString
: `https://${endpointString}`;
});
let serverEndpoint: string = $derived(normalizedServerEndpoint(page.params.server));
function normalizedServerEndpoint(serverURIComponent: string) {
const endpointString = decodeURIComponent(serverURIComponent);
return endpointString.startsWith('http') ? endpointString : `https://${endpointString}`;
}
let server: BlahChatServerConnection | null = $state();
run(() => {
if (browser) {
server = chatServerConnectionPool.getConnection(serverEndpoint);
}
let server: BlahChatServerConnection | null = $state(null);
$effect.pre(() => {
server = chatServerConnectionPool.getConnection(serverEndpoint);
});
</script>
<div class="flex h-full w-full flex-col items-center justify-center">
{#if server}
{@const { info, sectionedMessages, sendMessage } = useChat(server, roomId)}
<ChatPage {info} {sectionedMessages} on:sendMessage={(e) => sendMessage(e.detail)} />
{@const { sendMessage, ...rest } = useChat(server, roomId)}
<ChatPage {...rest} onSendMessage={sendMessage} />
{:else}
<ServiceMessage>
To view this chat, you need to connect to chat server

View file

@ -1,17 +1,21 @@
<script lang="ts">
import Button from '$lib/components/Button.svelte';
import RichTextInput from '$lib/components/RichTextInput.svelte';
import { deltaToBlahRichText, type BlahRichText } from '@blah-im/core/richText';
import { createEventDispatcher } from 'svelte';
import type { BlahRichText } from '@blah-im/core/richText';
import { deltaToBlahRichText } from '$lib/richText';
import type { Delta, Editor } from 'typewriter-editor';
let {
onSendMessage
}: {
onSendMessage: (message: BlahRichText) => void;
} = $props();
let editor: Editor | undefined = $state();
let delta: Delta | undefined = $state();
let plainText: string = $state('');
let form: HTMLFormElement | null = $state(null);
const dispatch = createEventDispatcher<{ sendMessage: BlahRichText }>();
function onKeyboardSubmit() {
editor?.select(null);
form?.requestSubmit();
@ -23,7 +27,7 @@
if (plainText.trim() === '' || !delta) return;
const brt = deltaToBlahRichText(delta);
dispatch('sendMessage', brt);
onSendMessage(brt);
plainText = '';
}
@ -58,7 +62,7 @@
placeholder="Message"
class="max-h-40 flex-1"
keyboardSubmitMethod="enter"
on:keyboardSubmit={onKeyboardSubmit}
{onKeyboardSubmit}
/>
<Button class="p-1.5" variant="primary" type="submit">
<svg

View file

@ -1,7 +1,7 @@
<script lang="ts">
import type { Readable } from 'svelte/store';
import type { Chat, Message } from '$lib/types';
import type { Chat } from '$lib/types';
import BgPattern from '$lib/components/BgPattern.svelte';
import { currentKeyPair } from '$lib/keystore';
@ -14,17 +14,14 @@
interface Props {
info: Readable<Chat>;
sectionedMessages: Readable<MessageSection[]>;
onSendMessage: (brt: BlahRichText) => void;
}
let { info, sectionedMessages }: Props = $props();
interface $$Events {
sendMessage: CustomEvent<BlahRichText>;
}
let { info, sectionedMessages, onSendMessage }: Props = $props();
</script>
<ChatHeader info={$info} outsideUnreadCount={263723} />
<BgPattern class="w-full flex-1" pattern="charlieBrown">
<ChatHistory sectionedMessages={$sectionedMessages} mySenderId={$currentKeyPair?.id} />
</BgPattern>
<ChatInput on:sendMessage />
<ChatInput {onSendMessage} />

View file

@ -1,9 +1,9 @@
<script lang="ts">
import RichTextInput from '$lib/components/RichTextInput.svelte';
import { deltaToBlahRichText } from '@blah-im/core/richText';
import { deltaToBlahRichText } from '$lib/richText';
import type { Delta } from 'typewriter-editor';
let delta: Delta = $state();
let delta: Delta | undefined = $state();
let brt = $derived(delta ? deltaToBlahRichText(delta) : null);
</script>