@nextclaw/ui 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/assets/index-B6sMhZ9q.css +1 -0
- package/dist/assets/index-B_-o3-kG.js +337 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/components/doc-browser/DocBrowser.tsx +44 -14
- package/src/components/doc-browser/DocBrowserContext.tsx +14 -6
- package/src/components/doc-browser/useDocLinkInterceptor.ts +3 -0
- package/src/lib/i18n.ts +1 -0
- package/dist/assets/index-D16PLMLv.js +0 -337
- package/dist/assets/index-Wn63frSd.css +0 -1
package/dist/index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-B_-o3-kG.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B6sMhZ9q.css">
|
|
11
11
|
</head>
|
|
12
12
|
|
|
13
13
|
<body>
|
package/package.json
CHANGED
|
@@ -31,7 +31,11 @@ export function DocBrowser() {
|
|
|
31
31
|
|
|
32
32
|
const [urlInput, setUrlInput] = useState('');
|
|
33
33
|
const [isDragging, setIsDragging] = useState(false);
|
|
34
|
-
const [
|
|
34
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
35
|
+
const [floatPos, setFloatPos] = useState(() => ({
|
|
36
|
+
x: Math.max(40, window.innerWidth - 520),
|
|
37
|
+
y: 80,
|
|
38
|
+
}));
|
|
35
39
|
const [floatSize, setFloatSize] = useState({ w: 480, h: 600 });
|
|
36
40
|
const [dockedWidth, setDockedWidth] = useState(420);
|
|
37
41
|
const dragRef = useRef<{ startX: number; startY: number; startPosX: number; startPosY: number } | null>(null);
|
|
@@ -48,6 +52,16 @@ export function DocBrowser() {
|
|
|
48
52
|
}
|
|
49
53
|
}, [currentUrl]);
|
|
50
54
|
|
|
55
|
+
// Reposition floating window near right edge when switching from docked
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (mode === 'floating') {
|
|
58
|
+
setFloatPos(prev => ({
|
|
59
|
+
x: Math.max(40, window.innerWidth - floatSize.w - 40),
|
|
60
|
+
y: prev.y,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
}, [mode, floatSize.w]);
|
|
64
|
+
|
|
51
65
|
// Listen for route changes from the iframe via postMessage
|
|
52
66
|
useEffect(() => {
|
|
53
67
|
const handler = (e: MessageEvent) => {
|
|
@@ -105,10 +119,12 @@ export function DocBrowser() {
|
|
|
105
119
|
};
|
|
106
120
|
}, [isDragging]);
|
|
107
121
|
|
|
108
|
-
// --- Resize logic (floating mode
|
|
122
|
+
// --- Resize logic (floating mode) ---
|
|
109
123
|
const onResizeStart = useCallback((e: React.MouseEvent) => {
|
|
110
124
|
e.preventDefault();
|
|
111
125
|
e.stopPropagation();
|
|
126
|
+
setIsResizing(true);
|
|
127
|
+
const axis = (e.currentTarget as HTMLElement).dataset.axis; // 'x', 'y', or undefined (both)
|
|
112
128
|
resizeRef.current = {
|
|
113
129
|
startX: e.clientX,
|
|
114
130
|
startY: e.clientY,
|
|
@@ -117,12 +133,13 @@ export function DocBrowser() {
|
|
|
117
133
|
};
|
|
118
134
|
const onMove = (ev: MouseEvent) => {
|
|
119
135
|
if (!resizeRef.current) return;
|
|
120
|
-
setFloatSize({
|
|
121
|
-
w: Math.max(360, resizeRef.current
|
|
122
|
-
h: Math.max(400, resizeRef.current
|
|
123
|
-
});
|
|
136
|
+
setFloatSize(prev => ({
|
|
137
|
+
w: axis === 'y' ? prev.w : Math.max(360, resizeRef.current!.startW + (ev.clientX - resizeRef.current!.startX)),
|
|
138
|
+
h: axis === 'x' ? prev.h : Math.max(400, resizeRef.current!.startH + (ev.clientY - resizeRef.current!.startY)),
|
|
139
|
+
}));
|
|
124
140
|
};
|
|
125
141
|
const onUp = () => {
|
|
142
|
+
setIsResizing(false);
|
|
126
143
|
resizeRef.current = null;
|
|
127
144
|
window.removeEventListener('mousemove', onMove);
|
|
128
145
|
window.removeEventListener('mouseup', onUp);
|
|
@@ -135,14 +152,15 @@ export function DocBrowser() {
|
|
|
135
152
|
const onDockResizeStart = useCallback((e: React.MouseEvent) => {
|
|
136
153
|
e.preventDefault();
|
|
137
154
|
e.stopPropagation();
|
|
155
|
+
setIsResizing(true);
|
|
138
156
|
dockResizeRef.current = { startX: e.clientX, startW: dockedWidth };
|
|
139
157
|
const onMove = (ev: MouseEvent) => {
|
|
140
158
|
if (!dockResizeRef.current) return;
|
|
141
|
-
// Dragging left should increase width (since resize handle is on the left edge)
|
|
142
159
|
const delta = dockResizeRef.current.startX - ev.clientX;
|
|
143
160
|
setDockedWidth(Math.max(320, Math.min(800, dockResizeRef.current.startW + delta)));
|
|
144
161
|
};
|
|
145
162
|
const onUp = () => {
|
|
163
|
+
setIsResizing(false);
|
|
146
164
|
dockResizeRef.current = null;
|
|
147
165
|
window.removeEventListener('mousemove', onMove);
|
|
148
166
|
window.removeEventListener('mouseup', onUp);
|
|
@@ -246,12 +264,17 @@ export function DocBrowser() {
|
|
|
246
264
|
{/* Iframe Content */}
|
|
247
265
|
<div className="flex-1 relative overflow-hidden">
|
|
248
266
|
<iframe
|
|
267
|
+
key={currentUrl}
|
|
249
268
|
src={currentUrl}
|
|
250
269
|
className="absolute inset-0 w-full h-full border-0"
|
|
251
270
|
title="NextClaw Documentation"
|
|
252
271
|
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
|
|
253
272
|
allow="clipboard-read; clipboard-write"
|
|
254
273
|
/>
|
|
274
|
+
{/* Transparent overlay during resize to prevent iframe from stealing mouse events */}
|
|
275
|
+
{(isResizing || isDragging) && (
|
|
276
|
+
<div className="absolute inset-0 z-10" />
|
|
277
|
+
)}
|
|
255
278
|
</div>
|
|
256
279
|
|
|
257
280
|
{/* Footer */}
|
|
@@ -267,14 +290,21 @@ export function DocBrowser() {
|
|
|
267
290
|
</a>
|
|
268
291
|
</div>
|
|
269
292
|
|
|
270
|
-
{/* Resize
|
|
293
|
+
{/* Resize Handles (floating only) */}
|
|
271
294
|
{!isDocked && (
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
onMouseDown={onResizeStart}
|
|
275
|
-
|
|
276
|
-
<
|
|
277
|
-
|
|
295
|
+
<>
|
|
296
|
+
{/* Right edge */}
|
|
297
|
+
<div className="absolute top-0 right-0 w-1.5 h-full cursor-ew-resize z-20 hover:bg-primary/10 transition-colors" onMouseDown={onResizeStart} data-axis="x" />
|
|
298
|
+
{/* Bottom edge */}
|
|
299
|
+
<div className="absolute bottom-0 left-0 h-1.5 w-full cursor-ns-resize z-20 hover:bg-primary/10 transition-colors" onMouseDown={onResizeStart} data-axis="y" />
|
|
300
|
+
{/* Bottom-right corner */}
|
|
301
|
+
<div
|
|
302
|
+
className="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize z-30 flex items-center justify-center text-gray-300 hover:text-gray-500 transition-colors"
|
|
303
|
+
onMouseDown={onResizeStart}
|
|
304
|
+
>
|
|
305
|
+
<GripVertical className="w-3 h-3 rotate-[-45deg]" />
|
|
306
|
+
</div>
|
|
307
|
+
</>
|
|
278
308
|
)}
|
|
279
309
|
</div>
|
|
280
310
|
);
|
|
@@ -86,12 +86,20 @@ export function DocBrowserProvider({ children }: { children: ReactNode }) {
|
|
|
86
86
|
}, []);
|
|
87
87
|
|
|
88
88
|
const navigate = useCallback((url: string) => {
|
|
89
|
-
setState(prev =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
setState(prev => {
|
|
90
|
+
// Normalize URLs for comparison (strip trailing slash, .html suffix)
|
|
91
|
+
const normalize = (u: string) => {
|
|
92
|
+
try { return new URL(u).pathname.replace(/\.html$/, '').replace(/\/$/, ''); } catch { return u; }
|
|
93
|
+
};
|
|
94
|
+
// Skip if same as current URL (prevents loop from postMessage echo)
|
|
95
|
+
if (normalize(url) === normalize(prev.currentUrl)) return prev;
|
|
96
|
+
return {
|
|
97
|
+
...prev,
|
|
98
|
+
currentUrl: url,
|
|
99
|
+
history: [...prev.history.slice(0, prev.historyIndex + 1), url],
|
|
100
|
+
historyIndex: prev.historyIndex + 1,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
95
103
|
}, []);
|
|
96
104
|
|
|
97
105
|
const goBack = useCallback(() => {
|
|
@@ -18,6 +18,9 @@ export function useDocLinkInterceptor() {
|
|
|
18
18
|
const href = anchor.getAttribute('href') || '';
|
|
19
19
|
if (!isDocsUrl(href)) return;
|
|
20
20
|
|
|
21
|
+
// Don't intercept links explicitly marked for external opening
|
|
22
|
+
if (anchor.hasAttribute('data-doc-external')) return;
|
|
23
|
+
|
|
21
24
|
// Don't intercept if modifier keys are held (user wants new tab behavior)
|
|
22
25
|
if (e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
23
26
|
|
package/src/lib/i18n.ts
CHANGED
|
@@ -164,6 +164,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
164
164
|
// Doc Browser
|
|
165
165
|
docBrowserTitle: { zh: '帮助文档', en: 'Help Docs' },
|
|
166
166
|
docBrowserSearchPlaceholder: { zh: '搜索,也可以输入文档地址直接打开', en: 'Search, or enter a doc URL to open' },
|
|
167
|
+
docBrowserUrlPlaceholder: { zh: '输入文档路径,如 /guide/channels', en: 'Enter a doc path, e.g. /guide/channels' },
|
|
167
168
|
docBrowserOpenExternal: { zh: '文档中心打开', en: 'Open in Docs' },
|
|
168
169
|
docBrowserFloatMode: { zh: '悬浮窗口', en: 'Float Window' },
|
|
169
170
|
docBrowserDockMode: { zh: '固定到侧栏', en: 'Dock to Sidebar' },
|