"use client"; import React, { useState } from "react"; import { ButtonPrimary, ButtonTertiary } from "components/Buttons"; import { Popover } from "components/Popover"; import { Avatar } from "components/Avatar"; import { CloseTiny } from "components/Icons/CloseTiny"; import { InfoSmall } from "components/Icons/InfoSmall"; import { LoginModal } from "components/LoginButton"; import { BaseTextBlock } from "app/(app)/(published)/lish/[did]/[publication]/[rkey]/Blocks/BaseTextBlock"; import { LocalizedDate } from "app/(app)/(published)/lish/[did]/[publication]/LocalizedDate"; import { FacetedTextComposer, type FacetedText, } from "components/FacetedTextComposer"; import type { DocumentQuestion, QuestionState } from "src/documentQuestions"; import { ArrowDownTiny } from "components/Icons/ArrowDownTiny"; import { authorDisplayName, hasSeveralAuthors, InteractionAuthorName, } from "../InteractionAuthorName"; import { ReplyButtonTextInput } from "../ReplyBlock/RepliesHeader"; import { DEFAULT_QUESTIONS_BUTTON_TEXT } from "./constants"; import { questionsAudienceNotice, type QuestionsAudience, } from "src/questionsAudience"; export function QuestionsButtonTextInput(props: { value: string; onCommit: (text: string) => void; }) { return ( ); } export function PublicQuestionNotice(props: { defaultOpen?: boolean; audience?: QuestionsAudience; showPending?: boolean; }) { let restriction = questionsAudienceNotice(props.audience ?? "anyone"); return ( } > Questions are public. Yours is posted to your account, where anyone can see it,{" "} {props.showPending ? "and appears here unless the author hides it." : "and appears here once the author accepts or answers it."} {restriction &&
{restriction}
}
); } // The restriction readers see beside the composer, if there is one function AudienceNotice(props: { audience?: QuestionsAudience }) { let restriction = questionsAudienceNotice(props.audience ?? "anyone"); if (!restriction) return null; return {restriction}; } export function QuestionComposer(props: { draftKey: string | null; submitLabel?: string; autoFocus?: boolean; audience?: QuestionsAudience; showPending?: boolean; onSubmit: (text: FacetedText) => Promise; }) { return ( } /> ); } // The editor shows readers' input without taking questions. export function QuestionComposerPreview(props: { action: React.ReactNode; audience?: QuestionsAudience; showPending?: boolean; }) { return (
Readers write their question here…
{props.action}
); } // Readers without an Atmosphere account sign in (or link one) before they // can ask; the same box the comments drawer shows. export function SignInToAsk(props: { // Signed in, but without an Atmosphere account linkAccount?: boolean; redirectRoute?: string; trigger?: React.ReactNode; }) { return (
{props.trigger ?? ( )} {" "} {props.linkAccount ? "" : "with "}an Atmosphere account to ask a question
); } export function QuestionsDraft(props: { action: React.ReactNode; audience?: QuestionsAudience; showPending?: boolean; }) { return (
Questions open when you publish
Readers can ask you public questions. Accept one to show it, or answer it right here and the answer appears under it.
); } function QuestionBody(props: { question: DocumentQuestion; showHandle?: boolean; control?: React.ReactNode; }) { let { asker } = props.question; return (
{props.question.createdAt && ( )} {props.control && ( {props.control} )}
          
        
); } // A question, whatever answers it under it (the published answer, or the // author's inline editor), and whatever acts on it beside the asker's name. export function QuestionRow(props: { question: DocumentQuestion; showHandle?: boolean; answer?: React.ReactNode; control?: React.ReactNode; }) { return (
{props.answer &&
{props.answer}
}
); } export function AnswerEditorFrame(props: { children: React.ReactNode; // The question already has a published answer this would replace published: boolean; // Closing drops the draft discardOnClose: boolean; publishing?: boolean; onPublish: () => void; onCancel: () => void; }) { return (
{props.children}
{props.discardOnClose ? "Discard" : "Close"} {props.publishing ? "Publishing…" : props.published ? "Update answer" : "Publish answer"}
); } // The reader's view: the questions shown on the post, plus their own still // waiting. export function QuestionsList(props: { questions: DocumentQuestion[]; // The composer or a sign-in button action?: React.ReactNode; renderAnswer: RenderAnswer; onWithdraw?: (uri: string) => void; }) { let { onWithdraw } = props; return (
{props.action} !question.answer && ( <> {question.mine && question.state !== "accepted" ? "Awaiting the author" : "Awaiting an answer"} {onWithdraw && question.mine && ( )} ) } />
); } // The author's view: pending questions first, each with controls to accept // or hide it, then the ones shown on the post, and the hidden ones behind a // toggle. export function QuestionsModeration(props: { questions: DocumentQuestion[]; // Above the lists: the editor's composer preview header?: React.ReactNode; // A state change is being published busy?: boolean; // Unset for an editor who isn't the post's author onSetState?: (uri: string, state: QuestionState) => void; renderAnswer: RenderAnswer; // Beside a question's moderation controls: "Answer", or a link to the // editor renderControl: (question: DocumentQuestion) => React.ReactNode; }) { let [showHidden, setShowHidden] = useState(false); let inState = (state: QuestionState) => props.questions.filter((q) => q.state === state); let hidden = inState("hidden"); if (showHidden && hidden.length === 0) setShowHidden(false); let showHandles = showHandlesFor(props.questions); let stateButton = ( q: DocumentQuestion, state: QuestionState, label: string, ) => props.onSetState && ( props.onSetState?.(q.uri, state)} > {label} ); let rows = ( questions: DocumentQuestion[], controls: (q: DocumentQuestion) => React.ReactNode, className?: string, ) => ( ( <> {controls(q)} {props.renderControl(q)} )} /> ); return (
{props.header} {props.questions.length === 0 && (
No one has asked anything yet.
)} {rows( inState("pending"), (q) => ( <> {stateButton(q, "hidden", "Hide")} {stateButton(q, "accepted", "Accept")} ), "light-container p-2", )} {rows(inState("accepted"), (q) => stateButton(q, "hidden", "Hide"))} {hidden.length > 0 && (
setShowHidden(!showHidden)} > {hidden.length} hidden {showHidden && rows(hidden, (q) => stateButton(q, "pending", "Unhide"))}
)}
); } // Whether to show handles, judged across everyone the whole list shows: the // askers and whoever answered. function showHandlesFor(questions: DocumentQuestion[]) { return hasSeveralAuthors( questions.flatMap((q) => q.answer ? [q.asker, q.answer.author] : [q.asker], ), ); } type RenderAnswer = ( question: DocumentQuestion, showHandle?: boolean, ) => React.ReactNode; function QuestionRows(props: { questions: DocumentQuestion[]; className?: string; showHandles?: boolean; renderAnswer: RenderAnswer; renderControl: (question: DocumentQuestion) => React.ReactNode; }) { if (props.questions.length === 0) return null; return (
{props.questions.map((question) => ( ))}
); }