fix: chat accepts quotes now
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user