Path: blob/master/src/packages/frontend/chat/resolved-thread-panel.tsx
14422 views
/*1* This file is part of CoCalc: Copyright © 2020-2026 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Read-only "this thread is resolved" panel that replaces the reply input on7a resolved (LaTeX collaborative-TODO) thread. Shows who resolved it and8when, plus a "Start new chat thread" affordance that — after the user9confirms the target file/line — calls the editor's `insertChatMarker`.1011The panel is editor-agnostic at the UI layer: it discovers the marker12target via `frameTreeActions.previewMarkerInsertion()` and then fires13`frameTreeActions.insertChatMarker()` on confirm. Editors that don't14provide those methods just hide the button (defensive — only LaTeX wires15this in for now).16*/1718import { useState } from "react";19import { Button, Popconfirm } from "antd";2021import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";22import { Icon } from "@cocalc/frontend/components";23import { COLORS } from "@cocalc/util/theme";2425import type { ChatActions } from "./actions";2627interface Props {28actions: ChatActions;29account_id: string;30at: string; // ISO timestamp31}3233export function ResolvedThreadPanel({ actions, account_id, at }: Props) {34const userMap = useTypedRedux("users", "user_map");35const meAccountId = redux.getStore("account")?.get_account_id();3637const resolvedAt = new Date(at);38const resolvedDateText = isNaN(resolvedAt.valueOf())39? at40: resolvedAt.toLocaleString();4142let resolverName = "Someone";43if (account_id && account_id === meAccountId) {44resolverName = "You";45} else if (account_id && userMap != null) {46const u = userMap.get(account_id);47const first = u?.get("first_name") ?? "";48const last = u?.get("last_name") ?? "";49const full = `${first} ${last}`.trim();50if (full) resolverName = full;51}5253const [target, setTarget] = useState<{ path: string; line: number } | null>(54null,55);56const editorActions: any = actions.frameTreeActions;57const canStartNew =58typeof editorActions?.previewMarkerInsertion === "function" &&59typeof editorActions?.insertChatMarker === "function";6061const handleOpenStartNew = () => {62if (!canStartNew) return;63try {64setTarget(editorActions.previewMarkerInsertion());65} catch {66setTarget(null);67}68};69const handleConfirmStartNew = () => {70if (!canStartNew) return;71void editorActions.insertChatMarker({});72setTarget(null);73};7475return (76<div77style={{78margin: "5px 10px",79padding: "12px 16px",80background: COLORS.GRAY_LL,81border: `1px solid ${COLORS.GRAY_L}`,82borderRadius: 6,83color: COLORS.GRAY_DD,84display: "flex",85alignItems: "center",86gap: 12,87}}88>89<Icon90name="check-circle"91style={{ color: COLORS.GRAY_M, fontSize: "1.1em" }}92/>93<div style={{ flex: 1 }}>94<div style={{ fontWeight: 500 }}>This chat is resolved.</div>95<div style={{ fontSize: "0.9em", color: COLORS.GRAY_M }}>96Resolved by {resolverName} on {resolvedDateText}. Replies are disabled97— start a new thread to continue the discussion at a fresh location.98</div>99</div>100{canStartNew && (101<Popconfirm102title="Start a new chat thread?"103description={104<div style={{ maxWidth: 320 }}>105{target == null ? (106<span>107No active editor pane. Click into a <code>.tex</code> file108first so the new marker has a target.109</span>110) : (111<span>112This will insert a new <code>% chat:</code> marker in{" "}113<code>{target.path}</code> at line {target.line + 1} and open114a fresh chat thread there.115</span>116)}117</div>118}119okText={target == null ? undefined : "Insert marker"}120cancelText="Cancel"121okButtonProps={{ disabled: target == null }}122onConfirm={handleConfirmStartNew}123onOpenChange={(open) => {124if (open) handleOpenStartNew();125}}126placement="topRight"127>128<Button type="primary" size="small">129<Icon name="comment" /> Start new chat thread130</Button>131</Popconfirm>132)}133</div>134);135}136137138