mirror of
				https://github.com/Blah-IM/Weblah.git
				synced 2025-10-31 10:01:37 +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 | <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 | ||||||
| 	)} | 	)} | ||||||
| > | > | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
| > | > | ||||||
|  |  | ||||||
							
								
								
									
										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; | 	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; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Shibo Lyu
						Shibo Lyu