dsh-vscode-mode 0.10.0 → 0.11.0
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 +33 -3
- package/lib/client.js +1501 -269
- package/lib/client.js.map +1 -1
- package/lib/index.js +2163 -1042
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/dap/hover.ts +2 -7
- package/src/client/index.ts +25 -0
- package/src/client/luaReservedWords.ts +10 -0
- package/src/client/monaco/lsp/keywords.ts +121 -0
- package/src/client/monaco/lsp/lspClient.ts +68 -6
- package/src/client/monaco/lsp/memberScope.ts +90 -0
- package/src/client/monaco/lsp/providers.ts +14 -4
- package/src/client/settingsDocRouter.ts +52 -0
- package/src/client/sidebar/menuItems.ts +1 -0
- package/src/client/sidebar/types.ts +2 -0
- package/src/client/styles/editor.css +143 -37
- package/src/client/svnActions.ts +4 -4
- package/src/client/ui/ContextMenu.ts +4 -2
- package/src/client/ui/EditorView.ts +132 -52
- package/src/client/ui/SvnLogDialog.ts +200 -69
- package/src/client/ui/SvnUpdateDialog.ts +472 -0
- package/src/client/ui/svnUpdateModel.ts +135 -0
- package/src/index.ts +2 -0
- package/src/lsp/completionCap.ts +80 -0
- package/src/lsp/manager.ts +42 -10
- package/src/lsp/rpc.ts +8 -5
- package/src/lsp/server.ts +18 -4
- package/src/rpc.ts +74 -2
- package/src/settingsDocPath.ts +30 -0
- package/src/shared/rpc.ts +12 -2
- package/src/shared/svn.ts +38 -0
- package/src/svn.ts +102 -7
- package/src/svnText.ts +22 -13
- package/src/svnUpdateJob.ts +588 -0
|
@@ -48,8 +48,10 @@ const MSG_H_DEFAULT = 140
|
|
|
48
48
|
const MSG_H_MIN = 60
|
|
49
49
|
/** 分段拖拽时下段保留的最小空间。 */
|
|
50
50
|
const PANE_KEEP_MIN = 170
|
|
51
|
-
/**
|
|
52
|
-
const
|
|
51
|
+
/** 保底计算需额外扣除的两根横向分隔条合计高度(2 × 6px;拖拽 clamp 与初始 fit 共用)。 @author ddj 2026年09月24号 */
|
|
52
|
+
const HSPLIT_TOTAL_PX = 12
|
|
53
|
+
/** 日志动作字母的配色分级(新增=绿、删除=红、替换=琥珀、修改=蓝;13-log-ui-polish 语义归位)。 */
|
|
54
|
+
const ACTION_TONE = { A: 'added', D: 'deleted', R: 'replaced', M: 'modified' }
|
|
53
55
|
/** 排序键 → 列头名(P0-12;顺序 = 列头展示顺序)。 */
|
|
54
56
|
const SORT_COLUMNS = [
|
|
55
57
|
{ key: 'revision', label: '版本' },
|
|
@@ -427,7 +429,7 @@ function LogItem(props) {
|
|
|
427
429
|
+ (merged ? ' edrv-svnlog-item-merged' : '')
|
|
428
430
|
+ (wc ? ' edrv-svnlog-item-wc' : ''),
|
|
429
431
|
title,
|
|
430
|
-
style: merged ? { paddingLeft:
|
|
432
|
+
style: merged ? { paddingLeft: 16 + level * 18 } : undefined,
|
|
431
433
|
onClick: (event) => onPick(event, item),
|
|
432
434
|
onContextMenu: (event) => onMenu?.(event, item),
|
|
433
435
|
},
|
|
@@ -460,24 +462,33 @@ function Splitter(props) {
|
|
|
460
462
|
}
|
|
461
463
|
|
|
462
464
|
/**
|
|
463
|
-
*
|
|
464
|
-
* 拖拽常把指针移出卡片,松手后 click
|
|
465
|
-
*
|
|
465
|
+
* 吞掉拖拽松手后的合成 click(capture;仅遮罩目标 + 300ms 窗口)。
|
|
466
|
+
* 拖拽常把指针移出卡片,松手后 click 落在遮罩上会误触「点遮罩关闭」——原实现 setTimeout(0)
|
|
467
|
+
* 太短,CDP/合成输入的 click 跨任务到达时窗口已释放(13-log-ui-polish 勘误3 实测复现)。
|
|
468
|
+
* 现改为:只拦 target=遮罩 的 click(卡片内点击放行,不误吞行选中),300ms 后正常。
|
|
469
|
+
* @author ddj 2026年09月17号 / 2026年09月24号
|
|
466
470
|
*/
|
|
467
471
|
function swallowNextClick() {
|
|
468
472
|
const swallow = (event) => {
|
|
469
|
-
event.
|
|
470
|
-
|
|
473
|
+
const onMask = event.target && event.target.classList && event.target.classList.contains('edrv-modal-mask')
|
|
474
|
+
if (onMask) {
|
|
475
|
+
event.stopPropagation()
|
|
476
|
+
event.preventDefault()
|
|
477
|
+
}
|
|
471
478
|
window.removeEventListener('click', swallow, true)
|
|
472
479
|
}
|
|
473
480
|
window.addEventListener('click', swallow, true)
|
|
474
|
-
window.setTimeout(() => window.removeEventListener('click', swallow, true),
|
|
481
|
+
window.setTimeout(() => window.removeEventListener('click', swallow, true), 300)
|
|
475
482
|
}
|
|
476
483
|
|
|
484
|
+
/** 八向手柄 → 提示文案(title 供无障碍与实机寻址;13-log-ui-polish 增量3)。 */
|
|
485
|
+
const GRIP_LABEL = { n: '上缘', s: '下缘', w: '左缘', e: '右缘', nw: '左上角', ne: '右上角', sw: '左下角', se: '右下角' }
|
|
486
|
+
|
|
477
487
|
/**
|
|
478
|
-
* 外框尺寸手柄(
|
|
479
|
-
*
|
|
480
|
-
* @
|
|
488
|
+
* 外框尺寸手柄(13-log-ui-polish 增量3 八向):mode ∈ n/s/w/e/nw/ne/sw/se(四边+四角);双击复位。
|
|
489
|
+
* 拖拽语义:拖哪边哪边动、对边固定(由弹窗侧 startResize 以 rect 起点 + absolute 锚定实现)。
|
|
490
|
+
* @author ddj 2026年09月17号 / 2026年09月24号
|
|
491
|
+
* @param props.mode 手柄形态(八向标准向名)
|
|
481
492
|
* @param props.onDragStart pointerdown 回调(弹窗侧启动 window 级跟踪)
|
|
482
493
|
* @param props.onReset 双击复位
|
|
483
494
|
* @returns 手柄元素
|
|
@@ -486,11 +497,25 @@ function Grip(props) {
|
|
|
486
497
|
const { mode, onDragStart, onReset } = props
|
|
487
498
|
return React.createElement('div', {
|
|
488
499
|
className: 'edrv-svnlog-grip edrv-svnlog-grip-' + mode,
|
|
500
|
+
title: '调整弹窗尺寸(' + (GRIP_LABEL[mode] ?? mode) + ')· 双击复位',
|
|
489
501
|
onPointerDown: (event) => onDragStart(event, mode),
|
|
490
502
|
onDoubleClick: (event) => { event.stopPropagation(); onReset() },
|
|
491
503
|
})
|
|
492
504
|
}
|
|
493
505
|
|
|
506
|
+
/**
|
|
507
|
+
* 路径两段拆分(目录段弱化 + 文件名加重;无斜杠时整段为文件名)。
|
|
508
|
+
* @author ddj 2026年09月24号
|
|
509
|
+
* @param text 显示用路径文本
|
|
510
|
+
* @returns { dir, base } 目录前缀(含尾斜杠,可为空串)与文件名
|
|
511
|
+
*/
|
|
512
|
+
function pathSegsOf(text) {
|
|
513
|
+
const raw = String(text ?? '')
|
|
514
|
+
const at = raw.lastIndexOf('/')
|
|
515
|
+
if (at < 0) return { dir: '', base: raw }
|
|
516
|
+
return { dir: raw.slice(0, at + 1), base: raw.slice(at + 1) }
|
|
517
|
+
}
|
|
518
|
+
|
|
494
519
|
/**
|
|
495
520
|
* 下窗格变更路径单行(双击看差异;目录打开;Copy from 成列 P0-7;非受影响灰显 P0-5)。
|
|
496
521
|
* relPath 三态:null = 仓库外(显示仓库原路径);'' = 工作副本根自身变更(如合并属性,
|
|
@@ -512,6 +537,7 @@ function PathRow(props) {
|
|
|
512
537
|
const open = () => { if (actionable) onOpenFile?.(item.relPath) }
|
|
513
538
|
const diff = () => { if (actionable && item.kind !== 'dir') onOpenDiff?.(item.relPath, revision) }
|
|
514
539
|
const relText = outside ? item.path + '(仓库外)' : (isRoot ? '(工作副本根)' : item.relPath)
|
|
540
|
+
const segs = pathSegsOf(relText)
|
|
515
541
|
const buttons = actionable ? React.createElement('span', { className: 'edrv-svnlog-acts' },
|
|
516
542
|
React.createElement('button', {
|
|
517
543
|
className: 'edrv-svn-act', title: '在工作区打开该文件',
|
|
@@ -533,7 +559,9 @@ function PathRow(props) {
|
|
|
533
559
|
onContextMenu: (event) => onMenu?.(event, item, revision),
|
|
534
560
|
},
|
|
535
561
|
actionLetterEl(item.action),
|
|
536
|
-
React.createElement('span', { className: 'edrv-svnlog-pathtext' },
|
|
562
|
+
React.createElement('span', { className: 'edrv-svnlog-pathtext' },
|
|
563
|
+
segs.dir ? React.createElement('span', { className: 'edrv-svnlog-pdir' }, segs.dir) : null,
|
|
564
|
+
React.createElement('span', { className: 'edrv-svnlog-pbase' }, segs.base)),
|
|
537
565
|
(item.kind === 'dir' ? React.createElement('span', { className: 'edrv-svnlog-kind' }, '目录') : null),
|
|
538
566
|
(item.copyFrom ? React.createElement('span', { className: 'edrv-svnlog-kind' }, '复制') : null),
|
|
539
567
|
React.createElement('span', { className: 'edrv-svnlog-copyfrom', title: item.copyFrom ? item.copyFrom + '@' + (item.copyFromRev ?? '?') : '' },
|
|
@@ -708,12 +736,13 @@ function LogFilterBar(props) {
|
|
|
708
736
|
React.createElement('label', { className: 'edrv-svnlog-check', title: '把每个词元当正则表达式(语法错误回落子串匹配)' },
|
|
709
737
|
React.createElement('input', { type: 'checkbox', checked: regexOn, onChange: (event) => onRegex(event.target.checked) }),
|
|
710
738
|
'正则'),
|
|
711
|
-
React.createElement('
|
|
712
|
-
'
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
'
|
|
716
|
-
|
|
739
|
+
React.createElement('span', { className: 'edrv-svnlog-dates' },
|
|
740
|
+
React.createElement('label', { className: 'edrv-svnlog-check' },
|
|
741
|
+
'从',
|
|
742
|
+
React.createElement('input', { type: 'date', className: 'edrv-svnlog-date', value: from, onChange: (event) => onFrom(event.target.value) })),
|
|
743
|
+
React.createElement('label', { className: 'edrv-svnlog-check' },
|
|
744
|
+
'到',
|
|
745
|
+
React.createElement('input', { type: 'date', className: 'edrv-svnlog-date', value: to, onChange: (event) => onTo(event.target.value) }))))
|
|
717
746
|
}
|
|
718
747
|
|
|
719
748
|
/**
|
|
@@ -776,41 +805,44 @@ function LogFoot(props) {
|
|
|
776
805
|
React.createElement('span', { style: { flex: 1 } }),
|
|
777
806
|
status),
|
|
778
807
|
React.createElement('div', { className: 'edrv-svnlog-btns' },
|
|
779
|
-
React.createElement('
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
+
React.createElement('span', { className: 'edrv-svnlog-btngroup' },
|
|
809
|
+
React.createElement('button', {
|
|
810
|
+
className: 'edrv-svn-act', disabled: busy || !truncated || rangeActive,
|
|
811
|
+
title: rangeActive ? '区间模式下不可加载更多(回到最新后可用)' : (truncated ? '再取 100 条' : '已到最早提交:该目标的全部历史已显示(svn 日志只含该路径发生过变更的版本)'),
|
|
812
|
+
onClick: onLoadMore,
|
|
813
|
+
}, 'Next 100'),
|
|
814
|
+
React.createElement('button', {
|
|
815
|
+
className: 'edrv-svn-act', disabled: busy || rangeActive,
|
|
816
|
+
title: rangeActive
|
|
817
|
+
? '区间模式下不可用(回到最新后可用)'
|
|
818
|
+
: '取到本插件上限 ' + SVN_LOG_SHOW_ALL_LIMIT + ' 条(超大仓库可能较慢;再往前的历史可用 Show Range… 指定区间)',
|
|
819
|
+
onClick: onShowAll,
|
|
820
|
+
}, 'Show All'),
|
|
821
|
+
React.createElement('button', {
|
|
822
|
+
className: 'edrv-svn-act', disabled: busy, title: '按版本区间拉取(P1-6;起始=较早、结束=较晚)',
|
|
823
|
+
onClick: onOpenRange,
|
|
824
|
+
}, 'Show Range…'),
|
|
825
|
+
(rangeActive
|
|
826
|
+
? React.createElement('button', { className: 'edrv-svn-act', disabled: busy, title: '清除区间,回到默认 HEAD 往前的窗口', onClick: onRangeReset }, '回到最新')
|
|
827
|
+
: null)),
|
|
828
|
+
React.createElement('span', { className: 'edrv-svnlog-btngroup' },
|
|
829
|
+
React.createElement('button', {
|
|
830
|
+
className: 'edrv-svn-act', disabled: busy || !filtered || !filtered.length,
|
|
831
|
+
title: '导出当前过滤后的修订列表为 CSV(带 BOM,Excel 直开;修订/作者/日期/信息/路径)',
|
|
832
|
+
onClick: onExportCsv,
|
|
833
|
+
}, '导出CSV'),
|
|
834
|
+
React.createElement('button', {
|
|
835
|
+
className: 'edrv-svn-act', disabled: busy || !filtered || !filtered.length,
|
|
836
|
+
title: '导出当前过滤后的修订列表为 HTML 报表(仅本地查看,不外发)',
|
|
837
|
+
onClick: onExportHtml,
|
|
838
|
+
}, '导出HTML')),
|
|
808
839
|
React.createElement('span', { style: { flex: 1 } }),
|
|
809
|
-
React.createElement('
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
840
|
+
React.createElement('span', { className: 'edrv-svnlog-btngroup' },
|
|
841
|
+
React.createElement('button', { className: 'edrv-svn-act', disabled: busy, title: '重新读取日志(F5)', onClick: onRefresh }, 'Refresh'),
|
|
842
|
+
React.createElement('button', {
|
|
843
|
+
className: 'edrv-svn-act', disabled: !list || !list.length,
|
|
844
|
+
title: '统计当前显示的修订(期间 / 按作者 / 按日期,P1-10)', onClick: onStats,
|
|
845
|
+
}, 'Statistics'))))
|
|
814
846
|
}
|
|
815
847
|
|
|
816
848
|
/**
|
|
@@ -856,6 +888,7 @@ export function SvnLogDialog(props) {
|
|
|
856
888
|
const [menu, setMenu] = React.useState(null)
|
|
857
889
|
const [dlgW, setDlgW] = React.useState(() => Math.min(DIALOG_W_DEFAULT, (typeof window !== 'undefined' ? window.innerWidth : 1280) - 24)) // P0-14:外框宽(拖拽受控)
|
|
858
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号
|
|
859
892
|
const [listH, setListH] = React.useState(LIST_H_DEFAULT) // P0-14:上段修订列表高(拖拽受控)
|
|
860
893
|
const [msgH, setMsgH] = React.useState(MSG_H_DEFAULT) // P0-14:中段提交信息高(拖拽受控)
|
|
861
894
|
const [statsOpen, setStatsOpen] = React.useState(false) // P1-10:统计窗开关
|
|
@@ -863,6 +896,8 @@ export function SvnLogDialog(props) {
|
|
|
863
896
|
const [rangeFrom, setRangeFrom] = React.useState('') // P1-6:起始修订(较早)
|
|
864
897
|
const [rangeTo, setRangeTo] = React.useState('') // P1-6:结束修订(较晚)
|
|
865
898
|
const bodyRef = React.useRef(null)
|
|
899
|
+
const paneRef = React.useRef(null) // 下段 pathspane 实高测量点(fitHeights 导出非本段占用;13-log-ui-polish 勘误) @author ddj 2026年09月24号
|
|
900
|
+
const fitOnDataRef = React.useRef(false) // 初次数据到达只补 fit 一次(挂载时 chrome 量到的是数据前布局,终态差 ~4px) @author ddj 2026年09月24号
|
|
866
901
|
|
|
867
902
|
const list = Array.isArray(entries) ? entries : null
|
|
868
903
|
// 选中项:默认首条;数据刷新/过滤后仅保留仍存在的选中(G10:按 revision 维护),全部消失回落首条;
|
|
@@ -963,17 +998,45 @@ export function SvnLogDialog(props) {
|
|
|
963
998
|
window.addEventListener('pointerup', up)
|
|
964
999
|
}
|
|
965
1000
|
|
|
966
|
-
//
|
|
1001
|
+
// 八向(13-log-ui-polish 增量3):pointerdown 取卡片视口 rect 起点并立即 absolute 锚定,
|
|
1002
|
+
// 实现「拖哪边哪边动、对边固定」(flex 居中模型无法表达该语义);clamp = 尺寸下限 680/480
|
|
1003
|
+
// 与视口 12px 边距;每把手柄都以实际 rect 重锚 → CSS 截断/取整漂移自校正。
|
|
967
1004
|
const startResize = (event, mode) => {
|
|
968
1005
|
event.preventDefault()
|
|
969
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 }
|
|
970
1011
|
const startX = event.clientX
|
|
971
1012
|
const startY = event.clientY
|
|
972
|
-
const
|
|
973
|
-
const
|
|
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))
|
|
974
1024
|
const move = (e) => {
|
|
975
|
-
|
|
976
|
-
|
|
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))
|
|
977
1040
|
}
|
|
978
1041
|
const up = () => {
|
|
979
1042
|
window.removeEventListener('pointermove', move)
|
|
@@ -984,20 +1047,77 @@ export function SvnLogDialog(props) {
|
|
|
984
1047
|
window.addEventListener('pointerup', up)
|
|
985
1048
|
}
|
|
986
1049
|
|
|
987
|
-
/**
|
|
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
|
+
/** 上段修订列表高度拖拽(clamp:下限 200,上限 = body 高 - 信息区/路径区/分隔条保留空间)。 */
|
|
988
1061
|
const onListHDrag = (event) => startDrag(event, 'y', listH, (next) => {
|
|
989
1062
|
const total = bodyRef.current?.clientHeight ?? 0
|
|
990
|
-
const max = Math.max(LIST_H_MIN + 80, total - msgH - PANE_KEEP_MIN)
|
|
1063
|
+
const max = Math.max(LIST_H_MIN + 80, total - msgH - PANE_KEEP_MIN - HSPLIT_TOTAL_PX)
|
|
991
1064
|
setListH(Math.min(max, Math.max(LIST_H_MIN, next)))
|
|
992
1065
|
})
|
|
993
1066
|
|
|
994
|
-
/** 中段提交信息高度拖拽(clamp:下限 60,上限 = body 高 -
|
|
1067
|
+
/** 中段提交信息高度拖拽(clamp:下限 60,上限 = body 高 - 列表区/路径区/分隔条保留空间)。 */
|
|
995
1068
|
const onMsgHDrag = (event) => startDrag(event, 'y', msgH, (next) => {
|
|
996
1069
|
const total = bodyRef.current?.clientHeight ?? 0
|
|
997
|
-
const max = Math.max(MSG_H_MIN + 80, total - listH - PANE_KEEP_MIN)
|
|
1070
|
+
const max = Math.max(MSG_H_MIN + 80, total - listH - PANE_KEEP_MIN - HSPLIT_TOTAL_PX)
|
|
998
1071
|
setMsgH(Math.min(max, Math.max(MSG_H_MIN, next)))
|
|
999
1072
|
})
|
|
1000
1073
|
|
|
1074
|
+
/**
|
|
1075
|
+
* 上/中段高度收敛进 body(初始挂载、dlgH 变化、双击复位共用;shrink-only 只缩不涨)。
|
|
1076
|
+
* 非本段占用 chrome 由 DOM 实测导出(= bodyH - 上中段 state - 下段实占;含 gap4×6 + 分隔条
|
|
1077
|
+
* 2×10 + 上段 padding/border6 + 中段 padding/border18 = 68px),免手算且 CSS 微调自动跟随;
|
|
1078
|
+
* 保下段 ≥ PANE_KEEP_MIN。极端小视口(预算 < 两段地板和)给各自下限不倒挂;
|
|
1079
|
+
* 地板救济:抬 list 至地板后超出从 msg 扣回(预算 ≥ 地板和保证不破对方地板)。
|
|
1080
|
+
* 只缩不涨 + 地板兜底,与拖拽 clamp 同向,天然不会与拖拽互相拉扯成循环。
|
|
1081
|
+
* @author ddj 2026年09月24号
|
|
1082
|
+
* @param wantList 目标上段(修订列表)高度
|
|
1083
|
+
* @param wantMsg 目标中段(提交信息)高度
|
|
1084
|
+
*/
|
|
1085
|
+
const fitHeights = (wantList, wantMsg) => {
|
|
1086
|
+
const total = bodyRef.current?.clientHeight ?? 0
|
|
1087
|
+
if (!total) { setListH(wantList); setMsgH(wantMsg); return }
|
|
1088
|
+
const paneOcc = paneRef.current?.offsetHeight ?? -1
|
|
1089
|
+
// 实测导出非本段占用(勘误 2026-09-24:手算公式漏 gap/margin/padding 曾致保底仅114);测不到回落分隔条
|
|
1090
|
+
const chrome = paneOcc >= 0 ? Math.max(0, total - listH - msgH - paneOcc) : HSPLIT_TOTAL_PX
|
|
1091
|
+
const budget = total - PANE_KEEP_MIN - chrome
|
|
1092
|
+
if (budget < LIST_H_MIN + MSG_H_MIN) {
|
|
1093
|
+
setListH(Math.min(wantList, LIST_H_MIN))
|
|
1094
|
+
setMsgH(Math.min(wantMsg, MSG_H_MIN))
|
|
1095
|
+
return
|
|
1096
|
+
}
|
|
1097
|
+
if (wantList + wantMsg <= budget) { setListH(wantList); setMsgH(wantMsg); return }
|
|
1098
|
+
const scale = budget / (wantList + wantMsg)
|
|
1099
|
+
let nextList = Math.floor(wantList * scale)
|
|
1100
|
+
let nextMsg = Math.floor(wantMsg * scale)
|
|
1101
|
+
if (nextList < LIST_H_MIN) { nextList = LIST_H_MIN; nextMsg = Math.min(nextMsg, budget - nextList) }
|
|
1102
|
+
else if (nextMsg < MSG_H_MIN) { nextMsg = MSG_H_MIN; nextList = Math.min(nextList, budget - nextMsg) }
|
|
1103
|
+
setListH(nextList)
|
|
1104
|
+
setMsgH(nextMsg)
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// 初始/弹窗尺寸变化 fit(2026-09-24 用户反馈:固定高中段把下段变更文件区挤到 0):
|
|
1108
|
+
// 挂载与 dlgH 变化时收敛一次;拖拽期间不介入(拖拽自带 clamp),双击复位经 fitHeights 显式收敛。
|
|
1109
|
+
React.useEffect(() => {
|
|
1110
|
+
fitHeights(listH, msgH)
|
|
1111
|
+
}, [dlgH])
|
|
1112
|
+
|
|
1113
|
+
// 勘误2(2026-09-24):挂载 fit 量到的是数据到达前布局(终态差 ~4px → 保底 166)。
|
|
1114
|
+
// 初次数据到达以终态布局补 fit 一次收敛至 ≥170;单次门闩,之后刷新/加载更多/拖拽不介入。
|
|
1115
|
+
React.useEffect(() => {
|
|
1116
|
+
if (!list || fitOnDataRef.current) return
|
|
1117
|
+
fitOnDataRef.current = true
|
|
1118
|
+
fitHeights(listH, msgH)
|
|
1119
|
+
}, [list])
|
|
1120
|
+
|
|
1001
1121
|
// 键盘:F5 刷新(防浏览器整页刷新);↑↓ 在显示列表内移动选中(输入框内不劫持)
|
|
1002
1122
|
React.useEffect(() => {
|
|
1003
1123
|
const onKey = (event) => {
|
|
@@ -1094,20 +1214,25 @@ export function SvnLogDialog(props) {
|
|
|
1094
1214
|
height: listH, wcRev,
|
|
1095
1215
|
}),
|
|
1096
1216
|
React.createElement(Splitter, {
|
|
1097
|
-
title: '调整修订列表高度', onDragStart: onListHDrag, onReset: () =>
|
|
1217
|
+
title: '调整修订列表高度', onDragStart: onListHDrag, onReset: () => fitHeights(LIST_H_DEFAULT, msgH),
|
|
1098
1218
|
}),
|
|
1099
1219
|
React.createElement('div', { className: 'edrv-svnlog-pane edrv-svnlog-msgpane', style: { height: msgH } },
|
|
1100
1220
|
React.createElement(LogMsgInner, { selected: visibleSelected })),
|
|
1101
1221
|
React.createElement(Splitter, {
|
|
1102
|
-
title: '调整提交信息区高度', onDragStart: onMsgHDrag, onReset: () =>
|
|
1222
|
+
title: '调整提交信息区高度', onDragStart: onMsgHDrag, onReset: () => fitHeights(listH, MSG_H_DEFAULT),
|
|
1103
1223
|
}),
|
|
1104
|
-
React.createElement('div', { className: 'edrv-svnlog-pane edrv-svnlog-pathspane' },
|
|
1224
|
+
React.createElement('div', { className: 'edrv-svnlog-pane edrv-svnlog-pathspane', ref: paneRef },
|
|
1105
1225
|
React.createElement(LogPathsInner, { selected: visibleSelected, target, onlyAffected, onOpenFile, onOpenDiff, onPathMenu })))
|
|
1106
1226
|
|
|
1107
1227
|
return React.createElement(ModalShell, {
|
|
1108
1228
|
dialogClass: 'edrv-svnlog-dialog',
|
|
1109
1229
|
width: Math.round(dlgW) + 'px',
|
|
1110
|
-
|
|
1230
|
+
// 八向锚定(增量3):anchor 非空时 absolute 定位于视口坐标(脱离 flex 居中,视觉零跳变);
|
|
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
|
+
),
|
|
1111
1236
|
onClose,
|
|
1112
1237
|
},
|
|
1113
1238
|
React.createElement(LogHead, { target, onClose }),
|
|
@@ -1124,9 +1249,15 @@ export function SvnLogDialog(props) {
|
|
|
1124
1249
|
onExportCsv: () => onExport('csv'), onExportHtml: () => onExport('html'),
|
|
1125
1250
|
rangeActive: rangeActive === true, onOpenRange: () => setRangeOpen(true), onRangeReset: () => onRangeReset?.(),
|
|
1126
1251
|
}),
|
|
1127
|
-
|
|
1128
|
-
React.createElement(Grip, { mode: '
|
|
1129
|
-
React.createElement(Grip, { mode: '
|
|
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 }),
|
|
1130
1261
|
(menu ? React.createElement(ContextMenu, { x: menu.x, y: menu.y, entries: menu.entries, onClose: () => setMenu(null) }) : null),
|
|
1131
1262
|
(statsOpen ? React.createElement(SvnLogStats, { key: 'edrv-svnlog-stats', entries: sorted, onClose: () => setStatsOpen(false) }) : null),
|
|
1132
1263
|
(rangeOpen ? React.createElement(ModalShell, {
|