mirror of
https://github.com/Blah-IM/Weblah.git
synced 2025-04-30 16:21:09 +00:00
fix: enter to send
This commit is contained in:
parent
72b962fb77
commit
431f14b35d
7 changed files with 101 additions and 42 deletions
|
@ -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
|
||||
)}
|
||||
>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
15
src/lib/components/RichTextInput/keyboardSubmitModule.ts
Normal file
15
src/lib/components/RichTextInput/keyboardSubmitModule.ts
Normal 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'
|
||||
}
|
||||
});
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue