fix: enter to send

This commit is contained in:
Shibo Lyu 2024-09-03 15:28:39 +08:00
parent 72b962fb77
commit 431f14b35d
7 changed files with 101 additions and 42 deletions

View file

@ -7,7 +7,7 @@
<div <div
class={tw( class={tw(
'flex items-center gap-1 rounded-md px-2 py-1.5 shadow-[inset_0_1px_2px_0_rgb(0_0_0/0.05)] ring-1 ring-ss-secondary', 'flex items-center gap-1 rounded-md px-2 py-1.5 caret-accent-500 shadow-[inset_0_1px_2px_0_rgb(0_0_0/0.05)] ring-1 ring-ss-secondary',
className className
)} )}
> >

View file

@ -1,12 +1,14 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import type { Delta } from 'typewriter-editor'; import type { Delta, Editor } from 'typewriter-editor';
import InputFrame from '$lib/components/InputFrame.svelte'; import InputFrame from '$lib/components/InputFrame.svelte';
import { tw } from '$lib/tw'; import { tw } from '$lib/tw';
export let delta: Delta | null = null; export let delta: Delta | null = null;
export let plainText: string | undefined = undefined; export let plainText: string | undefined = undefined;
export let keyboardSubmitMethod: 'enter' | 'shiftEnter' | undefined = undefined;
export let placeholder: string = ''; export let placeholder: string = '';
export let editor: Editor | undefined;
let className = ''; let className = '';
export { className as class }; export { className as class };
@ -24,7 +26,15 @@
<p>{placeholder}</p> <p>{placeholder}</p>
</div> </div>
{:then Input} {:then Input}
<svelte:component this={Input} bind:delta bind:plainText {placeholder} on:keydown> <svelte:component
this={Input}
bind:delta
bind:plainText
{placeholder}
bind:editor
{keyboardSubmitMethod}
on:keyboardSubmit
>
<slot /> <slot />
</svelte:component> </svelte:component>
{/await} {/await}

View file

@ -1,11 +1,24 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte';
import { Delta, Editor, asRoot, h } from 'typewriter-editor'; import { Delta, Editor, asRoot, h } from 'typewriter-editor';
import { keyboardSubmit } from './keyboardSubmitModule';
export let delta: Delta = new Delta(); export let delta: Delta = new Delta();
export let plainText: string | undefined = undefined; export let plainText: string | undefined = undefined;
export let placeholder: string = ''; export let placeholder: string = '';
export let keyboardSubmitMethod: 'enter' | 'shiftEnter' | undefined = undefined;
const editor = new Editor(); const dispatch = createEventDispatcher<{
keyboardSubmit: void;
}>();
let editor: Editor;
function initEditor() {
const modules = keyboardSubmitMethod
? { keyboardSubmit: keyboardSubmit(() => dispatch('keyboardSubmit'), keyboardSubmitMethod) }
: undefined;
editor = new Editor({ modules });
editor.typeset.formats.add({ editor.typeset.formats.add({
name: 'underline', name: 'underline',
selector: 'span[data-weblah-brt=underline]', selector: 'span[data-weblah-brt=underline]',
@ -28,6 +41,9 @@
delta = editor.getDelta(); delta = editor.getDelta();
if (typeof plainText === 'string') plainText = editor.getText(); if (typeof plainText === 'string') plainText = editor.getText();
}); });
}
$: if (keyboardSubmitMethod || typeof keyboardSubmitMethod === 'undefined') initEditor();
$: editor.setDelta(delta ?? new Delta()); $: editor.setDelta(delta ?? new Delta());
$: if (typeof plainText === 'string' && plainText !== editor.getText()) editor.setText(plainText); $: if (typeof plainText === 'string' && plainText !== editor.getText()) editor.setText(plainText);
@ -40,7 +56,6 @@
? 'true' ? 'true'
: undefined} : undefined}
data-weblah-placeholder={placeholder} data-weblah-placeholder={placeholder}
on:keydown
role="textbox" role="textbox"
tabindex="0" tabindex="0"
> >

View file

@ -0,0 +1,15 @@
import type { ModuleInitializer } from 'typewriter-editor';
export const keyboardSubmit = function keyboardSubmit(
onSubmit: () => void,
method: 'enter' | 'shiftEnter'
): ModuleInitializer {
return () => ({
commands: {
keyboardSubmit: onSubmit
},
shortcuts: {
[method === 'enter' ? 'Enter' : 'Shift+Enter']: 'keyboardSubmit'
}
});
};

View file

