fix: text compose papercuts (#270)
* make fixes * cleanup
This commit was merged in pull request #270.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { Paperclip } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getImmersiveTextStyle } from '@/lib/immersive-text';
|
||||
import { hasMarkdownFormatting } from '@/lib/markdown';
|
||||
import { metaKey } from '@/lib/platform';
|
||||
import { useAllLinkMetadata } from '@/hooks/use-link-metadata';
|
||||
import { AttachmentStrip } from '@/features/compose/attachment-strip';
|
||||
@@ -35,12 +37,6 @@ interface TextEditorProps {
|
||||
|
||||
const IMMERSIVE_CHAR_LIMIT = 120;
|
||||
|
||||
function getImmersiveTextStyle(length: number) {
|
||||
if (length < 70) return { size: 'text-5xl', weight: 'font-semibold' };
|
||||
if (length < 130) return { size: 'text-3xl', weight: 'font-semibold' };
|
||||
return { size: 'text-2xl', weight: 'font-normal' };
|
||||
}
|
||||
|
||||
export function TextEditor({
|
||||
textContent,
|
||||
onTextChange,
|
||||
@@ -50,7 +46,9 @@ export function TextEditor({
|
||||
attachmentProps,
|
||||
}: TextEditorProps) {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [forceCardMode, setForceCardMode] = useState(false);
|
||||
// Card mode latches: once the message needs the full editor, snapping back
|
||||
// to immersive mid-edit would be jarring, so it stays for the session.
|
||||
const [carded, setCarded] = useState(false);
|
||||
|
||||
const [debouncedText, setDebouncedText] = useState(textContent);
|
||||
useEffect(() => {
|
||||
@@ -61,13 +59,16 @@ export function TextEditor({
|
||||
|
||||
const attachmentCount = attachmentProps?.attachments.length ?? 0;
|
||||
const hasEnrichments = attachmentCount > 0 || linkPreviews.length > 0;
|
||||
const immersive =
|
||||
textContent.length < IMMERSIVE_CHAR_LIMIT &&
|
||||
!hasEnrichments &&
|
||||
!forceCardMode;
|
||||
const shouldCard =
|
||||
!carded &&
|
||||
(textContent.length >= IMMERSIVE_CHAR_LIMIT ||
|
||||
hasEnrichments ||
|
||||
hasMarkdownFormatting(textContent));
|
||||
if (shouldCard) setCarded(true);
|
||||
const immersive = !carded;
|
||||
|
||||
// Keep the immersive textarea focused with the caret at the end when we
|
||||
// (re)enter it. The card-mode editor manages its own focus.
|
||||
// Keep the immersive textarea focused with the caret at the end on mount.
|
||||
// The card-mode editor manages its own focus.
|
||||
useEffect(() => {
|
||||
if (!immersive) return;
|
||||
const t = setTimeout(() => {
|
||||
@@ -95,7 +96,7 @@ export function TextEditor({
|
||||
} else if (e.key === 'm' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setForceCardMode(true);
|
||||
setCarded(true);
|
||||
}
|
||||
},
|
||||
[onCancel, onSubmit, textContent],
|
||||
@@ -127,7 +128,7 @@ export function TextEditor({
|
||||
{immersive && (
|
||||
<KeyHint
|
||||
keys={`${metaKey}+M`}
|
||||
onClick={() => setForceCardMode(true)}
|
||||
onClick={() => setCarded(true)}
|
||||
title={`Switch to markdown editor (or press ${metaKey}+M)`}
|
||||
>
|
||||
markdown
|
||||
@@ -194,7 +195,7 @@ export function TextEditor({
|
||||
{...dropZoneProps}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
>
|
||||
<div className="mx-8 flex h-[calc(100%-8rem)] w-full min-w-0 flex-col overflow-hidden rounded border border-white/10 bg-white/5 backdrop-blur-xl">
|
||||
<div className="mx-8 flex h-[calc(100%-8rem)] w-full min-w-0 max-w-[calc(var(--message-content-width)_+_var(--message-editor-gutter)*2)] flex-col overflow-hidden rounded border border-white/10 bg-white/5 backdrop-blur-xl animate-in fade-in zoom-in-95 duration-200">
|
||||
{/* No padding here: the editor's own scroll box hosts the slash menu,
|
||||
so we pad inside the editor (ProseMirror) instead. That keeps the
|
||||
menu's clipping bounds the full card rather than the inset box. */}
|
||||
|
||||
Reference in New Issue
Block a user