dsh-vscode-mode 0.15.3 → 0.15.4
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/README.md +6 -3
- package/lib/client.js +286 -301
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/state/floatingWindow.ts +50 -0
- package/src/client/styles/editor.css +14 -1
- package/src/client/ui/EditorView.ts +24 -28
- package/src/client/ui/FloatingEditor.ts +16 -70
- package/src/client/ui/FloatingWindow.ts +121 -0
- package/src/client/ui/SvnAiPlanDialog.ts +4 -4
- package/src/client/ui/SvnLogDialog.ts +27 -101
- package/src/client/ui/SvnLogStats.ts +4 -5
- package/src/client/ui/SvnPatchDialog.ts +4 -3
- package/src/client/ui/SvnSumDialog.ts +4 -3
- package/src/client/ui/SvnUpdateDialog.ts +4 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.4",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 DSH 0.1.5+ 官方右侧 Sidebar 与对话同屏)+ 命令栏(Ctrl+Shift+P)与可扩展指令系统 + VS Code 兼容代码片段(.code-snippets 全局/项目,IntelliSense 展开)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ SVN 集成(侧栏变更面板/日志弹窗/并排差异)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车,界面状态(页签/光标/侧栏/编辑 Tab/导航历史)镜像到 DSH home 跨重启恢复",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"keywords": [
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Shared nonmodal geometry and bounded stacking. @author ddj 2026年10月14号 */
|
|
2
|
+
import { clampFloat } from './editorLayout.js'
|
|
3
|
+
import type { FloatRect } from './editorLayout.js'
|
|
4
|
+
|
|
5
|
+
const windows: string[] = []
|
|
6
|
+
const listeners = new Set<() => void>()
|
|
7
|
+
let version = 0
|
|
8
|
+
|
|
9
|
+
/** @private @author ddj 2026年10月14号 Notify every mounted window after an order change. */
|
|
10
|
+
function notify(): void { version += 1; for (const listener of listeners) listener() }
|
|
11
|
+
/** @public @author ddj 2026年10月14号 @param listener Order observer. @returns Unsubscribe callback. */
|
|
12
|
+
export function watchFloats(listener: () => void): () => void {
|
|
13
|
+
listeners.add(listener)
|
|
14
|
+
return () => { listeners.delete(listener) }
|
|
15
|
+
}
|
|
16
|
+
/** @public @author ddj 2026年10月14号 @returns Stable external-store revision. */
|
|
17
|
+
export function floatVersion(): number { return version }
|
|
18
|
+
/** @public @author ddj 2026年10月14号 @param id Window identity. Bring forward without exceeding modal layers. */
|
|
19
|
+
export function raiseFloat(id: string): void {
|
|
20
|
+
const index = windows.indexOf(id)
|
|
21
|
+
if (index === windows.length - 1 && index >= 0) return
|
|
22
|
+
if (index >= 0) windows.splice(index, 1)
|
|
23
|
+
windows.push(id)
|
|
24
|
+
notify()
|
|
25
|
+
}
|
|
26
|
+
/** @public @author ddj 2026年10月14号 @param id Unmounted window identity. */
|
|
27
|
+
export function dropFloat(id: string): void {
|
|
28
|
+
const index = windows.indexOf(id)
|
|
29
|
+
if (index < 0) return
|
|
30
|
+
windows.splice(index, 1)
|
|
31
|
+
notify()
|
|
32
|
+
}
|
|
33
|
+
/** @public @author ddj 2026年10月14号 @param id Window identity. @returns Layer below menus/modals (190+). */
|
|
34
|
+
export function floatLayer(id: string): number {
|
|
35
|
+
return 60 + Math.floor(120 * Math.max(0, windows.indexOf(id)) / Math.max(1, windows.length))
|
|
36
|
+
}
|
|
37
|
+
/** @public @author ddj 2026年10月14号 @param width Preferred width. @param height Preferred height. @param vw Viewport width. @param vh Viewport height. @returns Centered, reachable bounds. */
|
|
38
|
+
export function initialFloat(width: number, height: number, vw: number, vh: number): FloatRect {
|
|
39
|
+
return clampFloat({ width, height, left: (vw - width) / 2, top: (vh - height) / 2 }, vw, vh)
|
|
40
|
+
}
|
|
41
|
+
/** @public @author ddj 2026年10月14号 @param rect Gesture origin. @param dx Horizontal delta. @param dy Vertical delta. @param sizing Resize instead of move. @param vw Viewport width. @param vh Viewport height. @returns Clamped next bounds. */
|
|
42
|
+
export function moveFloat(rect: FloatRect, dx: number, dy: number, sizing: boolean, vw: number, vh: number): FloatRect {
|
|
43
|
+
const next = sizing ? { ...rect, width: rect.width + dx, height: rect.height + dy }
|
|
44
|
+
: { ...rect, left: rect.left + dx, top: rect.top + dy }
|
|
45
|
+
return clampFloat(next, vw, vh)
|
|
46
|
+
}
|
|
47
|
+
/** @public @author ddj 2026年10月14号 @param target Key target. @returns Whether native text/control navigation owns the key. */
|
|
48
|
+
export function inputOwnsKey(target: EventTarget | null): boolean {
|
|
49
|
+
return target instanceof Element && !!target.closest('input, textarea, select, [contenteditable="true"], .monaco-editor')
|
|
50
|
+
}
|
|
@@ -994,7 +994,20 @@
|
|
|
994
994
|
[data-edrv-view] .edrv-split-handle:hover, [data-edrv-view] .edrv-split-handle:focus-visible { background: var(--dsw-alias-state-info-primary, #4d6bfe); }
|
|
995
995
|
[data-edrv-view].edrv-floating { position: fixed; display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-base, #fff); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: 0 8px 28px rgba(0,0,0,.22); }
|
|
996
996
|
[data-edrv-view] .edrv-floating-title { display: flex; align-items: center; gap: 8px; flex: 0 0 32px; padding: 0 8px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); cursor: move; touch-action: none; user-select: none; }
|
|
997
|
-
[data-edrv-view] .edrv-floating-title > span { flex: 1; }
|
|
997
|
+
[data-edrv-view] .edrv-floating-title > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
998
|
+
[data-edrv-view] .edrv-floating-content { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; overflow: auto; box-sizing: border-box; }
|
|
999
|
+
[data-edrv-view] .edrv-floating-content.edrv-svnlog-dialog { padding: 12px; overflow: auto; }
|
|
1000
|
+
[data-edrv-view] .edrv-floating-content.edrv-svn-patch-dialog,
|
|
1001
|
+
[data-edrv-view] .edrv-floating-content.edrv-svn-sum-dialog,
|
|
1002
|
+
[data-edrv-view] .edrv-floating-content.edrv-svnplan-dialog { padding: 12px; max-width: none; gap: 8px; }
|
|
1003
|
+
[data-edrv-view] .edrv-floating-content.edrv-svnlog-stats { max-height: none; }
|
|
1004
|
+
[data-edrv-view] .edrv-floating-content .edrv-svn-patch-pre,
|
|
1005
|
+
[data-edrv-view] .edrv-floating-content .edrv-svn-sum-list { flex: 1; min-height: 0; max-height: none; }
|
|
1006
|
+
[data-edrv-view] .edrv-floating-content .edrv-svnlog-filter,
|
|
1007
|
+
[data-edrv-view] .edrv-floating-content .edrv-svn-sum-form { flex-wrap: wrap; }
|
|
1008
|
+
[data-edrv-view] .edrv-floating-content > .edrv-svnlog-head,
|
|
1009
|
+
[data-edrv-view] .edrv-floating-content > .edrv-svnlog-filter,
|
|
1010
|
+
[data-edrv-view] .edrv-floating-content > .edrv-svnlog-foot { flex-shrink: 0; }
|
|
998
1011
|
[data-edrv-view] .edrv-floating-title > button { border: 0; background: transparent; color: inherit; cursor: pointer; }
|
|
999
1012
|
[data-edrv-view] .edrv-floating-grip { position: absolute; width: 16px; height: 16px; bottom: 0; right: 0; cursor: nwse-resize; touch-action: none; border-right: 3px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 3px solid var(--dsw-alias-border-l2, #cdd6da); }
|
|
1000
1013
|
.edrv-drag-overlay { position: fixed; z-index: 150; pointer-events: none; box-sizing: border-box; border: 0; background: color-mix(in srgb, var(--dsw-alias-state-info-primary, #4d6bfe) 25%, transparent); }
|
|
@@ -11,6 +11,7 @@ import React from 'react'
|
|
|
11
11
|
import { createPortal } from 'react-dom'
|
|
12
12
|
import { EditorPane, EditorSplit, PassiveEditor } from './EditorPane.js'
|
|
13
13
|
import { FloatingEditor } from './FloatingEditor.js'
|
|
14
|
+
import { FloatingWindow } from './FloatingWindow.js'
|
|
14
15
|
import { createLayout, parseLayout, syncTabs, ownerOf, focusGroup, moveTab, returnGroup, GROUP_LIMIT, MAIN_GROUP } from '../state/editorLayout.js'
|
|
15
16
|
import { createTabDrag, hitTarget } from '../tabDrag.js'
|
|
16
17
|
import { createDocStore } from '../state/documentStore.js'
|
|
@@ -3505,8 +3506,7 @@ export function EditorView(props) {
|
|
|
3505
3506
|
//(focusDiff=true 跳过:差异审查对象必为本插件编辑的文本文件,审查优先);
|
|
3506
3507
|
// handler 缺失/失败(旧版无 openResource / seat 未挂)→ 回退现状页签打开,不阻断
|
|
3507
3508
|
if (!focusDiff && inNativePath(path) && tryNativeOpen(path)) return
|
|
3508
|
-
//
|
|
3509
|
-
setSvnDiff(null)
|
|
3509
|
+
// 对比浮窗独立保留,允许同时打开并编辑其他文件。
|
|
3510
3510
|
recordNav()
|
|
3511
3511
|
// G9:统一归一为页签规范形态(差异栏/启动器/树/命令栏等入口一致)
|
|
3512
3512
|
const normalized = addTabNorm(path, true)
|
|
@@ -3555,7 +3555,6 @@ export function EditorView(props) {
|
|
|
3555
3555
|
if (!document) return
|
|
3556
3556
|
flushSave()
|
|
3557
3557
|
saveViewState(active)
|
|
3558
|
-
setSvnDiff(null)
|
|
3559
3558
|
setDiskActive(null)
|
|
3560
3559
|
setPlanTabs((prev) => [...prev.filter((item) => item.address !== document.address), document].slice(-32))
|
|
3561
3560
|
setPlanActive(document.address)
|
|
@@ -3578,7 +3577,6 @@ export function EditorView(props) {
|
|
|
3578
3577
|
flushSave()
|
|
3579
3578
|
saveViewState(active)
|
|
3580
3579
|
recordNav()
|
|
3581
|
-
setSvnDiff(null)
|
|
3582
3580
|
const positioned = request.line != null || request.column != null
|
|
3583
3581
|
if (positioned && mdPreviewRef.current.has(path)) toggleMdPreview(path)
|
|
3584
3582
|
setError(null)
|
|
@@ -3611,7 +3609,7 @@ export function EditorView(props) {
|
|
|
3611
3609
|
const image = imgElRef.current
|
|
3612
3610
|
return {
|
|
3613
3611
|
sessionId, cwd, active, sequence: loadSeqRef.current, load: receiptLoad,
|
|
3614
|
-
mounted: Boolean(viewRootRef.current), blocked:
|
|
3612
|
+
mounted: Boolean(viewRootRef.current), blocked: false,
|
|
3615
3613
|
kind: isImageActive ? 'image' : isPdfActive ? 'pdf' : mdPreviewing ? 'markdown' : 'text',
|
|
3616
3614
|
contentPath, contentReady, modelMatches, positionMatches,
|
|
3617
3615
|
imageLoaded: Boolean(imageSrc && !imgBroken && image?.getAttribute('src') === imageSrc && image.complete && image.naturalWidth > 0),
|
|
@@ -3624,7 +3622,7 @@ export function EditorView(props) {
|
|
|
3624
3622
|
function receiptReady(request) {
|
|
3625
3623
|
if (request.plan) return request.sessionId === sessionId && planActive === request.plan.address
|
|
3626
3624
|
&& planTabs.some((item) => item.address === request.plan.address && item.markdown === request.plan.markdown)
|
|
3627
|
-
&& planHostRef.current?.getAttribute('data-plan-address') === request.plan.address
|
|
3625
|
+
&& planHostRef.current?.getAttribute('data-plan-address') === request.plan.address
|
|
3628
3626
|
return receiptReadyFor(request, receiptView(request))
|
|
3629
3627
|
}
|
|
3630
3628
|
/** @private @author ddj 2026年09月28号 @param request Claimed target. @returns Only its current load/render error. */
|
|
@@ -4013,27 +4011,6 @@ export function EditorView(props) {
|
|
|
4013
4011
|
: React.createElement('pre', { className: 'edrv-mdview-pre' }, text)
|
|
4014
4012
|
body = React.createElement('div', { className: 'edrv-mdview', ref: planHostRef, 'data-plan-address': activePlan.address },
|
|
4015
4013
|
React.createElement('div', { className: 'edrv-mdview-stage' }, React.createElement('div', { className: 'edrv-mdview-doc' }, render)))
|
|
4016
|
-
} else if (svnDiff) {
|
|
4017
|
-
// 差异视图优先占位(覆盖编辑器主体):只读审阅态,关闭后回到原编辑器
|
|
4018
|
-
body = React.createElement(SvnDiffPanel, {
|
|
4019
|
-
key: 'edrv-svndiff:' + svnDiff.scheme + ':' + svnDiff.path,
|
|
4020
|
-
monaco,
|
|
4021
|
-
path: svnDiff.path,
|
|
4022
|
-
base: svnDiff.base,
|
|
4023
|
-
working: svnDiff.working,
|
|
4024
|
-
reason: svnDiff.reason,
|
|
4025
|
-
message: svnDiff.message,
|
|
4026
|
-
leftLabel: svnDiff.leftLabel,
|
|
4027
|
-
rightLabel: svnDiff.rightLabel,
|
|
4028
|
-
onBack: svnDiff.logTarget !== undefined ? backToSvnLog : undefined,
|
|
4029
|
-
onClose: () => {
|
|
4030
|
-
setSvnDiff(null)
|
|
4031
|
-
if (svnUpdateReturn?.from === 'diff') {
|
|
4032
|
-
setSvnUpdateDialog(svnUpdateReturn.dialog)
|
|
4033
|
-
setSvnUpdateReturn(null)
|
|
4034
|
-
}
|
|
4035
|
-
},
|
|
4036
|
-
})
|
|
4037
4014
|
} else if (!monaco && !monacoErr) {
|
|
4038
4015
|
body = loadingBody(loadStage.message, loadStage.progress)
|
|
4039
4016
|
} else if (monacoErr) {
|
|
@@ -4329,7 +4306,7 @@ export function EditorView(props) {
|
|
|
4329
4306
|
}
|
|
4330
4307
|
|
|
4331
4308
|
// 「添加配置」按钮(VSCode 同款形态,编辑区右下角):仅插件专属 launch.json 打开且非差异审阅态显示
|
|
4332
|
-
const addCfgBtn = (active &&
|
|
4309
|
+
const addCfgBtn = (active && LAUNCH_JSON_RE.test(String(active).replace(/\\/g, '/')))
|
|
4333
4310
|
? React.createElement('button', {
|
|
4334
4311
|
className: 'edrv-addcfg',
|
|
4335
4312
|
title: '添加调试配置(插入到 configurations 数组开头)',
|
|
@@ -4517,6 +4494,23 @@ export function EditorView(props) {
|
|
|
4517
4494
|
},
|
|
4518
4495
|
})
|
|
4519
4496
|
: null
|
|
4497
|
+
/** @private @author ddj 2026年10月14号 关闭对比浮窗,按原 job 恢复来源更新窗口。 */
|
|
4498
|
+
function closeSvnDiff() {
|
|
4499
|
+
setSvnDiff(null)
|
|
4500
|
+
if (svnUpdateReturn?.from === 'diff') {
|
|
4501
|
+
setSvnUpdateDialog(svnUpdateReturn.dialog)
|
|
4502
|
+
setSvnUpdateReturn(null)
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
const svnDiffEl = svnDiff ? React.createElement(FloatingWindow, {
|
|
4506
|
+
key: 'edrv-svndiff-window:' + scope,
|
|
4507
|
+
title: '文件对比 · ' + baseNameOf(svnDiff.path), width: 1100, height: 680,
|
|
4508
|
+
owner: String(scope), onClose: closeSvnDiff,
|
|
4509
|
+
}, React.createElement(SvnDiffPanel, {
|
|
4510
|
+
...svnDiff, key: 'edrv-svndiff:' + svnDiff.scheme + ':' + svnDiff.path, monaco,
|
|
4511
|
+
onBack: svnDiff.logTarget !== undefined ? backToSvnLog : undefined,
|
|
4512
|
+
onClose: closeSvnDiff,
|
|
4513
|
+
})) : null
|
|
4520
4514
|
// W2-1 补丁对话框 / W2-2 目录对比对话框:portal 到 body,与日志弹窗同一挂载方式
|
|
4521
4515
|
const patchDialogEl = patchDialog
|
|
4522
4516
|
? React.createElement(SvnPatchDialog, {
|
|
@@ -4613,6 +4607,7 @@ export function EditorView(props) {
|
|
|
4613
4607
|
paletteEl,
|
|
4614
4608
|
svnLogEl,
|
|
4615
4609
|
svnUpdateEl,
|
|
4610
|
+
svnDiffEl,
|
|
4616
4611
|
patchDialogEl,
|
|
4617
4612
|
sumDialogEl,
|
|
4618
4613
|
dlogEl,
|
|
@@ -4625,6 +4620,7 @@ export function EditorView(props) {
|
|
|
4625
4620
|
paletteEl,
|
|
4626
4621
|
svnLogEl,
|
|
4627
4622
|
svnUpdateEl,
|
|
4623
|
+
svnDiffEl,
|
|
4628
4624
|
patchDialogEl,
|
|
4629
4625
|
sumDialogEl,
|
|
4630
4626
|
dlogEl,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/** In-page window
|
|
1
|
+
/** In-page editor window; closing returns tabs, never closes documents. @author ddj 2026年10月14号 */
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import type { FloatRect } from '../state/editorLayout.js'
|
|
4
|
+
import { FloatingWindow } from './FloatingWindow.js'
|
|
5
5
|
|
|
6
6
|
export interface FloatingProps {
|
|
7
7
|
group: string
|
|
@@ -15,73 +15,19 @@ export interface FloatingProps {
|
|
|
15
15
|
children?: React.ReactNode
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/** @public @author ddj 2026年10月14号 @param props Window geometry and group callbacks. @returns
|
|
19
|
-
export function FloatingEditor(props: FloatingProps): React.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const cleanup = React.useRef<(() => void) | null>(null)
|
|
23
|
-
/** @private @author ddj 2026年10月14号 Cancel a gesture on unmount without altering tabs. */
|
|
24
|
-
function cancel(): void { cleanup.current?.(); cleanup.current = null }
|
|
25
|
-
React.useEffect(() => cancel, [])
|
|
26
|
-
React.useEffect(() => {
|
|
27
|
-
/** @private @author ddj 2026年10月14号 Keep title/chrome visible after viewport changes. */
|
|
28
|
-
function resize(): void {
|
|
29
|
-
const current = latest.current
|
|
30
|
-
current.onBounds(current.group, clampFloat(current.rect, window.innerWidth, window.innerHeight))
|
|
31
|
-
}
|
|
32
|
-
window.addEventListener('resize', resize)
|
|
33
|
-
resize()
|
|
34
|
-
return () => window.removeEventListener('resize', resize)
|
|
35
|
-
}, [])
|
|
36
|
-
|
|
37
|
-
/** @private @author ddj 2026年10月14号 @param event Handle press. @param sizing Resize rather than move. */
|
|
38
|
-
function start(event: React.PointerEvent, sizing: boolean): void {
|
|
39
|
-
if (event.button !== 0 || !event.isPrimary) return
|
|
40
|
-
cancel()
|
|
41
|
-
event.preventDefault()
|
|
42
|
-
const initial = latest.current.rect
|
|
43
|
-
const x = event.clientX, y = event.clientY, pointer = event.pointerId
|
|
44
|
-
let frame: number | null = null
|
|
45
|
-
let next = initial
|
|
46
|
-
latest.current.onFocus(props.group)
|
|
47
|
-
/** @private @author ddj 2026年10月14号 Apply at most one geometry change per animation frame. */
|
|
48
|
-
function paint(): void { frame = null; latest.current.onBounds(props.group, next) }
|
|
49
|
-
/** @private @author ddj 2026年10月14号 @param moveEvent Pointer movement for the originating pointer. */
|
|
50
|
-
function move(moveEvent: PointerEvent): void {
|
|
51
|
-
if (moveEvent.pointerId !== pointer) return
|
|
52
|
-
moveEvent.preventDefault()
|
|
53
|
-
const dx = moveEvent.clientX - x, dy = moveEvent.clientY - y
|
|
54
|
-
next = clampFloat(sizing ? { ...initial, width: initial.width + dx, height: initial.height + dy } : { ...initial, left: initial.left + dx, top: initial.top + dy }, window.innerWidth, window.innerHeight)
|
|
55
|
-
if (frame === null) frame = requestAnimationFrame(paint)
|
|
56
|
-
}
|
|
57
|
-
/** @private @author ddj 2026年10月14号 Remove gesture listeners and pending frames. */
|
|
58
|
-
function stop(): void {
|
|
59
|
-
if (frame !== null) { cancelAnimationFrame(frame); paint() }
|
|
60
|
-
window.removeEventListener('pointermove', move, true)
|
|
61
|
-
window.removeEventListener('pointerup', stop, true)
|
|
62
|
-
window.removeEventListener('pointercancel', stop, true)
|
|
63
|
-
window.removeEventListener('blur', stop)
|
|
64
|
-
cleanup.current = null
|
|
65
|
-
}
|
|
66
|
-
cleanup.current = stop
|
|
67
|
-
window.addEventListener('pointermove', move, true)
|
|
68
|
-
window.addEventListener('pointerup', stop, true)
|
|
69
|
-
window.addEventListener('pointercancel', stop, true)
|
|
70
|
-
window.addEventListener('blur', stop)
|
|
71
|
-
}
|
|
72
|
-
/** @private @author ddj 2026年10月14号 @param event Title press. Move window without stealing button presses. */
|
|
73
|
-
function moveStart(event: React.PointerEvent): void { if (!(event.target as Element).closest('button')) start(event, false) }
|
|
74
|
-
/** @private @author ddj 2026年10月14号 @param event Resize grip press. */
|
|
75
|
-
function sizeStart(event: React.PointerEvent): void { start(event, true) }
|
|
18
|
+
/** @public @author ddj 2026年10月14号 @param props Window geometry and group callbacks. @returns Shared window chrome preserving tab ownership. */
|
|
19
|
+
export function FloatingEditor(props: FloatingProps): React.ReactElement {
|
|
20
|
+
/** @private @author ddj 2026年10月14号 @param rect Updated window bounds. */
|
|
21
|
+
function bounds(rect: FloatRect): void { props.onBounds(props.group, rect) }
|
|
76
22
|
/** @private @author ddj 2026年10月14号 Return all tabs to the main group. */
|
|
77
|
-
function returnTabs(): void {
|
|
23
|
+
function returnTabs(): void { props.onReturn(props.group) }
|
|
78
24
|
/** @private @author ddj 2026年10月14号 Bring the group to the foreground. */
|
|
79
|
-
function focus(): void {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
React.createElement('
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
25
|
+
function focus(): void { props.onFocus(props.group) }
|
|
26
|
+
const actions = React.createElement(React.Fragment, null,
|
|
27
|
+
React.createElement('button', { type: 'button', title: '将全部文件移回主编辑区', onClick: returnTabs }, '移回主编辑区'),
|
|
28
|
+
React.createElement('button', { type: 'button', title: '关闭浮窗并将文件移回主编辑区', 'aria-label': '关闭浮窗并移回文件', onClick: returnTabs }, '×'))
|
|
29
|
+
return React.createElement(FloatingWindow, {
|
|
30
|
+
title: '浮动编辑组', owner: props.owner, group: props.group, rect: props.rect,
|
|
31
|
+
closeOnEsc: false, onBounds: bounds, onFocus: focus, actions,
|
|
32
|
+
}, props.children)
|
|
87
33
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** Shared in-page window chrome, without a mask or focus trap. @author ddj 2026年10月14号 */
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { createPortal } from 'react-dom'
|
|
4
|
+
import { clampFloat } from '../state/editorLayout.js'
|
|
5
|
+
import type { FloatRect } from '../state/editorLayout.js'
|
|
6
|
+
import { dropFloat, floatLayer, floatVersion, initialFloat, moveFloat, raiseFloat, watchFloats } from '../state/floatingWindow.js'
|
|
7
|
+
|
|
8
|
+
export interface WindowProps {
|
|
9
|
+
title: string
|
|
10
|
+
dialogClass?: string
|
|
11
|
+
width?: number
|
|
12
|
+
height?: number
|
|
13
|
+
rect?: FloatRect
|
|
14
|
+
owner?: string
|
|
15
|
+
group?: string
|
|
16
|
+
closeOnEsc?: boolean
|
|
17
|
+
onClose?: () => void
|
|
18
|
+
onBounds?: (rect: FloatRect) => void
|
|
19
|
+
onFocus?: () => void
|
|
20
|
+
onKeyDown?: (event: React.KeyboardEvent) => void
|
|
21
|
+
actions?: React.ReactNode
|
|
22
|
+
children?: React.ReactNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @private @author ddj 2026年10月14号 @param start Originating press. @param rect Initial bounds. @param sizing Resize mode. @param apply Commit bounds. @param done Gesture cleanup notification. @returns Gesture cancellation. */
|
|
26
|
+
function beginGesture(start: React.PointerEvent, rect: FloatRect, sizing: boolean, apply: (rect: FloatRect) => void, done: () => void): () => void {
|
|
27
|
+
let frame: number | null = null
|
|
28
|
+
let next = rect
|
|
29
|
+
/** @private @author ddj 2026年10月14号 Apply no more than once per frame. */
|
|
30
|
+
function paint(): void { frame = null; apply(next) }
|
|
31
|
+
/** @private @author ddj 2026年10月14号 @param event Pointer motion. Ignore unrelated pointers. */
|
|
32
|
+
function move(event: PointerEvent): void {
|
|
33
|
+
if (event.pointerId !== start.pointerId) return
|
|
34
|
+
event.preventDefault()
|
|
35
|
+
next = moveFloat(rect, event.clientX - start.clientX, event.clientY - start.clientY, sizing, window.innerWidth, window.innerHeight)
|
|
36
|
+
if (frame === null) frame = requestAnimationFrame(paint)
|
|
37
|
+
}
|
|
38
|
+
/** @private @author ddj 2026年10月14号 Remove listeners and commit pending geometry. */
|
|
39
|
+
function stop(): void {
|
|
40
|
+
if (frame !== null) { cancelAnimationFrame(frame); paint() }
|
|
41
|
+
window.removeEventListener('pointermove', move, true)
|
|
42
|
+
window.removeEventListener('pointerup', end, true)
|
|
43
|
+
window.removeEventListener('pointercancel', end, true)
|
|
44
|
+
window.removeEventListener('blur', stop)
|
|
45
|
+
done()
|
|
46
|
+
}
|
|
47
|
+
/** @private @author ddj 2026年10月14号 @param event Pointer release. Only end originating gesture. */
|
|
48
|
+
function end(event: PointerEvent): void { if (event.pointerId === start.pointerId) stop() }
|
|
49
|
+
window.addEventListener('pointermove', move, true)
|
|
50
|
+
window.addEventListener('pointerup', end, true)
|
|
51
|
+
window.addEventListener('pointercancel', end, true)
|
|
52
|
+
window.addEventListener('blur', stop)
|
|
53
|
+
return stop
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @public @author ddj 2026年10月14号 @param props Window contents and lifecycle callbacks. @returns Movable, resizable nonmodal portal. */
|
|
57
|
+
export function FloatingWindow(props: WindowProps): React.ReactPortal {
|
|
58
|
+
const id = React.useId()
|
|
59
|
+
const root = React.useRef<HTMLDivElement>(null)
|
|
60
|
+
const latest = React.useRef(props)
|
|
61
|
+
latest.current = props
|
|
62
|
+
const [bounds, setBounds] = React.useState(() => initialFloat(props.width ?? 760, props.height ?? 600, window.innerWidth, window.innerHeight))
|
|
63
|
+
const current = React.useRef(props.rect ?? bounds)
|
|
64
|
+
current.current = props.rect ?? bounds
|
|
65
|
+
const cleanup = React.useRef<(() => void) | null>(null)
|
|
66
|
+
React.useSyncExternalStore(watchFloats, floatVersion, floatVersion)
|
|
67
|
+
/** @private @author ddj 2026年10月14号 @param rect Next geometry. Notify controlled owner or retain local geometry. */
|
|
68
|
+
function apply(rect: FloatRect): void {
|
|
69
|
+
if (latest.current.rect) latest.current.onBounds?.(rect)
|
|
70
|
+
else setBounds(rect)
|
|
71
|
+
}
|
|
72
|
+
/** @private @author ddj 2026年10月14号 End any active gesture. */
|
|
73
|
+
function cancel(): void { cleanup.current?.(); cleanup.current = null }
|
|
74
|
+
/** @private @author ddj 2026年10月14号 Re-clamp after viewport resize. */
|
|
75
|
+
function resize(): void { cancel(); apply(clampFloat(current.current, window.innerWidth, window.innerHeight)) }
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
raiseFloat(id)
|
|
78
|
+
window.addEventListener('resize', resize)
|
|
79
|
+
resize()
|
|
80
|
+
return () => { cancel(); window.removeEventListener('resize', resize); dropFloat(id) }
|
|
81
|
+
}, [id])
|
|
82
|
+
/** @private @author ddj 2026年10月14号 Activate without stealing text input focus. */
|
|
83
|
+
function focus(): void { raiseFloat(id); latest.current.onFocus?.() }
|
|
84
|
+
/** @private @author ddj 2026年10月14号 @param event Window press. Give noninteractive content a keyboard owner. */
|
|
85
|
+
function activate(event: React.PointerEvent): void {
|
|
86
|
+
focus()
|
|
87
|
+
if (!(event.target as Element).closest('button, input, select, textarea, a, [contenteditable], .monaco-editor')) root.current?.focus({ preventScroll: true })
|
|
88
|
+
}
|
|
89
|
+
/** @private @author ddj 2026年10月14号 @param event Handle press. @param sizing Resize instead of move. */
|
|
90
|
+
function start(event: React.PointerEvent, sizing: boolean): void {
|
|
91
|
+
if (event.button !== 0 || !event.isPrimary) return
|
|
92
|
+
cancel()
|
|
93
|
+
event.preventDefault()
|
|
94
|
+
cleanup.current = beginGesture(event, current.current, sizing, apply, clear)
|
|
95
|
+
}
|
|
96
|
+
/** @private @author ddj 2026年10月14号 Clear completed gesture reference. */
|
|
97
|
+
function clear(): void { cleanup.current = null }
|
|
98
|
+
/** @private @author ddj 2026年10月14号 @param event Title press. Preserve title action buttons. */
|
|
99
|
+
function moveStart(event: React.PointerEvent): void { if (!(event.target as Element).closest('button')) start(event, false) }
|
|
100
|
+
/** @private @author ddj 2026年10月14号 @param event Resize press. */
|
|
101
|
+
function sizeStart(event: React.PointerEvent): void { start(event, true) }
|
|
102
|
+
/** @private @author ddj 2026年10月14号 @param event Local key. Exclude React-bubbling child portals. */
|
|
103
|
+
function keyDown(event: React.KeyboardEvent): void {
|
|
104
|
+
if ((event.target as Element).closest('[data-edrv-window]') !== root.current || event.defaultPrevented) return
|
|
105
|
+
if (event.key === 'Escape' && props.closeOnEsc !== false && props.onClose) {
|
|
106
|
+
event.preventDefault(); event.stopPropagation(); props.onClose(); return
|
|
107
|
+
}
|
|
108
|
+
props.onKeyDown?.(event)
|
|
109
|
+
}
|
|
110
|
+
const rect = clampFloat(props.rect ?? bounds, window.innerWidth, window.innerHeight)
|
|
111
|
+
return createPortal(React.createElement('div', {
|
|
112
|
+
ref: root, tabIndex: -1, role: 'dialog', 'aria-label': props.title, 'aria-modal': false,
|
|
113
|
+
className: 'edrv-floating', 'data-edrv-view': '1', 'data-edrv-window': id,
|
|
114
|
+
'data-edrv-float': props.group, 'data-edrv-owner': props.owner,
|
|
115
|
+
style: { ...rect, zIndex: floatLayer(id) }, onPointerDownCapture: activate, onFocusCapture: focus, onKeyDown: keyDown,
|
|
116
|
+
},
|
|
117
|
+
React.createElement('div', { className: 'edrv-floating-title', onPointerDown: moveStart },
|
|
118
|
+
React.createElement('span', null, props.title), props.actions),
|
|
119
|
+
React.createElement('div', { className: 'edrv-floating-content' + (props.dialogClass ? ' ' + props.dialogClass : '') }, props.children),
|
|
120
|
+
React.createElement('div', { className: 'edrv-floating-grip', title: '拖动调整窗口大小', onPointerDown: sizeStart })), document.body)
|
|
121
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 作者 ddj 2026年09月23号
|
|
15
15
|
*/
|
|
16
16
|
import React from 'react'
|
|
17
|
-
import {
|
|
17
|
+
import { FloatingWindow } from './FloatingWindow.js'
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* 预览勾选初值:分组默认全勾(changelist 可逆),还原/忽略默认不勾(有副作用)。
|
|
@@ -282,11 +282,11 @@ export function SvnAiPlanDialog(props) {
|
|
|
282
282
|
}, '取消剩余'))))
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
return React.createElement(
|
|
285
|
+
return React.createElement(FloatingWindow, {
|
|
286
|
+
title: 'SVN 智能整理', height: 600,
|
|
286
287
|
key: 'edrv-svn-plan',
|
|
287
288
|
dialogClass: 'edrv-svnplan-dialog',
|
|
288
|
-
width:
|
|
289
|
-
closeOnMask: !running,
|
|
289
|
+
width: 680,
|
|
290
290
|
closeOnEsc: !running,
|
|
291
291
|
onClose,
|
|
292
292
|
}, header, metaEl, body, footer)
|
|
@@ -26,6 +26,8 @@ import { SVN_LOG_ACTION_LABEL } from '../../shared/svn.js'
|
|
|
26
26
|
import { orderLogEntries } from '../../svnLog.js'
|
|
27
27
|
import { SVN_LOG_SHOW_ALL_LIMIT } from '../svnLog.js'
|
|
28
28
|
import { ModalShell } from './ModalShell.js'
|
|
29
|
+
import { FloatingWindow } from './FloatingWindow.js'
|
|
30
|
+
import { inputOwnsKey } from '../state/floatingWindow.js'
|
|
29
31
|
import { ContextMenu } from './ContextMenu.js'
|
|
30
32
|
import type { ContextMenuEntry } from './ContextMenu.js'
|
|
31
33
|
import { SvnLogStats } from './SvnLogStats.js'
|
|
@@ -886,9 +888,6 @@ export function SvnLogDialog(props) {
|
|
|
886
888
|
const [sortKey, setSortKey] = React.useState(null)
|
|
887
889
|
const [sortDir, setSortDir] = React.useState('desc')
|
|
888
890
|
const [menu, setMenu] = React.useState(null)
|
|
889
|
-
const [dlgW, setDlgW] = React.useState(() => Math.min(DIALOG_W_DEFAULT, (typeof window !== 'undefined' ? window.innerWidth : 1280) - 24)) // P0-14:外框宽(拖拽受控)
|
|
890
|
-
const [dlgH, setDlgH] = React.useState(() => Math.min(DIALOG_H_DEFAULT, (typeof window !== 'undefined' ? window.innerHeight : 800) - 24)) // P0-14:外框高(拖拽受控)
|
|
891
|
-
const [anchor, setAnchor] = React.useState(null) // 八向拖拽锚点(null=flex 居中;非 null=absolute 定位于该视口坐标) @author ddj 2026年09月24号
|
|
892
891
|
const [listH, setListH] = React.useState(LIST_H_DEFAULT) // P0-14:上段修订列表高(拖拽受控)
|
|
893
892
|
const [msgH, setMsgH] = React.useState(MSG_H_DEFAULT) // P0-14:中段提交信息高(拖拽受控)
|
|
894
893
|
const [statsOpen, setStatsOpen] = React.useState(false) // P1-10:统计窗开关
|
|
@@ -998,65 +997,6 @@ export function SvnLogDialog(props) {
|
|
|
998
997
|
window.addEventListener('pointerup', up)
|
|
999
998
|
}
|
|
1000
999
|
|
|
1001
|
-
// 八向(13-log-ui-polish 增量3):pointerdown 取卡片视口 rect 起点并立即 absolute 锚定,
|
|
1002
|
-
// 实现「拖哪边哪边动、对边固定」(flex 居中模型无法表达该语义);clamp = 尺寸下限 680/480
|
|
1003
|
-
// 与视口 12px 边距;每把手柄都以实际 rect 重锚 → CSS 截断/取整漂移自校正。
|
|
1004
|
-
const startResize = (event, mode) => {
|
|
1005
|
-
event.preventDefault()
|
|
1006
|
-
event.stopPropagation()
|
|
1007
|
-
const card = event.currentTarget.parentElement
|
|
1008
|
-
const rect = card ? card.getBoundingClientRect() : null
|
|
1009
|
-
if (!rect) return
|
|
1010
|
-
const r0 = { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
|
|
1011
|
-
const startX = event.clientX
|
|
1012
|
-
const startY = event.clientY
|
|
1013
|
-
const moveL = mode === 'w' || mode === 'nw' || mode === 'sw'
|
|
1014
|
-
const moveR = mode === 'e' || mode === 'ne' || mode === 'se'
|
|
1015
|
-
const moveT = mode === 'n' || mode === 'nw' || mode === 'ne'
|
|
1016
|
-
const moveB = mode === 's' || mode === 'sw' || mode === 'se'
|
|
1017
|
-
const EDGE = 12 // 视口边距
|
|
1018
|
-
const right0 = r0.left + r0.width
|
|
1019
|
-
const bottom0 = r0.top + r0.height
|
|
1020
|
-
// 起手锚定:切 absolute 到当前 rect(视觉零跳变)并同步 state=实际渲染尺寸
|
|
1021
|
-
setAnchor({ left: r0.left, top: r0.top })
|
|
1022
|
-
setDlgW(Math.round(r0.width))
|
|
1023
|
-
setDlgH(Math.round(r0.height))
|
|
1024
|
-
const move = (e) => {
|
|
1025
|
-
const dx = e.clientX - startX
|
|
1026
|
-
const dy = e.clientY - startY
|
|
1027
|
-
let left = r0.left
|
|
1028
|
-
let top = r0.top
|
|
1029
|
-
let width = r0.width
|
|
1030
|
-
let height = r0.height
|
|
1031
|
-
if (moveR) width = Math.min(Math.max(DIALOG_W_MIN, r0.width + dx), window.innerWidth - EDGE - r0.left)
|
|
1032
|
-
if (moveL) { left = Math.min(Math.max(EDGE, r0.left + dx), right0 - DIALOG_W_MIN); width = right0 - left }
|
|
1033
|
-
if (moveB) height = Math.min(Math.max(DIALOG_H_MIN, r0.height + dy), window.innerHeight - EDGE - r0.top)
|
|
1034
|
-
if (moveT) { top = Math.min(Math.max(EDGE, r0.top + dy), bottom0 - DIALOG_H_MIN); height = bottom0 - top }
|
|
1035
|
-
const nextLeft = left
|
|
1036
|
-
const nextTop = top
|
|
1037
|
-
setAnchor((prev) => (prev && prev.left === nextLeft && prev.top === nextTop) ? prev : { left: nextLeft, top: nextTop })
|
|
1038
|
-
setDlgW(Math.round(width))
|
|
1039
|
-
setDlgH(Math.round(height))
|
|
1040
|
-
}
|
|
1041
|
-
const up = () => {
|
|
1042
|
-
window.removeEventListener('pointermove', move)
|
|
1043
|
-
window.removeEventListener('pointerup', up)
|
|
1044
|
-
swallowNextClick()
|
|
1045
|
-
}
|
|
1046
|
-
window.addEventListener('pointermove', move)
|
|
1047
|
-
window.addEventListener('pointerup', up)
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
/**
|
|
1051
|
-
* 双击复位:清锚回 flex 居中 + 默认尺寸(视口 clamp 同初始,防小屏溢出)。
|
|
1052
|
-
* @author ddj 2026年09月24号
|
|
1053
|
-
*/
|
|
1054
|
-
const resetDlgSize = () => {
|
|
1055
|
-
setAnchor(null)
|
|
1056
|
-
setDlgW(Math.min(DIALOG_W_DEFAULT, window.innerWidth - 24))
|
|
1057
|
-
setDlgH(Math.min(DIALOG_H_DEFAULT, window.innerHeight - 24))
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
1000
|
/** 上段修订列表高度拖拽(clamp:下限 200,上限 = body 高 - 信息区/路径区/分隔条保留空间)。 */
|
|
1061
1001
|
const onListHDrag = (event) => startDrag(event, 'y', listH, (next) => {
|
|
1062
1002
|
const total = bodyRef.current?.clientHeight ?? 0
|
|
@@ -1106,9 +1046,14 @@ export function SvnLogDialog(props) {
|
|
|
1106
1046
|
|
|
1107
1047
|
// 初始/弹窗尺寸变化 fit(2026-09-24 用户反馈:固定高中段把下段变更文件区挤到 0):
|
|
1108
1048
|
// 挂载与 dlgH 变化时收敛一次;拖拽期间不介入(拖拽自带 clamp),双击复位经 fitHeights 显式收敛。
|
|
1049
|
+
const fitRef = React.useRef(null)
|
|
1050
|
+
fitRef.current = () => fitHeights(listH, msgH)
|
|
1109
1051
|
React.useEffect(() => {
|
|
1110
|
-
|
|
1111
|
-
|
|
1052
|
+
const observer = new ResizeObserver(() => fitRef.current?.())
|
|
1053
|
+
if (bodyRef.current) observer.observe(bodyRef.current)
|
|
1054
|
+
fitRef.current?.()
|
|
1055
|
+
return () => observer.disconnect()
|
|
1056
|
+
}, [])
|
|
1112
1057
|
|
|
1113
1058
|
// 勘误2(2026-09-24):挂载 fit 量到的是数据到达前布局(终态差 ~4px → 保底 166)。
|
|
1114
1059
|
// 初次数据到达以终态布局补 fit 一次收敛至 ≥170;单次门闩,之后刷新/加载更多/拖拽不介入。
|
|
@@ -1118,24 +1063,20 @@ export function SvnLogDialog(props) {
|
|
|
1118
1063
|
fitHeights(listH, msgH)
|
|
1119
1064
|
}, [list])
|
|
1120
1065
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1136
|
-
window.addEventListener('keydown', onKey, true)
|
|
1137
|
-
return () => window.removeEventListener('keydown', onKey, true)
|
|
1138
|
-
}, [sorted, onRefresh, selected])
|
|
1066
|
+
/** @private @author ddj 2026年10月14号 @param event 日志浮窗内按键。其他窗口及输入控件保留自己的键盘行为。 */
|
|
1067
|
+
function onLogKey(event) {
|
|
1068
|
+
if (rangeOpen || menu || inputOwnsKey(event.target)) return
|
|
1069
|
+
if (event.key === 'F5') { event.preventDefault(); event.stopPropagation(); onRefresh(); return }
|
|
1070
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
|
|
1071
|
+
if (!sorted || !sorted.length) return
|
|
1072
|
+
event.preventDefault()
|
|
1073
|
+
event.stopPropagation()
|
|
1074
|
+
const at = sorted.findIndex((item) => item.revision === selected[0]?.revision)
|
|
1075
|
+
const next = at < 0 ? 0 : Math.min(sorted.length - 1, Math.max(0, at + (event.key === 'ArrowDown' ? 1 : -1)))
|
|
1076
|
+
const hit = sorted[next]
|
|
1077
|
+
setSelected([hit])
|
|
1078
|
+
setAnchorRev(hit.revision)
|
|
1079
|
+
}
|
|
1139
1080
|
|
|
1140
1081
|
/** 复制当前选中修订(P0-9;剪贴板不可用静默忽略)。 */
|
|
1141
1082
|
const copyRevision = (entry) => {
|
|
@@ -1224,16 +1165,10 @@ export function SvnLogDialog(props) {
|
|
|
1224
1165
|
React.createElement('div', { className: 'edrv-svnlog-pane edrv-svnlog-pathspane', ref: paneRef },
|
|
1225
1166
|
React.createElement(LogPathsInner, { selected: visibleSelected, target, onlyAffected, onOpenFile, onOpenDiff, onPathMenu })))
|
|
1226
1167
|
|
|
1227
|
-
return React.createElement(
|
|
1228
|
-
dialogClass: 'edrv-svnlog-dialog',
|
|
1229
|
-
width:
|
|
1230
|
-
|
|
1231
|
-
// border-box 令 rect==state(clamp/拖拽零换算);maxHeight:none 消除 88vh 造成 state≠视觉的漂移。
|
|
1232
|
-
cardStyle: Object.assign(
|
|
1233
|
-
{ height: Math.round(dlgH) + 'px', boxSizing: 'border-box', maxHeight: 'none' },
|
|
1234
|
-
anchor ? { position: 'absolute', left: Math.round(anchor.left) + 'px', top: Math.round(anchor.top) + 'px' } : null,
|
|
1235
|
-
),
|
|
1236
|
-
onClose,
|
|
1168
|
+
return React.createElement(FloatingWindow, {
|
|
1169
|
+
title: 'SVN 日志', dialogClass: 'edrv-svnlog-dialog',
|
|
1170
|
+
width: DIALOG_W_DEFAULT, height: DIALOG_H_DEFAULT,
|
|
1171
|
+
onKeyDown: onLogKey, onClose,
|
|
1237
1172
|
},
|
|
1238
1173
|
React.createElement(LogHead, { target, onClose }),
|
|
1239
1174
|
React.createElement(LogFilterBar, {
|
|
@@ -1249,15 +1184,6 @@ export function SvnLogDialog(props) {
|
|
|
1249
1184
|
onExportCsv: () => onExport('csv'), onExportHtml: () => onExport('html'),
|
|
1250
1185
|
rangeActive: rangeActive === true, onOpenRange: () => setRangeOpen(true), onRangeReset: () => onRangeReset?.(),
|
|
1251
1186
|
}),
|
|
1252
|
-
// 八向手柄(增量3):四边先渲染、四角后渲染(角盖过相邻边 2px overlap);双击复位统一走 resetDlgSize
|
|
1253
|
-
React.createElement(Grip, { mode: 'n', onDragStart: startResize, onReset: resetDlgSize }),
|
|
1254
|
-
React.createElement(Grip, { mode: 's', onDragStart: startResize, onReset: resetDlgSize }),
|
|
1255
|
-
React.createElement(Grip, { mode: 'w', onDragStart: startResize, onReset: resetDlgSize }),
|
|
1256
|
-
React.createElement(Grip, { mode: 'e', onDragStart: startResize, onReset: resetDlgSize }),
|
|
1257
|
-
React.createElement(Grip, { mode: 'nw', onDragStart: startResize, onReset: resetDlgSize }),
|
|
1258
|
-
React.createElement(Grip, { mode: 'ne', onDragStart: startResize, onReset: resetDlgSize }),
|
|
1259
|
-
React.createElement(Grip, { mode: 'sw', onDragStart: startResize, onReset: resetDlgSize }),
|
|
1260
|
-
React.createElement(Grip, { mode: 'se', onDragStart: startResize, onReset: resetDlgSize }),
|
|
1261
1187
|
(menu ? React.createElement(ContextMenu, { x: menu.x, y: menu.y, entries: menu.entries, onClose: () => setMenu(null) }) : null),
|
|
1262
1188
|
(statsOpen ? React.createElement(SvnLogStats, { key: 'edrv-svnlog-stats', entries: sorted, onClose: () => setStatsOpen(false) }) : null),
|
|
1263
1189
|
(rangeOpen ? React.createElement(ModalShell, {
|