fix: chat accepts quotes now

This commit is contained in:
gosangam
2026-08-29 11:58:33 +05:30
parent 028a8d7e1c
commit 76cd106ef7
11 changed files with 559 additions and 41 deletions
@@ -382,7 +382,10 @@ export function PostCommentsDrawer({
rows={1}
maxLength={2000}
placeholder={replyTo ? "Write a reply…" : "Add a comment…"}
className="w-full resize-none rounded-lg border border-border bg-background px-3 py-2 text-base sm:text-sm text-foreground outline-none focus-visible:ring-2 focus-visible:ring-primary/40 max-h-32"
// 16px wherever the pointer is coarse, so iOS Safari doesn't
// auto-zoom on focus. Keyed off pointer type rather than width:
// an iPad is wider than `sm` but still zooms a 14px field.
className="w-full resize-none rounded-lg border border-border bg-background px-3 py-2 text-sm pointer-coarse:text-base text-foreground outline-none focus-visible:ring-2 focus-visible:ring-primary/40 max-h-32"
/>
<button
type="button"
+41 -5
View File
@@ -39,6 +39,9 @@ import {
type Comment,
type Post,
} from "@/lib/social";
import { buildPostShareText } from "@/lib/post-share-text";
import { useLongPress } from "@/lib/use-long-press";
import { ShareToChatDialog } from "@/components/chat/share-to-chat-dialog";
import { useAuth } from "@/components/auth/auth-provider";
import { useAuthGate } from "@/components/auth/auth-gate";
import { useMediaQuery } from "@/components/selection-toolbar/shared";
@@ -62,6 +65,8 @@ export function PostActionsFooter({ post }: { post: Post }) {
// on mobile native-share; sheet mount on desktop) so the Send icon
// can render a spinner while the user waits.
const [sharePending, setSharePending] = useState(false);
// The post as chat-ready text. Non-null opens the picker.
const [chatBody, setChatBody] = useState<string | null>(null);
// Optimistic local mirror of post.viewer_has_reshared so the swap
// happens the instant the user taps Reshare. Reconciles to the
// server's value via rollback if the API call fails.
@@ -140,7 +145,27 @@ export function PostActionsFooter({ post }: { post: Post }) {
// parallel, then navigator.share) and the desktop/no-native-share
// path (mount the custom SharePostSheet). Provider's openShareSheet
// is always-resolves, so a single try/finally toggles the spinner.
// Tap → send to a chat. Hold → the platform's own share sheet, for
// everything outside the app. The link text is identical either way; only
// the destination differs.
async function onSendClick() {
if (sharePending) return;
setSharePending(true);
try {
const share = await buildPostShareText(post, post.author_handle ?? "");
if (!share) {
// No handle means no post URL — fall back to the sheet, which
// resolves the handle itself and has its own failure path.
await openShareSheet(post);
return;
}
setChatBody(share.text);
} finally {
setSharePending(false);
}
}
async function onSendHold() {
if (sharePending) return;
setSharePending(true);
try {
@@ -150,6 +175,12 @@ export function PostActionsFooter({ post }: { post: Post }) {
}
}
const sendGestures = useLongPress({
onClick: onSendClick,
onLongPress: onSendHold,
disabled: sharePending,
});
return (
<>
<div className="flex items-center justify-between px-2">
@@ -197,8 +228,8 @@ export function PostActionsFooter({ post }: { post: Post }) {
{showSend ? (
<ActionButton
label={sharePending ? "Preparing share link…" : "Send"}
onClick={onSendClick}
disabled={sharePending}
{...sendGestures}
icon={
sharePending ? (
<Loader2
@@ -270,29 +301,34 @@ export function PostActionsFooter({ post }: { post: Post }) {
{/* SharePostSheet is mounted globally by SharePostProvider, so
no per-card instance lives here. The provider's
openShareSheet swaps the active post under the hood. */}
{chatBody !== null && (
<ShareToChatDialog body={chatBody} onClose={() => setChatBody(null)} />
)}
</>
);
}
function ActionButton({
label,
onClick,
icon,
disabled = false,
...buttonProps
}: {
label: string;
onClick: () => void;
icon: React.ReactNode;
/** Greys out the button and blocks clicks. Used by the Send button
* while the short URL is being resolved on the mobile native-share
* path so users can't fire navigator.share before the URL is
* ready. */
disabled?: boolean;
}) {
/** onClick, plus the pointer handlers the Send button needs to tell a
* tap from a hold. */
} & React.ComponentProps<"button">) {
return (
<button
{...buttonProps}
type="button"
onClick={onClick}
disabled={disabled}
aria-label={label}
title={label}
+8 -22
View File
@@ -38,8 +38,7 @@ import {
useState,
} from "react";
import type { Post } from "@/lib/social";
import { createShortLink } from "@/lib/share";
import { structuredReferenceForPost } from "@/lib/source-reference";
import { buildPostShareText } from "@/lib/post-share-text";
import { useAuth } from "@/components/auth/auth-provider";
import { useMediaQuery } from "@/components/selection-toolbar/shared";
import { SharePostSheet } from "./share-post-sheet";
@@ -131,30 +130,17 @@ export function SharePostProvider({
// it.
// 3. On any non-AbortError, fall back to the custom sheet.
try {
const longPath = `/u/${encodeURIComponent(handle)}/posts#post-${post.id}`;
const hasImage = !!post.images?.[0];
const [code, imageFile] = await Promise.all([
createShortLink(longPath),
const [share, imageFile] = await Promise.all([
buildPostShareText(post, handle),
hasImage ? fetchShareImageFile(post.id) : Promise.resolve(null),
]);
const origin = window.location.origin;
const url = code
? new URL(`/share/${code}`, origin).toString()
: new URL(longPath, origin).toString();
if (!share) {
setActivePost(post);
return;
}
const ref = structuredReferenceForPost(
post.source_url,
post.source_title,
);
const lines = [`“${post.caption}”`, "", "Source:", ref.title];
if (ref.detail) lines.push(ref.detail);
lines.push(url);
const text = lines.join("\n");
const title = ref.detail
? `${ref.title} — ${ref.detail}`
: ref.title;
const payload: ShareData = { title, text };
const payload: ShareData = { title: share.title, text: share.text };
// Only attach files when the platform actually accepts them.
// canShare returns false on Chrome desktop / Firefox even
// though navigator.share itself exists, and we want to fail