feat(mindspace): 公开页分享组件、workspace 路径与 Plaza 发布增强
- 新增 public share widget 与 workspace relative path 解析 - 增强 publications/chat-plaza 发布链路与缩略图 demo - 补充 schema、cover 检查脚本与回归测试 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ApiError, quickPlazaFromChat } from '../api/client';
|
||||
import { resolvePlazaPostUrl } from '../utils/publicUrl';
|
||||
import { ChatLoadingSpinner } from './ChatLoadingSpinner';
|
||||
|
||||
type PlazaPublishPhase =
|
||||
| { kind: 'confirm' }
|
||||
| { kind: 'loading' }
|
||||
| { kind: 'success'; plazaUrl: string }
|
||||
| { kind: 'already'; plazaUrl: string; message: string }
|
||||
@@ -28,11 +29,15 @@ export function ChatPlazaPublishModal({
|
||||
messageId: string;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [phase, setPhase] = useState<PlazaPublishPhase>({ kind: 'loading' });
|
||||
const [phase, setPhase] = useState<PlazaPublishPhase>({ kind: 'confirm' });
|
||||
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const onCloseRef = useRef(onClose);
|
||||
onCloseRef.current = onClose;
|
||||
|
||||
const startPublish = useCallback(() => {
|
||||
setPhase({ kind: 'loading' });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const prev = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
@@ -52,6 +57,7 @@ export function ChatPlazaPublishModal({
|
||||
}, [onClose, phase.kind]);
|
||||
|
||||
useEffect(() => {
|
||||
if (phase.kind !== 'loading') return;
|
||||
const controller = new AbortController();
|
||||
|
||||
void quickPlazaFromChat({ sessionId, messageId }, controller.signal)
|
||||
@@ -79,7 +85,7 @@ export function ChatPlazaPublishModal({
|
||||
});
|
||||
|
||||
return () => controller.abort();
|
||||
}, [sessionId, messageId]);
|
||||
}, [phase.kind, sessionId, messageId]);
|
||||
|
||||
const canClose = phase.kind !== 'loading';
|
||||
|
||||
@@ -97,6 +103,21 @@ export function ChatPlazaPublishModal({
|
||||
aria-busy={phase.kind === 'loading'}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
{phase.kind === 'confirm' && (
|
||||
<div className="chat-plaza-publish-body">
|
||||
<h3 id="chat-plaza-publish-title">发布到 Plaza</h3>
|
||||
<p>将把当前对话内容保存为公开页面并推送到发现广场,确认继续?</p>
|
||||
<div className="chat-plaza-publish-actions">
|
||||
<button type="button" className="chat-plaza-publish-close-btn" onClick={onClose}>
|
||||
取消
|
||||
</button>
|
||||
<button type="button" className="chat-plaza-publish-confirm-btn" onClick={startPublish}>
|
||||
确认发布
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{phase.kind === 'loading' && (
|
||||
<div className="chat-plaza-publish-body">
|
||||
<ChatLoadingSpinner className="chat-plaza-publish-spinner" />
|
||||
|
||||
+26
-1
@@ -1532,8 +1532,17 @@ body,
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.chat-plaza-publish-close-btn {
|
||||
.chat-plaza-publish-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.chat-plaza-publish-close-btn {
|
||||
margin-top: 0;
|
||||
padding: 8px 18px;
|
||||
border: 1px solid rgba(24, 33, 29, 0.14);
|
||||
border-radius: 999px;
|
||||
@@ -1544,11 +1553,27 @@ body,
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chat-plaza-publish-confirm-btn {
|
||||
padding: 8px 18px;
|
||||
border: 1px solid rgba(47, 111, 87, 0.35);
|
||||
border-radius: 999px;
|
||||
background: #2f6f57;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chat-plaza-publish-close-btn:hover {
|
||||
border-color: rgba(47, 111, 87, 0.35);
|
||||
color: #2f6f57;
|
||||
}
|
||||
|
||||
.chat-plaza-publish-confirm-btn:hover {
|
||||
background: #265a46;
|
||||
}
|
||||
|
||||
.page-save-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -56,8 +56,8 @@ export function isImageAsset(asset: MindSpaceAsset) {
|
||||
return asset.assetType === 'image' || asset.mimeType.startsWith('image/');
|
||||
}
|
||||
|
||||
export function buildAssetImageUrl(asset: Pick<MindSpaceAsset, 'id' | 'updatedAt' | 'publicUrl'>) {
|
||||
if (asset.publicUrl) return asset.publicUrl;
|
||||
export function buildAssetImageUrl(asset: Pick<MindSpaceAsset, 'id' | 'updatedAt'>) {
|
||||
// MindSpace 空间内始终走受鉴权的下载接口,避免 publicUrl(imgproxy / 公开静态路径)在本地或工作区资产上失效。
|
||||
return `/api/mindspace/v1/assets/${asset.id}/download?inline=1&viewer=0&v=${asset.updatedAt}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user