release: bump version to 0.66.1
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled

This commit is contained in:
rayd1o
2026-05-26 04:38:18 +08:00
parent 5bf5c73ca0
commit 887fec972e
17 changed files with 667 additions and 56 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "planet-frontend",
"version": "0.66.0",
"version": "0.66.1",
"private": true,
"packageManager": "bun@1",
"dependencies": {

View File

@@ -2495,6 +2495,7 @@ function ModuleConsole({ config }: { config: ModuleConfig }) {
const taskId = text(payload.task_id, sourceId || source)
const pendingKey = taskId || sourceId || source
if (!pendingKey || completedDatasourceTasksRef.current.has(pendingKey)) return
completedDatasourceTasksRef.current.add(pendingKey)
const pendingEntry = pendingDatasourceTasksRef.current[pendingKey]
? [pendingKey, pendingDatasourceTasksRef.current[pendingKey]] as const
: Object.entries(pendingDatasourceTasksRef.current).find(([, item]) => isSameDatasourceRow({ id: item.sourceId, source: item.source }, sourceId, source))
@@ -2512,7 +2513,6 @@ function ModuleConsole({ config }: { config: ModuleConfig }) {
updateDatasourceRow({ ...payload, id: sourceId || pending?.sourceId, source: source || pending?.source }, { removeIfFilteredOut: true })
}
completedDatasourceTasksRef.current.add(pendingKey)
if (pending) {
const finalStatus = datasourceStatus(finalRow)
const titleName = pending.name || text(finalRow.name || finalRow.source, '数据源')

View File

@@ -1,6 +1,6 @@
import { Check, Copy } from 'lucide-react'
import { memo, useEffect, useId, useRef, useState } from 'react'
import type { ReactNode } from 'react'
import type { PointerEvent as ReactPointerEvent, ReactNode, WheelEvent as ReactWheelEvent } from 'react'
import Scrollbar from '../Scrollbar/Scrollbar'
@@ -161,6 +161,11 @@ async function copyToClipboard(text: string): Promise<void> {
document.body.removeChild(textarea)
}
function isMermaidTextTarget(target: EventTarget | null): boolean {
if (!(target instanceof Element)) return false
return Boolean(target.closest('text, tspan, foreignObject, .nodeLabel, .edgeLabel, .label'))
}
function MarkdownCodeBlock({ code, language }: { code: string; language?: string }) {
const [copied, setCopied] = useState(false)
const label = language?.trim() || 'text'
@@ -200,8 +205,18 @@ function MarkdownMermaidBlock({ code }: { code: string }) {
const [svg, setSvg] = useState('')
const [error, setError] = useState('')
const [themeMode, setThemeMode] = useState<'light' | 'dark'>('light')
const [isExpanded, setIsExpanded] = useState(false)
const [zoom, setZoom] = useState(1)
const [pan, setPan] = useState({ x: 0, y: 0 })
const blockId = useId().replace(/[^a-zA-Z0-9_-]/g, '')
const containerRef = useRef<HTMLDivElement | null>(null)
const dragRef = useRef<{
pointerId: number
startX: number
startY: number
initialX: number
initialY: number
} | null>(null)
useEffect(() => {
const themeElement = containerRef.current?.closest('[data-theme]')
@@ -269,31 +284,138 @@ function MarkdownMermaidBlock({ code }: { code: string }) {
window.setTimeout(() => setCopied(false), COPY_FEEDBACK_MS)
}
const openExpanded = () => {
if (!svg) return
setZoom(1)
setPan({ x: 0, y: 0 })
setIsExpanded(true)
}
const closeExpanded = () => {
dragRef.current = null
setIsExpanded(false)
}
const handleWheel = (event: ReactWheelEvent<HTMLDivElement>) => {
event.preventDefault()
const direction = event.deltaY > 0 ? -1 : 1
setZoom((value) => Math.min(4, Math.max(0.35, value + direction * 0.12)))
}
const handlePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
if (!isExpanded) return
if (isMermaidTextTarget(event.target)) return
event.currentTarget.setPointerCapture(event.pointerId)
dragRef.current = {
pointerId: event.pointerId,
startX: event.clientX,
startY: event.clientY,
initialX: pan.x,
initialY: pan.y,
}
}
const handlePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
const drag = dragRef.current
if (!drag || drag.pointerId !== event.pointerId) return
setPan({
x: drag.initialX + event.clientX - drag.startX,
y: drag.initialY + event.clientY - drag.startY,
})
}
const handlePointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
if (dragRef.current?.pointerId === event.pointerId) {
dragRef.current = null
}
}
return (
<div className="markdown-renderer__mermaid-block" ref={containerRef}>
<div className="markdown-renderer__code-toolbar markdown-renderer__mermaid-toolbar">
<span className="markdown-renderer__code-language">mermaid</span>
<button
type="button"
className="markdown-renderer__code-copy"
onClick={handleCopy}
aria-label={copied ? '已复制图表源码' : '复制图表源码'}
title={copied ? '已复制' : '复制图表源码'}
>
{copied ? <Check size={14} /> : <Copy size={14} />}
</button>
</div>
{svg ? (
<div className="markdown-renderer__mermaid-canvas" dangerouslySetInnerHTML={{ __html: svg }} />
) : error ? (
<div className="markdown-renderer__mermaid-error">
<strong>Mermaid render failed</strong>
<span>{error}</span>
<>
<div className="markdown-renderer__mermaid-block" ref={containerRef}>
<div className="markdown-renderer__code-toolbar markdown-renderer__mermaid-toolbar">
<span className="markdown-renderer__code-language">mermaid</span>
<button
type="button"
className="markdown-renderer__code-copy"
onClick={handleCopy}
aria-label={copied ? '已复制图表源码' : '复制图表源码'}
title={copied ? '已复制' : '复制图表源码'}
>
{copied ? <Check size={14} /> : <Copy size={14} />}
</button>
</div>
) : (
<div className="markdown-renderer__mermaid-loading">Rendering diagram...</div>
)}
</div>
{svg ? (
<div
role="button"
tabIndex={0}
className="markdown-renderer__mermaid-canvas"
onClick={(event) => {
if (isMermaidTextTarget(event.target)) return
if (window.getSelection()?.toString()) return
openExpanded()
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
openExpanded()
}
}}
aria-label="放大查看 Mermaid 图表"
title="点击放大查看"
>
<span className="markdown-renderer__mermaid-canvas-inner" dangerouslySetInnerHTML={{ __html: svg }} />
</div>
) : error ? (
<div className="markdown-renderer__mermaid-error">
<strong>Mermaid render failed</strong>
<span>{error}</span>
</div>
) : (
<div className="markdown-renderer__mermaid-loading">Rendering diagram...</div>
)}
</div>
{isExpanded && svg ? (
<div
className="markdown-renderer__mermaid-viewer"
role="dialog"
aria-modal="true"
aria-label="Mermaid 图表查看器"
onClick={closeExpanded}
>
<button
type="button"
className="markdown-renderer__mermaid-viewer-close"
onClick={closeExpanded}
aria-label="关闭 Mermaid 图表查看器"
title="关闭"
>
×
</button>
<div
className="markdown-renderer__mermaid-viewer-stage"
onClick={(event) => event.stopPropagation()}
onWheel={handleWheel}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerUp}
>
<div
className="markdown-renderer__mermaid-viewer-content"
style={{
transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
}}
dangerouslySetInnerHTML={{ __html: svg }}
/>
</div>
<div className="markdown-renderer__mermaid-viewer-hint">
· ·
</div>
</div>
) : null}
</>
)
}

View File

@@ -673,14 +673,135 @@
.docs-markdown .markdown-renderer__mermaid-canvas {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 180px;
padding: 22px;
border: 0;
background: transparent;
color: inherit;
overflow-x: auto;
cursor: zoom-in;
user-select: none;
}
.docs-markdown .markdown-renderer__mermaid-canvas svg {
max-width: 100%;
.docs-markdown .markdown-renderer__mermaid-canvas-inner {
display: flex;
justify-content: center;
width: 100%;
}
.docs-markdown .markdown-renderer__mermaid-canvas-inner svg {
width: 100%;
max-width: min(100%, 1120px);
height: auto;
min-height: 160px;
user-select: none;
}
.docs-markdown .markdown-renderer__mermaid-canvas-inner svg text {
user-select: text;
-webkit-user-select: text;
}
.docs-markdown .markdown-renderer__mermaid-canvas-inner svg tspan,
.docs-markdown .markdown-renderer__mermaid-canvas-inner svg foreignObject,
.docs-markdown .markdown-renderer__mermaid-canvas-inner svg foreignObject * {
user-select: text;
-webkit-user-select: text;
}
.markdown-renderer__mermaid-viewer {
position: fixed;
inset: 0;
z-index: 10000;
display: grid;
place-items: center;
padding: 42px;
background: rgba(3, 7, 18, 0.78);
backdrop-filter: blur(10px);
}
.markdown-renderer__mermaid-viewer-stage {
width: min(1180px, calc(100vw - 84px));
height: min(760px, calc(100vh - 112px));
overflow: hidden;
border: 1px solid rgba(148, 163, 184, 0.26);
border-radius: 8px;
background: var(--d-mermaid-bg);
box-shadow: 0 28px 80px rgba(0, 0, 0, 0.4);
cursor: grab;
touch-action: none;
user-select: none;
}
.markdown-renderer__mermaid-viewer-stage:active {
cursor: grabbing;
}
.markdown-renderer__mermaid-viewer-content {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
transform-origin: center;
transition: transform 0.08s ease-out;
}
.markdown-renderer__mermaid-viewer-content svg {
max-width: 92%;
max-height: 92%;
width: auto;
height: auto;
user-select: none;
}
.markdown-renderer__mermaid-viewer-content svg text {
user-select: text;
-webkit-user-select: text;
}
.markdown-renderer__mermaid-viewer-content svg tspan,
.markdown-renderer__mermaid-viewer-content svg foreignObject,
.markdown-renderer__mermaid-viewer-content svg foreignObject * {
user-select: text;
-webkit-user-select: text;
}
.markdown-renderer__mermaid-viewer-close {
position: fixed;
top: 18px;
right: 20px;
z-index: 1;
width: 36px;
height: 36px;
border: 1px solid rgba(148, 163, 184, 0.34);
border-radius: 8px;
background: rgba(15, 23, 42, 0.78);
color: #e5eefb;
font-size: 26px;
line-height: 1;
cursor: pointer;
}
.markdown-renderer__mermaid-viewer-close:hover {
background: rgba(30, 41, 59, 0.92);
color: #ffffff;
}
.markdown-renderer__mermaid-viewer-hint {
position: fixed;
left: 50%;
bottom: 18px;
transform: translateX(-50%);
padding: 8px 12px;
border: 1px solid rgba(148, 163, 184, 0.24);
border-radius: 999px;
background: rgba(15, 23, 42, 0.72);
color: #cbd5e1;
font-size: 12px;
pointer-events: none;
}
.docs-markdown .markdown-renderer__mermaid-loading,