@ -45,17 +45,22 @@ function deltaAttributesToBlahRichTextSpanAttributes(
return isObjectEmpty(blahRichTextSpanAttributes) ? null : blahRichTextSpanAttributes; return isObjectEmpty(blahRichTextSpanAttributes) ? null : blahRichTextSpanAttributes;
} }
export function deltaToBlahRichText(delta: Delta): BlahRichText { export function deltaToBlahRichText(delta: Delta, trim?: boolean = true): BlahRichText {
const spans: BlahRichText = []; const spans: BlahRichText = [];
let lastText = ''; let lastText = '';
let lastAttributes: BlahRichTextSpanAttributes | null = null; let lastAttributes: BlahRichTextSpanAttributes | null = null;
let canonicalizedLastAttributes: string = 'null'; let canonicalizedLastAttributes: string = 'null';
function commitSpan() { function commitSpan(trim?: 'start' | 'end'): boolean {
spans.push(lastAttributes === null ? lastText : [lastText, lastAttributes]); 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) { for (const op of delta.ops) {
// Not sure in what cases op.insert would not be a string, but let's be safe // Not sure in what cases op.insert would not be a string, but let's be safe
if (typeof op.insert !== 'string') continue; if (typeof op.insert !== 'string') continue;
@ -68,12 +73,27 @@ export function deltaToBlahRichText(delta: Delta): BlahRichText {
continue; continue;
} }
commitSpan(); const commited = commitSpan(trim && isFirstSpan ? 'start' : undefined);
if (commited) isFirstSpan = false;
lastText = op.insert; lastText = op.insert;
lastAttributes = attributes; lastAttributes = attributes;
canonicalizedLastAttributes = canonicalizedAttributes; canonicalizedLastAttributes = canonicalizedAttributes;
} }
commitSpan(); 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; return spans;
} }

View file

@ -9,7 +9,7 @@
</script> </script>
<div <div
class="relative box-border flex min-h-[calc(3rem+1px)] w-full items-center gap-2 border-b border-ss-secondary bg-sb-primary p-2 shadow-sm" class="relative z-10 box-border flex min-h-[calc(3rem+1px)] w-full items-center gap-2 border-b border-ss-secondary bg-sb-primary p-2 shadow-sm"
> >
<Button href="/" class="rounded-full sm:hidden"> <Button href="/" class="rounded-full sm:hidden">
<svg <svg

View file

@ -4,27 +4,24 @@
import Button from '$lib/components/Button.svelte'; import Button from '$lib/components/Button.svelte';
import RichTextInput from '$lib/components/RichTextInput.svelte'; import RichTextInput from '$lib/components/RichTextInput.svelte';
import { deltaToBlahRichText } from '$lib/richText'; import { deltaToBlahRichText } from '$lib/richText';
import type { Delta } from 'typewriter-editor'; import type { Delta, Editor } from 'typewriter-editor';
export let roomId: string; export let roomId: string;
export let server: BlahChatServerConnection | undefined; export let server: BlahChatServerConnection | undefined;
let editor: Editor | undefined;
let delta: Delta; let delta: Delta;
let plainText: string = ''; let plainText: string = '';
let form: HTMLFormElement | null = null; let form: HTMLFormElement | null = null;
let sendDisabled = false; let sendDisabled = false;
function onInputKeydown(event: KeyboardEvent) { function onKeyboardSubmit() {
console.log(event.key, event.shiftKey, event.isComposing, plainText); editor?.select(null);
if (event.key === 'Enter' && !event.shiftKey && !event.isComposing) {
event.preventDefault();
form?.requestSubmit(); form?.requestSubmit();
} }
}
async function submit() { async function submit() {
if (!server || plainText.trim() === '') return; if (!server || plainText.trim() === '') return;
console.log('submit');
const brt = deltaToBlahRichText(delta); const brt = deltaToBlahRichText(delta);
sendDisabled = true; sendDisabled = true;
@ -44,7 +41,7 @@
plainText = ''; plainText = '';
} }
$: sendDisabled = !!server; $: sendDisabled = !server;
</script> </script>
<form <form
@ -70,11 +67,13 @@
<span class="sr-only">Attach</span> <span class="sr-only">Attach</span>
</Button> </Button>
<RichTextInput <RichTextInput
bind:editor
bind:delta bind:delta
bind:plainText bind:plainText
placeholder="Message" placeholder="Message"
class="max-h-40 flex-1" class="max-h-40 flex-1"
on:keydown={onInputKeydown} keyboardSubmitMethod="enter"
on:keyboardSubmit={onKeyboardSubmit}
/> />
<Button class="p-1.5" variant="primary" type="submit" disabled={sendDisabled}> <Button class="p-1.5" variant="primary" type="submit" disabled={sendDisabled}>
<svg <svg