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
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
)}
>

View file

@ -1,12 +1,14 @@
<script lang="ts">
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 { tw } from '$lib/tw';
export let delta: Delta | null = null;
export let plainText: string | undefined = undefined;
export let keyboardSubmitMethod: 'enter' | 'shiftEnter' | undefined = undefined;
export let placeholder: string = '';
export let editor: Editor | undefined;
let className = '';
export { className as class };
@ -24,7 +26,15 @@
<p>{placeholder}</p>
</div>
{: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 />
</svelte:component>
{/await}

View file

@ -1,33 +1,49 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { Delta, Editor, asRoot, h } from 'typewriter-editor';
import { keyboardSubmit } from './keyboardSubmitModule';
export let delta: Delta = new Delta();
export let plainText: string | undefined = undefined;
export let placeholder: string = '';
export let keyboardSubmitMethod: 'enter' | 'shiftEnter' | undefined = undefined;
const editor = new Editor();
editor.typeset.formats.add({
name: 'underline',
selector: 'span[data-weblah-brt=underline]',
styleSelector: '[style*="text-decoration:underline"], [style*="text-decoration: underline"]',
commands: (editor) => () => editor.toggleTextFormat({ underline: true }),
shortcuts: 'Mod+U',
render: (attributes, children) => h('span', { 'data-weblah-brt': 'underline' }, children)
});
editor.typeset.formats.add({
name: 'strikethrough',
selector: 's',
styleSelector:
'[style*="text-decoration:line-through"], [style*="text-decoration: line-through"]',
commands: (editor) => () => editor.toggleTextFormat({ strikethrough: true }),
shortcuts: 'Mod+Shift+X',
render: (attributes, children) => h('s', null, children)
});
const dispatch = createEventDispatcher<{
keyboardSubmit: void;
}>();
editor.on('change', () => {
delta = editor.getDelta();
if (typeof plainText === 'string') plainText = editor.getText();
});
let editor: Editor;
function initEditor() {
const modules = keyboardSubmitMethod
? { keyboardSubmit: keyboardSubmit(() => dispatch('keyboardSubmit'), keyboardSubmitMethod) }
: undefined;
editor = new Editor({ modules });
editor.typeset.formats.add({
name: 'underline',
selector: 'span[data-weblah-brt=underline]',
styleSelector: '[style*="text-decoration:underline"], [style*="text-decoration: underline"]',
commands: (editor) => () => editor.toggleTextFormat({ underline: true }),
shortcuts: 'Mod+U',
render: (attributes, children) => h('span', { 'data-weblah-brt': 'underline' }, children)
});
editor.typeset.formats.add({
name: 'strikethrough',
selector: 's',
styleSelector:
'[style*="text-decoration:line-through"], [style*="text-decoration: line-through"]',
commands: (editor) => () => editor.toggleTextFormat({ strikethrough: true }),
shortcuts: 'Mod+Shift+X',
render: (attributes, children) => h('s', null, children)
});
editor.on('change', () => {
delta = editor.getDelta();
if (typeof plainText === 'string') plainText = editor.getText();
});
}
$: if (keyboardSubmitMethod || typeof keyboardSubmitMethod === 'undefined') initEditor();
$: editor.setDelta(delta ?? new Delta());
$: if (typeof plainText === 'string' && plainText !== editor.getText()) editor.setText(plainText);
@ -40,7 +56,6 @@
? 'true'
: undefined}
data-weblah-placeholder={placeholder}
on:keydown
role="textbox"
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;
}
export function deltaToBlahRichText(delta: Delta): BlahRichText {
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() {
spans.push(lastAttributes === null ? lastText : [lastText, lastAttributes]);
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;
@ -68,12 +73,27 @@ export function deltaToBlahRichText(delta: Delta): BlahRichText {
continue;
}
commitSpan();
const commited = commitSpan(trim && isFirstSpan ? 'start' : undefined);
if (commited) isFirstSpan = false;
lastText = op.insert;
lastAttributes = attributes;
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;
}

View file

@ -9,7 +9,7 @@
</script>
<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">
<svg

View file

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