react-terminal-viewer-cicd 3.0.0-beta.43 → 3.0.0-beta.45

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.
Files changed (26) hide show
  1. package/dist/esm/Addon/WorkerLog/LogWorker.js +1 -1
  2. package/dist/esm/PipelineLogViewer/PipelineLogViewer.d.ts.map +1 -1
  3. package/dist/esm/PipelineLogViewer/PipelineLogViewer.js +156 -144
  4. package/dist/esm/PipelineLogViewer/components/LogRow.d.ts +2 -0
  5. package/dist/esm/PipelineLogViewer/components/LogRow.d.ts.map +1 -1
  6. package/dist/esm/PipelineLogViewer/components/LogRow.js +3 -1
  7. package/dist/esm/PipelineLogViewer/constants.d.ts +10 -0
  8. package/dist/esm/PipelineLogViewer/constants.d.ts.map +1 -1
  9. package/dist/esm/PipelineLogViewer/constants.js +12 -0
  10. package/dist/esm/PipelineLogViewer/hooks/useStickyHeader.d.ts.map +1 -1
  11. package/dist/esm/PipelineLogViewer/hooks/useStickyHeader.js +21 -1
  12. package/dist/esm/PipelineLogViewer/index.less +2 -2
  13. package/dist/esm/PipelineLogViewer/types.d.ts +4 -0
  14. package/dist/esm/PipelineLogViewer/types.d.ts.map +1 -1
  15. package/dist/esm/PipelineLogViewer/utils/groupLineNumber.d.ts +9 -7
  16. package/dist/esm/PipelineLogViewer/utils/groupLineNumber.d.ts.map +1 -1
  17. package/dist/esm/PipelineLogViewer/utils/groupLineNumber.js +14 -26
  18. package/dist/esm/PipelineLogViewer/utils/virtuosoBoundaryScroll.d.ts +8 -1
  19. package/dist/esm/PipelineLogViewer/utils/virtuosoBoundaryScroll.d.ts.map +1 -1
  20. package/dist/esm/PipelineLogViewer/utils/virtuosoBoundaryScroll.js +48 -11
  21. package/dist/worker/src/PipelineLogViewer/components/LogRow.d.ts +2 -0
  22. package/dist/worker/src/PipelineLogViewer/constants.d.ts +10 -0
  23. package/dist/worker/src/PipelineLogViewer/types.d.ts +4 -0
  24. package/dist/worker/src/PipelineLogViewer/utils/groupLineNumber.d.ts +9 -7
  25. package/dist/worker/src/PipelineLogViewer/utils/virtuosoBoundaryScroll.d.ts +8 -1
  26. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  function LogWorker() {
2
2
  var workerPath = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '/worker';
3
3
  var worker = null;
4
- var version = "3.0.0-beta.43" || '0.0.0';
4
+ var version = "3.0.0-beta.45" || '0.0.0';
5
5
  var path = workerPath.includes('http') ? "".concat(workerPath, "/log.worker.js") : "".concat(window.location.origin).concat(workerPath, "/log.worker.js");
6
6
  var blob = new Blob(["importScripts(\"".concat(path, "?v=").concat(version, "\")")], {
7
7
  type: 'application/javascript'
@@ -1 +1 @@
1
- {"version":3,"file":"PipelineLogViewer.d.ts","sourceRoot":"","sources":["../../../src/PipelineLogViewer/PipelineLogViewer.tsx"],"names":[],"mappings":"AAoBA,OAAO,KASN,MAAM,OAAO,CAAC;AAef,OAAO,cAAc,CAAC;AACtB,OAAO,KAAK,EAGV,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAwEjB,QAAA,MAAM,iBAAiB,qGAoxCtB,CAAC;AAIF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"PipelineLogViewer.d.ts","sourceRoot":"","sources":["../../../src/PipelineLogViewer/PipelineLogViewer.tsx"],"names":[],"mappings":"AAoBA,OAAO,KASN,MAAM,OAAO,CAAC;AAiBf,OAAO,cAAc,CAAC;AACtB,OAAO,KAAK,EAGV,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAwEjB,QAAA,MAAM,iBAAiB,qGAkzCtB,CAAC;AAIF,eAAe,iBAAiB,CAAC"}
@@ -36,14 +36,14 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
36
36
  import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
37
37
  import { Virtuoso } from 'react-virtuoso';
38
38
  import { GroupHeader, LogRow, SearchBar, StickyHeader } from "./components";
39
- import { BOUNDARY_PREFETCH_INDEX_MARGIN, DEFAULT_MAX_MEMORY_LINES, DEFAULT_PAGE_SIZE, DEFAULT_VIRTUOSO_ITEM_HEIGHT } from "./constants";
39
+ import { BOUNDARY_PREFETCH_INDEX_MARGIN, DEFAULT_MAX_MEMORY_LINES, DEFAULT_PAGE_SIZE, DEFAULT_VIRTUOSO_ITEM_HEIGHT, IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL, IGNORE_BOUNDARY_FETCH_MS_FIRST_SCREEN } from "./constants";
40
40
  import useDisplayLogs from "./hooks/useDisplayLogs";
41
41
  import useGroupManager from "./hooks/useGroupManager";
42
42
  import useLogFetcher from "./hooks/useLogFetcher";
43
43
  import useLogSearch from "./hooks/useLogSearch";
44
44
  import useStickyHeader from "./hooks/useStickyHeader";
45
45
  import "./index.less";
46
- import { getGlobalVisibleLineNumberLabel } from "./utils/groupLineNumber";
46
+ import { getPipelineGroupedLineNumberLabel } from "./utils/groupLineNumber";
47
47
  import normalizeGroupsForPipelineStatus from "./utils/normalizeGroupsForPipelineStatus";
48
48
  import { applyBoundaryScrollCompensation } from "./utils/virtuosoBoundaryScroll";
49
49
  import { firstVisibleIndexFromRenderedItems, lastVisibleIndexFromRenderedItems } from "./utils/virtuosoVisibleItems";
@@ -137,7 +137,9 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
137
137
  groupStatusIcons = _ref2.groupStatusIcons,
138
138
  onCollapsedChange = _ref2.onCollapsedChange,
139
139
  serverOffsetTime = _ref2.serverOffsetTime,
140
- highlightOptions = _ref2.highlightOptions;
140
+ highlightOptions = _ref2.highlightOptions,
141
+ _ref2$showLogTime = _ref2.showLogTime,
142
+ showLogTime = _ref2$showLogTime === void 0 ? true : _ref2$showLogTime;
141
143
  // ===================================================================
142
144
  // 一、分组数据
143
145
  // ===================================================================
@@ -350,12 +352,13 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
350
352
  useEffect(function () {
351
353
  if (initializedRef.current) return;
352
354
  initializedRef.current = true;
355
+ ignoreBoundaryFetchRef.current = Date.now() + IGNORE_BOUNDARY_FETCH_MS_FIRST_SCREEN;
353
356
  Promise.all([fetchGroups().then(function (groupData) {
354
357
  if (groupData && groupData.length > 0) {
355
358
  setGroups(normalizeGroupsForPipelineStatus(groupData, status));
356
359
  }
357
360
  }), initLoadRef.current()]).then(function () {
358
- ignoreBoundaryFetchRef.current = Date.now() + 500;
361
+ ignoreBoundaryFetchRef.current = Math.max(ignoreBoundaryFetchRef.current, Date.now() + IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL);
359
362
  requestAnimationFrame(function () {
360
363
  var _virtuosoRef$current;
361
364
  (_virtuosoRef$current = virtuosoRef.current) === null || _virtuosoRef$current === void 0 || _virtuosoRef$current.scrollToIndex({
@@ -389,7 +392,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
389
392
  boundaryFetchingRef.current = false;
390
393
  pendingScrollAfterPrevRef.current = null;
391
394
  pendingScrollAfterNextRef.current = null;
392
- ignoreBoundaryFetchRef.current = Date.now() + 500;
395
+ ignoreBoundaryFetchRef.current = Date.now() + IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL;
393
396
  }, [fetchLogs, logFetcherResult.reset, groupManager.expandAll, logSearchResult.clearSearch, status]);
394
397
  /* eslint-enable react-hooks/exhaustive-deps */
395
398
 
@@ -555,7 +558,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
555
558
  var prepended = prependedCountFromPrevRef.current;
556
559
  pendingScrollAfterPrevRef.current = null;
557
560
  displayLogsLenBeforeLoadPrevRef.current = null;
558
- ignoreBoundaryFetchRef.current = Date.now() + 500;
561
+ ignoreBoundaryFetchRef.current = Date.now() + IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL;
559
562
  if (boundaryIgnoreFollowUpTimeoutRef.current) {
560
563
  clearTimeout(boundaryIgnoreFollowUpTimeoutRef.current);
561
564
  }
@@ -577,26 +580,39 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
577
580
  }
578
581
  var anchorRowNext = pendingScrollAfterNextRef.current;
579
582
  if (anchorRowNext !== null) {
583
+ var _scrollerDomRef$curre3, _scrollerDomRef$curre4;
580
584
  var _lenBefore = displayLogsLenBeforeLoadNextRef.current;
581
585
  var _sameLength = _lenBefore !== null && _lenBefore === displayLogs.length && displayLogs.length > 0;
582
586
  var headRemoved = headRemovedFromNextRef.current;
583
587
  pendingScrollAfterNextRef.current = null;
584
588
  displayLogsLenBeforeLoadNextRef.current = null;
585
- ignoreBoundaryFetchRef.current = Date.now() + 500;
586
- var comp = applyBoundaryScrollCompensation(v, anchorRowNext, displayLogs, {
589
+
590
+ // 极大缩短屏蔽窗口(原本500ms):仅用 50ms 屏蔽当前帧由于 scrollTop 重置引起的乱序 rangeChanged。
591
+ // 不再长时间屏蔽,使得用户在快速下滚时 rangeChanged 依然灵敏,消除“触底发呆/卡顿”感。
592
+ ignoreBoundaryFetchRef.current = Date.now() + 50;
593
+
594
+ // 因为不再有漫长的屏蔽期,无需跟进定时器补偿,直接清理
595
+ if (boundaryIgnoreFollowUpTimeoutRef.current) {
596
+ clearTimeout(boundaryIgnoreFollowUpTimeoutRef.current);
597
+ boundaryIgnoreFollowUpTimeoutRef.current = null;
598
+ }
599
+
600
+ // 读取当前实时 scrollTop,而非请求前快照的 scrollTopBeforeLoadNextRef,
601
+ // 避免用户在 API 请求期间继续滚动导致补偿基准过时(跳屏)。
602
+ var realTimeScrollTop = (_scrollerDomRef$curre3 = (_scrollerDomRef$curre4 = scrollerDomRef.current) === null || _scrollerDomRef$curre4 === void 0 ? void 0 : _scrollerDomRef$curre4.scrollTop) !== null && _scrollerDomRef$curre3 !== void 0 ? _scrollerDomRef$curre3 : scrollTopBeforeLoadNextRef.current;
603
+ applyBoundaryScrollCompensation(v, anchorRowNext, displayLogs, {
587
604
  sameLength: _sameLength && headRemoved > 0,
588
605
  sameLengthPixelDeltaCount: headRemoved,
589
606
  scrollTopBefore: scrollTopBeforeLoadNextRef.current,
607
+ // 保留接口签名所需,但已被 currentScrollTop 覆盖作用
590
608
  defaultItemHeight: DEFAULT_VIRTUOSO_ITEM_HEIGHT,
591
- mode: 'appendHeadSlice'
609
+ mode: 'appendHeadSlice',
610
+ currentScrollTop: realTimeScrollTop
592
611
  });
593
- if (comp.kind === 'anchorMissing') {
594
- v.scrollToIndex({
595
- index: 'LAST',
596
- align: 'end',
597
- behavior: 'auto'
598
- });
599
- }
612
+
613
+ // 注意:这里不再添加 scrollIntoView 精修对齐。
614
+ // 实测在用户连续滚动滑轮/触摸板时,scrollIntoView 会强行接管视口,打断浏览器的滚动动量(momentum),
615
+ // 导致体感上每翻 5000 行截断时就会“硬卡一下”。仅做单纯的 scrollTop 像素增减,能保持滚动的平滑。
600
616
  }
601
617
  }, [displayLogs]);
602
618
 
@@ -740,7 +756,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
740
756
  if (groupActionRef.current) return;
741
757
  if (collapseAirdropActiveRef.current) return;
742
758
  if (userStoppedFollowingRef.current) return;
743
- ignoreBoundaryFetchRef.current = Date.now() + 500;
759
+ ignoreBoundaryFetchRef.current = Date.now() + IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL;
744
760
  (_virtuosoRef$current4 = virtuosoRef.current) === null || _virtuosoRef$current4 === void 0 || _virtuosoRef$current4.scrollToIndex({
745
761
  index: 'LAST',
746
762
  align: 'end',
@@ -758,7 +774,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
758
774
  // ===================================================================
759
775
  var scheduleScrollToDisplayIndex = useCallback(function (displayIndex) {
760
776
  var align = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'center';
761
- ignoreBoundaryFetchRef.current = Date.now() + 500;
777
+ ignoreBoundaryFetchRef.current = Date.now() + IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL;
762
778
  requestAnimationFrame(function () {
763
779
  var v = virtuosoRef.current;
764
780
  if (!v || displayLogsRef.current.length === 0) return;
@@ -916,7 +932,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
916
932
  if (newIndex !== -1) {
917
933
  var _virtuosoRef$current5;
918
934
  // 勿用更短的窗口覆盖分组操作已设置的较长忽略期
919
- ignoreBoundaryFetchRef.current = Math.max(ignoreBoundaryFetchRef.current, Date.now() + 800);
935
+ ignoreBoundaryFetchRef.current = Math.max(ignoreBoundaryFetchRef.current, Date.now() + IGNORE_BOUNDARY_FETCH_MS_FIRST_SCREEN);
920
936
  (_virtuosoRef$current5 = virtuosoRef.current) === null || _virtuosoRef$current5 === void 0 || _virtuosoRef$current5.scrollToIndex({
921
937
  index: newIndex,
922
938
  align: 'start'
@@ -965,14 +981,20 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
965
981
  });
966
982
  if (group) {
967
983
  if (airdropExpandInFlightRef.current.has(name)) return;
968
- suppressFollowForGroupAction();
969
- if (group.status === 'running') {
970
- // --- Running 分组:展开后跳到最新日志并恢复跟随 ---
984
+
985
+ /**
986
+ * 运行中、失败的分组从收起展开:始终 initLoad(倒序最后一页 = 当前最新日志窗口),
987
+ * 再展开并滚到底,避免仍停留在历史缓冲而看不到尾部输出。
988
+ * 此处不置 userStoppedFollowing / isUserBrowsingHistory,以便置底后继续 followOutput。
989
+ */
990
+ if (group.status === 'running' || group.status === 'failure') {
991
+ collapseAirdropActiveRef.current = false;
992
+ suppressFollowForGroupAction();
971
993
  airdropExpandInFlightRef.current.add(name);
972
994
  setLoadingGroupNames(function (prev) {
973
995
  return prev.includes(name) ? prev : [].concat(_toConsumableArray(prev), [name]);
974
996
  });
975
- var runExpandToLatest = /*#__PURE__*/function () {
997
+ var runRunningExpandToTail = /*#__PURE__*/function () {
976
998
  var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5() {
977
999
  return _regeneratorRuntime().wrap(function _callee5$(_context5) {
978
1000
  while (1) switch (_context5.prev = _context5.next) {
@@ -986,11 +1008,8 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
986
1008
  silent: true
987
1009
  });
988
1010
  case 5:
1011
+ ignoreBoundaryFetchRef.current = Math.max(ignoreBoundaryFetchRef.current, Date.now() + IGNORE_BOUNDARY_FETCH_MS_FIRST_SCREEN);
989
1012
  groupManager.toggleGroup(name);
990
- userStoppedFollowingRef.current = false;
991
- collapseAirdropActiveRef.current = false;
992
- logFetcherResult.setIsUserBrowsingHistory(false);
993
- ignoreBoundaryFetchRef.current = Date.now() + 500;
994
1013
  requestAnimationFrame(function () {
995
1014
  var _virtuosoRef$current6;
996
1015
  (_virtuosoRef$current6 = virtuosoRef.current) === null || _virtuosoRef$current6 === void 0 || _virtuosoRef$current6.scrollToIndex({
@@ -998,102 +1017,108 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
998
1017
  align: 'end',
999
1018
  behavior: 'auto'
1000
1019
  });
1020
+ // 未走「停跟」分支时仍可能带着此前的查阅历史态,置底后显式恢复跟随
1021
+ userStoppedFollowingRef.current = false;
1022
+ logFetcherResult.setIsUserBrowsingHistory(false);
1001
1023
  });
1002
- case 11:
1003
- _context5.prev = 11;
1024
+ _context5.next = 13;
1025
+ break;
1026
+ case 10:
1027
+ _context5.prev = 10;
1028
+ _context5.t0 = _context5["catch"](0);
1029
+ console.error('[PipelineLogViewer] running group expand to tail failed:', _context5.t0);
1030
+ case 13:
1031
+ _context5.prev = 13;
1004
1032
  airdropExpandInFlightRef.current.delete(name);
1005
1033
  setLoadingGroupNames(function (prev) {
1006
1034
  return prev.filter(function (n) {
1007
1035
  return n !== name;
1008
1036
  });
1009
1037
  });
1010
- return _context5.finish(11);
1011
- case 15:
1038
+ return _context5.finish(13);
1039
+ case 17:
1012
1040
  case "end":
1013
1041
  return _context5.stop();
1014
1042
  }
1015
- }, _callee5, null, [[0,, 11, 15]]);
1043
+ }, _callee5, null, [[0, 10, 13, 17]]);
1016
1044
  }));
1017
- return function runExpandToLatest() {
1045
+ return function runRunningExpandToTail() {
1018
1046
  return _ref9.apply(this, arguments);
1019
1047
  };
1020
1048
  }();
1021
- runExpandToLatest();
1022
- } else {
1023
- var _group$end2;
1024
- // --- 非 running 分组:展开后跳到分组最后一行 ---
1025
- var groupEnd = (_group$end2 = group.end) !== null && _group$end2 !== void 0 ? _group$end2 : group.start;
1026
- var _logFetcherResult$cur3 = logFetcherResult.cursor,
1027
- bufStart = _logFetcherResult$cur3.currentStartRow,
1028
- bufEnd = _logFetcherResult$cur3.currentEndRow;
1029
- var groupEndInBuffer = bufStart <= groupEnd && groupEnd <= bufEnd;
1030
- userStoppedFollowingRef.current = true;
1031
- logFetcherResult.setIsUserBrowsingHistory(true);
1032
- if (!groupEndInBuffer) {
1033
- airdropExpandInFlightRef.current.add(name);
1034
- setLoadingGroupNames(function (prev) {
1035
- return prev.includes(name) ? prev : [].concat(_toConsumableArray(prev), [name]);
1036
- });
1037
- var runAirdropThenExpand = /*#__PURE__*/function () {
1038
- var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6() {
1039
- var ok;
1040
- return _regeneratorRuntime().wrap(function _callee6$(_context6) {
1041
- while (1) switch (_context6.prev = _context6.next) {
1042
- case 0:
1043
- _context6.prev = 0;
1044
- _context6.next = 3;
1045
- return waitForFetchUnlock();
1046
- case 3:
1047
- _context6.next = 5;
1048
- return logFetcherResult.airdropLoad(groupEnd, {
1049
- window: 'center',
1050
- silent: true
1051
- });
1052
- case 5:
1053
- ok = _context6.sent;
1054
- if (ok) {
1055
- groupManager.toggleGroup(name);
1056
- pendingScrollAlignRef.current = 'center';
1057
- pendingScrollRowRef.current = groupEnd;
1058
- }
1059
- case 7:
1060
- _context6.prev = 7;
1061
- airdropExpandInFlightRef.current.delete(name);
1062
- setLoadingGroupNames(function (prev) {
1063
- return prev.filter(function (n) {
1064
- return n !== name;
1049
+ runRunningExpandToTail();
1050
+ return;
1051
+ }
1052
+ suppressFollowForGroupAction();
1053
+ userStoppedFollowingRef.current = true;
1054
+ logFetcherResult.setIsUserBrowsingHistory(true);
1055
+ var _logFetcherResult$cur3 = logFetcherResult.cursor,
1056
+ bufStart = _logFetcherResult$cur3.currentStartRow,
1057
+ bufEnd = _logFetcherResult$cur3.currentEndRow;
1058
+ // 当前内存窗口是否已包含 group.start(重复展开同一分组时不重复空降)
1059
+ var groupStartInBuffer = bufStart <= group.start && group.start <= bufEnd;
1060
+ if (!groupStartInBuffer) {
1061
+ airdropExpandInFlightRef.current.add(name);
1062
+ setLoadingGroupNames(function (prev) {
1063
+ return prev.includes(name) ? prev : [].concat(_toConsumableArray(prev), [name]);
1064
+ });
1065
+ var runAirdropThenExpand = /*#__PURE__*/function () {
1066
+ var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6() {
1067
+ var ok;
1068
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
1069
+ while (1) switch (_context6.prev = _context6.next) {
1070
+ case 0:
1071
+ _context6.prev = 0;
1072
+ _context6.next = 3;
1073
+ return logFetcherResult.airdropLoad(group.start, {
1074
+ window: 'fromStart',
1075
+ silent: true
1076
+ });
1077
+ case 3:
1078
+ ok = _context6.sent;
1079
+ if (!ok) {
1080
+ _context6.next = 7;
1081
+ break;
1082
+ }
1083
+ _context6.next = 7;
1084
+ return new Promise(function (resolve) {
1085
+ requestAnimationFrame(function () {
1086
+ requestAnimationFrame(function () {
1087
+ maintainScrollPosition(function () {
1088
+ groupManager.toggleGroup(name);
1089
+ });
1090
+ resolve();
1065
1091
  });
1066
1092
  });
1067
- return _context6.finish(7);
1068
- case 11:
1069
- case "end":
1070
- return _context6.stop();
1071
- }
1072
- }, _callee6, null, [[0,, 7, 11]]);
1073
- }));
1074
- return function runAirdropThenExpand() {
1075
- return _ref10.apply(this, arguments);
1076
- };
1077
- }();
1078
- runAirdropThenExpand();
1079
- } else {
1093
+ });
1094
+ case 7:
1095
+ _context6.prev = 7;
1096
+ airdropExpandInFlightRef.current.delete(name);
1097
+ setLoadingGroupNames(function (prev) {
1098
+ return prev.filter(function (n) {
1099
+ return n !== name;
1100
+ });
1101
+ });
1102
+ return _context6.finish(7);
1103
+ case 11:
1104
+ case "end":
1105
+ return _context6.stop();
1106
+ }
1107
+ }, _callee6, null, [[0,, 7, 11]]);
1108
+ }));
1109
+ return function runAirdropThenExpand() {
1110
+ return _ref10.apply(this, arguments);
1111
+ };
1112
+ }();
1113
+ runAirdropThenExpand().catch(function (err) {
1114
+ console.error('[PipelineLogViewer] airdrop expand failed:', err);
1115
+ });
1116
+ } else {
1117
+ // 缓冲内展开:用当前视口锚点重定位,避免原先 scrollToIndex(group.start) 把起点硬拉到顶,
1118
+ // running 等大分组展开时整屏上跳;与折叠分支一致走 maintainScrollPosition。
1119
+ maintainScrollPosition(function () {
1080
1120
  groupManager.toggleGroup(name);
1081
- ignoreBoundaryFetchRef.current = Date.now() + 500;
1082
- requestAnimationFrame(function () {
1083
- var currentLogs = displayLogsRef.current;
1084
- var displayIndex = currentLogs.findIndex(function (l) {
1085
- return l.absoluteRow === groupEnd;
1086
- });
1087
- if (displayIndex >= 0) {
1088
- var _virtuosoRef$current7;
1089
- (_virtuosoRef$current7 = virtuosoRef.current) === null || _virtuosoRef$current7 === void 0 || _virtuosoRef$current7.scrollToIndex({
1090
- index: displayIndex,
1091
- align: 'center',
1092
- behavior: 'auto'
1093
- });
1094
- }
1095
- });
1096
- }
1121
+ });
1097
1122
  }
1098
1123
  return;
1099
1124
  }
@@ -1143,27 +1168,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
1143
1168
  return map;
1144
1169
  }, [groups]);
1145
1170
 
1146
- /** START/END 控制行绝对行号集合(用于行号体系扣减,不参与分组边界判断) */
1147
- var controlMarkerRows = useMemo(function () {
1148
- var set = new Set();
1149
- var _iterator2 = _createForOfIteratorHelper(displayLogs),
1150
- _step2;
1151
- try {
1152
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1153
- var line = _step2.value;
1154
- if (isPipelineEndMarkerText(line.text) || isPipelineStartMarkerText(line.text)) {
1155
- set.add(line.absoluteRow);
1156
- }
1157
- }
1158
- } catch (err) {
1159
- _iterator2.e(err);
1160
- } finally {
1161
- _iterator2.f();
1162
- }
1163
- return set;
1164
- }, [displayLogs]);
1165
-
1166
- /** 整条流水线日志起算绝对行号(用于全局连续行号) */
1171
+ /** 整条流水线日志起算绝对行号(早于该行的缓冲内容行号展示回退) */
1167
1172
  var pipelineMinAbsoluteRow = useMemo(function () {
1168
1173
  return groups.length > 0 ? Math.min.apply(Math, _toConsumableArray(groups.map(function (g) {
1169
1174
  return g.start;
@@ -1181,8 +1186,6 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
1181
1186
  handleToggleGroupRef.current = handleToggleGroup;
1182
1187
  var lineNumberWidthRef = useRef(lineNumberWidth);
1183
1188
  lineNumberWidthRef.current = lineNumberWidth;
1184
- var controlMarkerRowsRef = useRef(controlMarkerRows);
1185
- controlMarkerRowsRef.current = controlMarkerRows;
1186
1189
  var pipelineMinAbsoluteRowRef = useRef(pipelineMinAbsoluteRow);
1187
1190
  pipelineMinAbsoluteRowRef.current = pipelineMinAbsoluteRow;
1188
1191
 
@@ -1198,7 +1201,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
1198
1201
  return {};
1199
1202
  },
1200
1203
  // eslint-disable-next-line react-hooks/exhaustive-deps
1201
- [groupManager.collapsedGroups, logSearchResult.searchState, lineNumberWidth, currentStartRow, highlightOptions, groups, loadingGroupNames]);
1204
+ [groupManager.collapsedGroups, logSearchResult.searchState, lineNumberWidth, currentStartRow, highlightOptions, groups, loadingGroupNames, showLogTime]);
1202
1205
  var itemContent = useCallback(function (index) {
1203
1206
  var _groupsForHeader;
1204
1207
  var currentDisplayLogs = displayLogsRef.current;
@@ -1236,14 +1239,20 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
1236
1239
  var showLogRow = !line.isFoldedGroupStart && !line.isEmptyGroupStart;
1237
1240
  var isControlEndMarker = isPipelineEndMarkerText(line.text);
1238
1241
  var isControlStartMarker = isPipelineStartMarkerText(line.text);
1239
- /** 仅压扁 START/END 控制行;行号体系也仅基于控制行扣减 */
1242
+ /** 仅压扁 START/END 控制行;行号规则见 getPipelineGroupedLineNumberLabel */
1240
1243
  var collapseGroupEdgeLog = showLogRow && !line.isEmptyGroupStart && (isControlEndMarker || isControlStartMarker);
1241
- var lineNumberLabel = groups.length > 0 && showLogRow ? getGlobalVisibleLineNumberLabel(line.absoluteRow, controlMarkerRowsRef.current, pipelineMinAbsoluteRowRef.current) : undefined;
1244
+ var rowGroup = currentGroupManager.getGroupByRow(line.absoluteRow);
1245
+ var rowGroupIndex = rowGroup ? groups.findIndex(function (g) {
1246
+ return g.name === rowGroup.name;
1247
+ }) : -1;
1248
+ var isControlMarkerRow = showLogRow && !line.isEmptyGroupStart && (isControlEndMarker || isControlStartMarker);
1249
+ var lineNumberLabel = groups.length > 0 && showLogRow ? getPipelineGroupedLineNumberLabel(line.absoluteRow, rowGroup, rowGroupIndex, isControlMarkerRow, pipelineMinAbsoluteRowRef.current) : undefined;
1242
1250
  var logRow = /*#__PURE__*/React.createElement(LogRow, {
1243
1251
  absoluteRow: line.absoluteRow,
1244
1252
  lineNumberLabel: lineNumberLabel,
1245
1253
  text: line.text,
1246
1254
  logTime: line.logTime,
1255
+ showLogTime: showLogTime,
1247
1256
  isMatched: isMatched,
1248
1257
  isActiveMatch: isActiveMatch,
1249
1258
  lineNumberWidth: lineNumberWidthRef.current,
@@ -1291,7 +1300,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
1291
1300
  }))) : 0;
1292
1301
  bufStart = logFetcherResult.cursor.currentStartRow;
1293
1302
  needsTopWindow = bufStart > globalFirstRow;
1294
- ignoreBoundaryFetchRef.current = Date.now() + 500;
1303
+ ignoreBoundaryFetchRef.current = Date.now() + IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL;
1295
1304
  if (!needsTopWindow) {
1296
1305
  _context7.next = 21;
1297
1306
  break;
@@ -1321,8 +1330,8 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
1321
1330
  return _context7.abrupt("return");
1322
1331
  case 21:
1323
1332
  requestAnimationFrame(function () {
1324
- var _virtuosoRef$current8;
1325
- (_virtuosoRef$current8 = virtuosoRef.current) === null || _virtuosoRef$current8 === void 0 || _virtuosoRef$current8.scrollToIndex({
1333
+ var _virtuosoRef$current7;
1334
+ (_virtuosoRef$current7 = virtuosoRef.current) === null || _virtuosoRef$current7 === void 0 || _virtuosoRef$current7.scrollToIndex({
1326
1335
  index: 0,
1327
1336
  align: 'start',
1328
1337
  behavior: 'auto'
@@ -1345,30 +1354,33 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
1345
1354
  return _regeneratorRuntime().wrap(function _callee8$(_context8) {
1346
1355
  while (1) switch (_context8.prev = _context8.next) {
1347
1356
  case 0:
1357
+ _context8.next = 2;
1358
+ return waitForFetchUnlock();
1359
+ case 2:
1348
1360
  groupManager.expandAll();
1349
1361
  userStoppedFollowingRef.current = false;
1350
1362
  collapseAirdropActiveRef.current = false;
1351
1363
  needsFetch = status !== 'running' && !logFetcherResult.boundaryLock.isLastPage || status === 'running' && logFetcherResult.isUserBrowsingHistory;
1352
1364
  if (!needsFetch) {
1353
- _context8.next = 8;
1365
+ _context8.next = 10;
1354
1366
  break;
1355
1367
  }
1356
- _context8.next = 7;
1368
+ _context8.next = 9;
1357
1369
  return logFetcherResult.initLoad();
1358
- case 7:
1370
+ case 9:
1359
1371
  logFetcherResult.updateBoundaryLock({
1360
1372
  isFirstPage: false
1361
1373
  });
1362
- case 8:
1363
- ignoreBoundaryFetchRef.current = Date.now() + 500;
1374
+ case 10:
1375
+ ignoreBoundaryFetchRef.current = Date.now() + IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL;
1364
1376
  setTimeout(function () {
1365
- var _virtuosoRef$current9;
1366
- (_virtuosoRef$current9 = virtuosoRef.current) === null || _virtuosoRef$current9 === void 0 || _virtuosoRef$current9.scrollToIndex({
1377
+ var _virtuosoRef$current8;
1378
+ (_virtuosoRef$current8 = virtuosoRef.current) === null || _virtuosoRef$current8 === void 0 || _virtuosoRef$current8.scrollToIndex({
1367
1379
  index: 'LAST',
1368
1380
  align: 'end'
1369
1381
  });
1370
1382
  }, 50);
1371
- case 10:
1383
+ case 12:
1372
1384
  case "end":
1373
1385
  return _context8.stop();
1374
1386
  }
@@ -1425,7 +1437,7 @@ var PipelineLogViewer = /*#__PURE__*/forwardRef(function (_ref2, ref) {
1425
1437
  group: stickyHeader.stickyGroup,
1426
1438
  collapsed: stickyHeader.stickyGroup ? groupManager.collapsedGroups.has(stickyHeader.stickyGroup.name) : false,
1427
1439
  loading: stickyHeader.stickyGroup != null && loadingGroupNames.includes(stickyHeader.stickyGroup.name),
1428
- onToggle: handleToggleGroup,
1440
+ onToggle: handleToggleGroupRef.current,
1429
1441
  groupStatusIcons: groupStatusIcons,
1430
1442
  serverOffsetTime: serverOffsetTime
1431
1443
  }), /*#__PURE__*/React.createElement(Virtuoso, {
@@ -17,6 +17,8 @@ interface LogRowProps {
17
17
  lineNumberWidth: number;
18
18
  /** 是否显示行号 */
19
19
  showLineNumber?: boolean;
20
+ /** 是否显示日志时间列(默认 true) */
21
+ showLogTime?: boolean;
20
22
  /** 关键字规则高亮;搜索命中时不会应用 */
21
23
  highlightOptions?: IHighlightOptions[];
22
24
  /** 当前搜索关键字(非空且本行命中时在 HTML 内包裹 mark,仅高亮关键字) */
@@ -1 +1 @@
1
- {"version":3,"file":"LogRow.d.ts","sourceRoot":"","sources":["../../../../src/PipelineLogViewer/components/LogRow.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AA4BlD,UAAU,WAAW;IACnB,aAAa;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wBAAwB;IACxB,gBAAgB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACvC,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;;AAiED,wBAAkC"}
1
+ {"version":3,"file":"LogRow.d.ts","sourceRoot":"","sources":["../../../../src/PipelineLogViewer/components/LogRow.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AA4BlD,UAAU,WAAW;IACnB,aAAa;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yBAAyB;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wBAAwB;IACxB,gBAAgB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACvC,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;;AAkED,wBAAkC"}
@@ -40,6 +40,8 @@ var LogRow = function LogRow(_ref) {
40
40
  lineNumberWidth = _ref.lineNumberWidth,
41
41
  _ref$showLineNumber = _ref.showLineNumber,
42
42
  showLineNumber = _ref$showLineNumber === void 0 ? true : _ref$showLineNumber,
43
+ _ref$showLogTime = _ref.showLogTime,
44
+ showLogTime = _ref$showLogTime === void 0 ? true : _ref$showLogTime,
43
45
  highlightOptions = _ref.highlightOptions,
44
46
  _ref$searchKeyword = _ref.searchKeyword,
45
47
  searchKeyword = _ref$searchKeyword === void 0 ? '' : _ref$searchKeyword;
@@ -69,7 +71,7 @@ var LogRow = function LogRow(_ref) {
69
71
  style: {
70
72
  minWidth: "".concat(lineNumberWidth, "px")
71
73
  }
72
- }, lineNumberLabel === null ? '' : lineNumberLabel !== null && lineNumberLabel !== void 0 ? lineNumberLabel : absoluteRow), logTime != null && logTime > 0 && /*#__PURE__*/React.createElement("span", {
74
+ }, lineNumberLabel === null ? '' : lineNumberLabel !== null && lineNumberLabel !== void 0 ? lineNumberLabel : absoluteRow), showLogTime && logTime != null && logTime > 0 && /*#__PURE__*/React.createElement("span", {
73
75
  className: "pipeline-log-row__log-time"
74
76
  }, formatLogTime(logTime)), /*#__PURE__*/React.createElement("span", {
75
77
  className: "pipeline-log-row__text",
@@ -10,6 +10,16 @@ export declare const DEFAULT_VIRTUOSO_ITEM_HEIGHT = 20;
10
10
  * 而不是用户刚看到的行(如 7571 在顶时 startIndex 可能已是 21,却满足不了 <=10)。
11
11
  */
12
12
  export declare const BOUNDARY_PREFETCH_INDEX_MARGIN = 400;
13
+ /**
14
+ * 首屏 init / 新日志源首包:屏蔽 rangeChanged 边界预取时长 (ms)。
15
+ * 略长于普通窗口,避免弹窗首帧、scrollToIndex(LAST) 前误判触顶触发 loadPrev。
16
+ */
17
+ export declare const IGNORE_BOUNDARY_FETCH_MS_FIRST_SCREEN = 800;
18
+ /**
19
+ * 程序化滚动、loadPrev/loadNext 补偿、scrollToTop/Bottom 等之后:
20
+ * 屏蔽 rangeChanged 边界预取时长 (ms)。
21
+ */
22
+ export declare const IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL = 500;
13
23
  /** running 态轮询间隔 (默认值,单位 ms) */
14
24
  export declare const DEFAULT_POLL_INTERVAL = 1000;
15
25
  /** running→结束态:收尾 initLoad 之后追加 loadNext 的次数 */
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/PipelineLogViewer/constants.ts"],"names":[],"mappings":"AAKA,mBAAmB;AACnB,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,qBAAqB;AACrB,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD,gCAAgC;AAChC,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,gDAAgD;AAChD,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,iCAAiC;AACjC,eAAO,MAAM,mCAAmC,OAAO,CAAC;AAExD,yBAAyB;AACzB,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD,oBAAoB;AACpB,eAAO,MAAM,wBAAwB,gBAAgB,CAAC;AAEtD,cAAc;AACd,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,cAAc;AACd,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,oBAAoB;AACpB,eAAO,MAAM,mBAAmB,YAAY,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/PipelineLogViewer/constants.ts"],"names":[],"mappings":"AAKA,mBAAmB;AACnB,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,qBAAqB;AACrB,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,qCAAqC,MAAM,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,qCAAqC,MAAM,CAAC;AAEzD,gCAAgC;AAChC,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,gDAAgD;AAChD,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,iCAAiC;AACjC,eAAO,MAAM,mCAAmC,OAAO,CAAC;AAExD,yBAAyB;AACzB,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD,oBAAoB;AACpB,eAAO,MAAM,wBAAwB,gBAAgB,CAAC;AAEtD,cAAc;AACd,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,cAAc;AACd,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,oBAAoB;AACpB,eAAO,MAAM,mBAAmB,YAAY,CAAC"}
@@ -19,6 +19,18 @@ export var DEFAULT_VIRTUOSO_ITEM_HEIGHT = 20;
19
19
  */
20
20
  export var BOUNDARY_PREFETCH_INDEX_MARGIN = 400;
21
21
 
22
+ /**
23
+ * 首屏 init / 新日志源首包:屏蔽 rangeChanged 边界预取时长 (ms)。
24
+ * 略长于普通窗口,避免弹窗首帧、scrollToIndex(LAST) 前误判触顶触发 loadPrev。
25
+ */
26
+ export var IGNORE_BOUNDARY_FETCH_MS_FIRST_SCREEN = 800;
27
+
28
+ /**
29
+ * 程序化滚动、loadPrev/loadNext 补偿、scrollToTop/Bottom 等之后:
30
+ * 屏蔽 rangeChanged 边界预取时长 (ms)。
31
+ */
32
+ export var IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL = 500;
33
+
22
34
  /** running 态轮询间隔 (默认值,单位 ms) */
23
35
  export var DEFAULT_POLL_INTERVAL = 1000;
24
36
 
@@ -1 +1 @@
1
- {"version":3,"file":"useStickyHeader.d.ts","sourceRoot":"","sources":["../../../../src/PipelineLogViewer/hooks/useStickyHeader.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,UAAU,sBAAsB;IAC9B,WAAW;IACX,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,UAAU,qBAAqB;IAC7B,0CAA0C;IAC1C,WAAW,EAAE,SAAS,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACzE;AAqCD;;GAEG;AACH,iBAAS,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CA0B/E;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"useStickyHeader.d.ts","sourceRoot":"","sources":["../../../../src/PipelineLogViewer/hooks/useStickyHeader.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,UAAU,sBAAsB;IAC9B,WAAW;IACX,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,UAAU,qBAAqB;IAC7B,0CAA0C;IAC1C,WAAW,EAAE,SAAS,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACzE;AA6CD;;GAEG;AACH,iBAAS,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAsC/E;AAED,eAAe,eAAe,CAAC"}
@@ -15,6 +15,14 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
15
15
  // ============================================================================
16
16
 
17
17
  import { useCallback, useEffect, useState } from 'react';
18
+ /** 提前探测行数:让吸顶分组在边界前更早感知切换趋势 */
19
+ var STICKY_LEAD_ROWS = 2;
20
+ /**
21
+ * 新分组头仍在视口可见区时,暂不渲染吸顶,避免出现「吸顶头 + 列表头」双头重影。
22
+ * 该窗口用绝对行号近似:Top 仍落在 group.start 附近时,优先显示列表内真实头。
23
+ */
24
+ var STICKY_SUPPRESS_ROWS_NEAR_GROUP_START = 1;
25
+
18
26
  /** 与 useDisplayLogs 折叠区间算法一致的有效上界(用于判断行是否落在该组) */
19
27
  function effectiveGroupEnd(group, gi, groups, bufferEnd) {
20
28
  var start = group.start,
@@ -68,7 +76,19 @@ function useStickyHeader(options) {
68
76
  setStickyGroup(null);
69
77
  return;
70
78
  }
71
- setStickyGroup(findStickyGroup(groups, absoluteTopRow, bufferEnd));
79
+ var stickyProbeRow = Math.max(0, absoluteTopRow + STICKY_LEAD_ROWS);
80
+ var nextStickyGroup = findStickyGroup(groups, stickyProbeRow, bufferEnd);
81
+ if (!nextStickyGroup) {
82
+ setStickyGroup(null);
83
+ return;
84
+ }
85
+ // 边界抑制:提前命中新分组时,其真实 GroupHeader 常仍在视口内,
86
+ // 这时渲染吸顶会出现双头。让列表内头部先过顶,再接管吸顶展示。
87
+ if (absoluteTopRow <= nextStickyGroup.start + STICKY_SUPPRESS_ROWS_NEAR_GROUP_START) {
88
+ setStickyGroup(null);
89
+ return;
90
+ }
91
+ setStickyGroup(nextStickyGroup);
72
92
  }, [groups]);
73
93
  return {
74
94
  stickyGroup: stickyGroup,
@@ -232,7 +232,7 @@ input.pipeline-log-search-bar__input {
232
232
 
233
233
  .pipeline-log-search-bar__dirty-hint,
234
234
  .pipeline-log-search-bar__nav-hint {
235
- margin-right: 4px;
235
+ margin: 0 4px;
236
236
  color: #959da5bf;
237
237
  font-size: 11px;
238
238
  line-height: 1.2;
@@ -335,7 +335,7 @@ input.pipeline-log-search-bar__input {
335
335
  }
336
336
 
337
337
  .pipeline-log-search-bar__result-count {
338
- margin-right: 4px;
338
+ margin: 0 4px;
339
339
  font-size: 12px;
340
340
  }
341
341
 
@@ -92,6 +92,10 @@ export interface PipelineLogViewerProps {
92
92
  highlightOptions?: IHighlightOptions[];
93
93
  /** 服务器当前时间 (ms) 与本地 Date.now() 的偏移时间差 */
94
94
  serverOffsetTime?: number;
95
+ /**
96
+ * 是否展示每行日志时间列;默认 true。
97
+ */
98
+ showLogTime?: boolean;
95
99
  }
96
100
  /** 日志游标状态 —— 标识内存中日志块的物理边界 */
97
101
  export interface LogCursor {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/PipelineLogViewer/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/PipelineLogViewer/types.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAMlC,WAAW;AACX,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7E,6BAA6B;AAC7B,MAAM,WAAW,SAAS;IACxB,WAAW;IACX,IAAI,EAAE,MAAM,CAAC;IACb,aAAa;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,oCAAoC;AACpC,MAAM,WAAW,aAAa;IAC5B,WAAW;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,QAAQ,EAAE,CAAC;CAClB;AAED,2BAA2B;AAC3B,MAAM,WAAW,UAAU;IACzB,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAMD,MAAM,WAAW,sBAAsB;IACrC,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc;IACd,iBAAiB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACpC,gCAAgC;IAChC,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,YAAY;IACZ,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IAChC,gBAAgB;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9D,sBAAsB;IACtB,MAAM,EAAE,iBAAiB,CAAC;IAG1B,aAAa;IACb,WAAW,EAAE,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACxC,aAAa;IACb,SAAS,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9D,gBAAgB;IAChB,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAErD,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB;IACjB,iBAAiB,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACvC,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAMD,8BAA8B;AAC9B,MAAM,WAAW,SAAS;IACxB,oBAAoB;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,aAAa;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,qCAAqC;IACrC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,YAAY;AACZ,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB,wBAAwB;IACxB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,WAAW;AACX,MAAM,WAAW,WAAW;IAC1B,YAAY;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,wBAAwB;IACxB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW;IACX,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,qCAAqC;AACrC,MAAM,WAAW,oBAAoB;IACnC,YAAY;IACZ,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY;IACZ,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,aAAa;IACb,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,aAAa;IACb,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,cAAc;IACd,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,iBAAiB;IACjB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,iBAAiB;IACjB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW;IACX,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB"}
@@ -1,11 +1,13 @@
1
+ import type { GroupData } from '../types';
1
2
  /**
2
- * 全局连续行号(隐藏各分组首尾边界行后,从 1 递增)。
3
+ * 流水线分组场景下的左侧行号。
3
4
  *
4
- * 对非边界行 R:序号 = 1 + |{ r | pipelineMinRow ≤ r < R 且 r 不在 edgeRows }|
5
- * 即:absoluteRow - pipelineMinRow - (落在 [pipelineMinRow, absoluteRow) 内的边界行个数) + 1
5
+ * 规则(与产品约定一致):
6
+ * - 每个分组在日志流中有 START / END 两行控制标识,各占一个绝对行号。
7
+ * - 第一个分组(groups[0]):非控制行展示 **绝对行号** `absoluteRow`。
8
+ * - 第 n 个分组(n = groups 下标,且 n ≥ 1):非控制行展示 `absoluteRow - (2n)`, 不是 2n -1 是因为第一个分组第1行才是有效日志,把整体行号往前推了1。
9
+ * - START/END 控制行:左侧不展示数字(返回 null)。
6
10
  *
7
- * pipelineMinRow 取各分组 start 的最小值,作为整条流水日志的起算行。
8
- *
9
- * @returns null 表示当前行为分组首尾占位行,左侧不展示数字;undefined 表示早于流水线起点,沿用绝对行号
11
+ * @returns null = 控制行不占位数字;undefined = 无法归类或早于 pipeline 起点,由 LogRow 回退为 absoluteRow
10
12
  */
11
- export declare function getGlobalVisibleLineNumberLabel(absoluteRow: number, edgeRows: Set<number>, pipelineMinRow: number): number | null | undefined;
13
+ export declare function getPipelineGroupedLineNumberLabel(absoluteRow: number, group: GroupData | undefined, groupIndex: number, isControlMarkerRow: boolean, pipelineMinRow: number): number | null | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"groupLineNumber.d.ts","sourceRoot":"","sources":["../../../../src/PipelineLogViewer/utils/groupLineNumber.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EACrB,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,IAAI,GAAG,SAAS,CAQ3B"}
1
+ {"version":3,"file":"groupLineNumber.d.ts","sourceRoot":"","sources":["../../../../src/PipelineLogViewer/utils/groupLineNumber.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,UAAU,EAAE,MAAM,EAClB,kBAAkB,EAAE,OAAO,EAC3B,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,IAAI,GAAG,SAAS,CAS3B"}
@@ -1,34 +1,22 @@
1
- function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
- function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
4
- /* eslint-disable no-plusplus */
5
1
  /* eslint-disable import/prefer-default-export */
6
- /* eslint-disable no-restricted-syntax */
2
+
7
3
  /**
8
- * 全局连续行号(隐藏各分组首尾边界行后,从 1 递增)。
4
+ * 流水线分组场景下的左侧行号。
9
5
  *
10
- * 对非边界行 R:序号 = 1 + |{ r | pipelineMinRow ≤ r < R 且 r 不在 edgeRows }|
11
- * 即:absoluteRow - pipelineMinRow - (落在 [pipelineMinRow, absoluteRow) 内的边界行个数) + 1
6
+ * 规则(与产品约定一致):
7
+ * - 每个分组在日志流中有 START / END 两行控制标识,各占一个绝对行号。
8
+ * - 第一个分组(groups[0]):非控制行展示 **绝对行号** `absoluteRow`。
9
+ * - 第 n 个分组(n = groups 下标,且 n ≥ 1):非控制行展示 `absoluteRow - (2n)`, 不是 2n -1 是因为第一个分组第1行才是有效日志,把整体行号往前推了1。
10
+ * - START/END 控制行:左侧不展示数字(返回 null)。
12
11
  *
13
- * pipelineMinRow 取各分组 start 的最小值,作为整条流水日志的起算行。
14
- *
15
- * @returns null 表示当前行为分组首尾占位行,左侧不展示数字;undefined 表示早于流水线起点,沿用绝对行号
12
+ * @returns null = 控制行不占位数字;undefined = 无法归类或早于 pipeline 起点,由 LogRow 回退为 absoluteRow
16
13
  */
17
- export function getGlobalVisibleLineNumberLabel(absoluteRow, edgeRows, pipelineMinRow) {
18
- if (edgeRows.has(absoluteRow)) return null;
14
+ export function getPipelineGroupedLineNumberLabel(absoluteRow, group, groupIndex, isControlMarkerRow, pipelineMinRow) {
15
+ if (isControlMarkerRow) return null;
19
16
  if (absoluteRow < pipelineMinRow) return undefined;
20
- var edgeBefore = 0;
21
- var _iterator = _createForOfIteratorHelper(edgeRows),
22
- _step;
23
- try {
24
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
25
- var e = _step.value;
26
- if (e >= pipelineMinRow && e < absoluteRow) edgeBefore++;
27
- }
28
- } catch (err) {
29
- _iterator.e(err);
30
- } finally {
31
- _iterator.f();
17
+ if (!group || groupIndex < 0) return undefined;
18
+ if (groupIndex === 0) {
19
+ return absoluteRow;
32
20
  }
33
- return absoluteRow - pipelineMinRow - edgeBefore + 1;
21
+ return absoluteRow - 2 * groupIndex;
34
22
  }
@@ -8,7 +8,9 @@ export type BoundaryScrollResult = {
8
8
  };
9
9
  /**
10
10
  * 滑动窗口 totalCount 不变时,Virtuoso 不会自动补偿 scrollTop。
11
- * 用像素粗调 + scrollIntoView 对齐到锚点行(与 loadPrev/loadNext 对称)。
11
+ * - prepend 模式:像素粗调 + scrollIntoView 对齐到锚点行。
12
+ * - appendHeadSlice 模式:仅用当前实时 scrollTop 减去截断行的像素偏移量。
13
+ * 不做锚点行定位,避免用户在 API 请求期间继续滚动时被强行拉回旧位置(跳屏)。
12
14
  */
13
15
  export declare function applyBoundaryScrollCompensation(v: VirtuosoHandle, anchorRow: number, rowsWithAbsolute: Array<{
14
16
  absoluteRow: number;
@@ -19,4 +21,9 @@ export declare function applyBoundaryScrollCompensation(v: VirtuosoHandle, ancho
19
21
  scrollTopBefore: number;
20
22
  defaultItemHeight: number;
21
23
  mode: BoundaryScrollMode;
24
+ /**
25
+ * appendHeadSlice 专用:当前实时 scrollTop(从 scroller DOM 读取)。
26
+ * 优先于 scrollTopBefore 使用,避免请求期间用户继续滚动导致补偿基准过时。
27
+ */
28
+ currentScrollTop?: number;
22
29
  }): BoundaryScrollResult;
@@ -1 +1 @@
1
- {"version":3,"file":"virtuosoBoundaryScroll.d.ts","sourceRoot":"","sources":["../../../../src/PipelineLogViewer/utils/virtuosoBoundaryScroll.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,6DAA6D;AAC7D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,iBAAiB,CAAC;AAE/D,MAAM,MAAM,oBAAoB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAE9E;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,CAAC,EAAE,cAAc,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,EAC5D,OAAO,EAAE;IACP,UAAU,EAAE,OAAO,CAAC;IACpB,+CAA+C;IAC/C,yBAAyB,EAAE,MAAM,CAAC;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,kBAAkB,CAAC;CAC1B,GACA,oBAAoB,CAgCtB"}
1
+ {"version":3,"file":"virtuosoBoundaryScroll.d.ts","sourceRoot":"","sources":["../../../../src/PipelineLogViewer/utils/virtuosoBoundaryScroll.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,6DAA6D;AAC7D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,iBAAiB,CAAC;AAE/D,MAAM,MAAM,oBAAoB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAE9E;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,CAAC,EAAE,cAAc,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,EAC5D,OAAO,EAAE;IACP,UAAU,EAAE,OAAO,CAAC;IACpB,+CAA+C;IAC/C,yBAAyB,EAAE,MAAM,CAAC;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,kBAAkB,CAAC;IACzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GACA,oBAAoB,CAyDtB"}
@@ -2,9 +2,51 @@
2
2
 
3
3
  /**
4
4
  * 滑动窗口 totalCount 不变时,Virtuoso 不会自动补偿 scrollTop。
5
- * 用像素粗调 + scrollIntoView 对齐到锚点行(与 loadPrev/loadNext 对称)。
5
+ * - prepend 模式:像素粗调 + scrollIntoView 对齐到锚点行。
6
+ * - appendHeadSlice 模式:仅用当前实时 scrollTop 减去截断行的像素偏移量。
7
+ * 不做锚点行定位,避免用户在 API 请求期间继续滚动时被强行拉回旧位置(跳屏)。
6
8
  */
7
9
  export function applyBoundaryScrollCompensation(v, anchorRow, rowsWithAbsolute, options) {
10
+ var sameLength = options.sameLength,
11
+ sameLengthPixelDeltaCount = options.sameLengthPixelDeltaCount,
12
+ scrollTopBefore = options.scrollTopBefore,
13
+ defaultItemHeight = options.defaultItemHeight,
14
+ mode = options.mode;
15
+
16
+ // -----------------------------------------------------------------------
17
+ // appendHeadSlice(loadNext 满载截断头部):
18
+ // 仅做像素偏移补偿,不做锚点行定位。
19
+ //
20
+ // 原理:head 截断 N 行后,所有 item 的 index 减小了 N,
21
+ // 视觉上内容整体上移了 N * itemHeight 像素。
22
+ // 要保持用户看到的内容不变,scrollTop 需减去同样的像素量。
23
+ //
24
+ // 使用 currentScrollTop(实时值)而非 scrollTopBefore(请求前快照),
25
+ // 解决用户在 API 请求期间继续滚动导致的跳屏问题。
26
+ // -----------------------------------------------------------------------
27
+ if (mode === 'appendHeadSlice') {
28
+ if (sameLength && sameLengthPixelDeltaCount > 0) {
29
+ var _options$currentScrol;
30
+ var baseScrollTop = (_options$currentScrol = options.currentScrollTop) !== null && _options$currentScrol !== void 0 ? _options$currentScrol : scrollTopBefore;
31
+ var delta = -sameLengthPixelDeltaCount * defaultItemHeight;
32
+ var top = Math.max(0, baseScrollTop + delta);
33
+ v.scrollTo({
34
+ top: top,
35
+ behavior: 'auto'
36
+ });
37
+ }
38
+ // 不管有没有像素补偿,都不再用锚点行做 scrollIntoView,
39
+ // 避免强行拉回旧位置。
40
+ return {
41
+ kind: 'ok'
42
+ };
43
+ }
44
+
45
+ // -----------------------------------------------------------------------
46
+ // prepend(loadPrev 满载截断尾部):
47
+ // 像素粗调 + scrollIntoView 锚点行精确对齐。
48
+ // prepend 场景下用户通常在最顶部等待加载,scrollTopBefore 不会过时。
49
+ // -----------------------------------------------------------------------
8
50
  var newIndex = rowsWithAbsolute.findIndex(function (l) {
9
51
  return l && l.absoluteRow === anchorRow;
10
52
  });
@@ -13,22 +55,17 @@ export function applyBoundaryScrollCompensation(v, anchorRow, rowsWithAbsolute,
13
55
  kind: 'anchorMissing'
14
56
  };
15
57
  }
16
- var sameLength = options.sameLength,
17
- sameLengthPixelDeltaCount = options.sameLengthPixelDeltaCount,
18
- scrollTopBefore = options.scrollTopBefore,
19
- defaultItemHeight = options.defaultItemHeight,
20
- mode = options.mode;
21
58
  if (sameLength && sameLengthPixelDeltaCount > 0) {
22
- var delta = mode === 'prepend' ? sameLengthPixelDeltaCount * defaultItemHeight : -sameLengthPixelDeltaCount * defaultItemHeight;
23
- var top = Math.max(0, scrollTopBefore + delta);
59
+ var _delta = sameLengthPixelDeltaCount * defaultItemHeight;
60
+ var _top = Math.max(0, scrollTopBefore + _delta);
24
61
  v.scrollTo({
25
- top: top,
62
+ top: _top,
26
63
  behavior: 'auto'
27
64
  });
28
65
  requestAnimationFrame(function () {
29
66
  v.scrollIntoView({
30
67
  index: newIndex,
31
- align: mode === 'prepend' ? 'start' : 'end',
68
+ align: 'start',
32
69
  behavior: 'auto'
33
70
  });
34
71
  });
@@ -38,7 +75,7 @@ export function applyBoundaryScrollCompensation(v, anchorRow, rowsWithAbsolute,
38
75
  }
39
76
  v.scrollToIndex({
40
77
  index: newIndex,
41
- align: mode === 'prepend' ? 'start' : 'end',
78
+ align: 'start',
42
79
  behavior: 'auto'
43
80
  });
44
81
  return {
@@ -17,6 +17,8 @@ interface LogRowProps {
17
17
  lineNumberWidth: number;
18
18
  /** 是否显示行号 */
19
19
  showLineNumber?: boolean;
20
+ /** 是否显示日志时间列(默认 true) */
21
+ showLogTime?: boolean;
20
22
  /** 关键字规则高亮;搜索命中时不会应用 */
21
23
  highlightOptions?: IHighlightOptions[];
22
24
  /** 当前搜索关键字(非空且本行命中时在 HTML 内包裹 mark,仅高亮关键字) */
@@ -10,6 +10,16 @@ export declare const DEFAULT_VIRTUOSO_ITEM_HEIGHT = 20;
10
10
  * 而不是用户刚看到的行(如 7571 在顶时 startIndex 可能已是 21,却满足不了 <=10)。
11
11
  */
12
12
  export declare const BOUNDARY_PREFETCH_INDEX_MARGIN = 400;
13
+ /**
14
+ * 首屏 init / 新日志源首包:屏蔽 rangeChanged 边界预取时长 (ms)。
15
+ * 略长于普通窗口,避免弹窗首帧、scrollToIndex(LAST) 前误判触顶触发 loadPrev。
16
+ */
17
+ export declare const IGNORE_BOUNDARY_FETCH_MS_FIRST_SCREEN = 800;
18
+ /**
19
+ * 程序化滚动、loadPrev/loadNext 补偿、scrollToTop/Bottom 等之后:
20
+ * 屏蔽 rangeChanged 边界预取时长 (ms)。
21
+ */
22
+ export declare const IGNORE_BOUNDARY_FETCH_MS_AFTER_SCROLL = 500;
13
23
  /** running 态轮询间隔 (默认值,单位 ms) */
14
24
  export declare const DEFAULT_POLL_INTERVAL = 1000;
15
25
  /** running→结束态:收尾 initLoad 之后追加 loadNext 的次数 */
@@ -92,6 +92,10 @@ export interface PipelineLogViewerProps {
92
92
  highlightOptions?: IHighlightOptions[];
93
93
  /** 服务器当前时间 (ms) 与本地 Date.now() 的偏移时间差 */
94
94
  serverOffsetTime?: number;
95
+ /**
96
+ * 是否展示每行日志时间列;默认 true。
97
+ */
98
+ showLogTime?: boolean;
95
99
  }
96
100
  /** 日志游标状态 —— 标识内存中日志块的物理边界 */
97
101
  export interface LogCursor {
@@ -1,11 +1,13 @@
1
+ import type { GroupData } from '../types';
1
2
  /**
2
- * 全局连续行号(隐藏各分组首尾边界行后,从 1 递增)。
3
+ * 流水线分组场景下的左侧行号。
3
4
  *
4
- * 对非边界行 R:序号 = 1 + |{ r | pipelineMinRow ≤ r < R 且 r 不在 edgeRows }|
5
- * 即:absoluteRow - pipelineMinRow - (落在 [pipelineMinRow, absoluteRow) 内的边界行个数) + 1
5
+ * 规则(与产品约定一致):
6
+ * - 每个分组在日志流中有 START / END 两行控制标识,各占一个绝对行号。
7
+ * - 第一个分组(groups[0]):非控制行展示 **绝对行号** `absoluteRow`。
8
+ * - 第 n 个分组(n = groups 下标,且 n ≥ 1):非控制行展示 `absoluteRow - (2n)`, 不是 2n -1 是因为第一个分组第1行才是有效日志,把整体行号往前推了1。
9
+ * - START/END 控制行:左侧不展示数字(返回 null)。
6
10
  *
7
- * pipelineMinRow 取各分组 start 的最小值,作为整条流水日志的起算行。
8
- *
9
- * @returns null 表示当前行为分组首尾占位行,左侧不展示数字;undefined 表示早于流水线起点,沿用绝对行号
11
+ * @returns null = 控制行不占位数字;undefined = 无法归类或早于 pipeline 起点,由 LogRow 回退为 absoluteRow
10
12
  */
11
- export declare function getGlobalVisibleLineNumberLabel(absoluteRow: number, edgeRows: Set<number>, pipelineMinRow: number): number | null | undefined;
13
+ export declare function getPipelineGroupedLineNumberLabel(absoluteRow: number, group: GroupData | undefined, groupIndex: number, isControlMarkerRow: boolean, pipelineMinRow: number): number | null | undefined;
@@ -8,7 +8,9 @@ export type BoundaryScrollResult = {
8
8
  };
9
9
  /**
10
10
  * 滑动窗口 totalCount 不变时,Virtuoso 不会自动补偿 scrollTop。
11
- * 用像素粗调 + scrollIntoView 对齐到锚点行(与 loadPrev/loadNext 对称)。
11
+ * - prepend 模式:像素粗调 + scrollIntoView 对齐到锚点行。
12
+ * - appendHeadSlice 模式:仅用当前实时 scrollTop 减去截断行的像素偏移量。
13
+ * 不做锚点行定位,避免用户在 API 请求期间继续滚动时被强行拉回旧位置(跳屏)。
12
14
  */
13
15
  export declare function applyBoundaryScrollCompensation(v: VirtuosoHandle, anchorRow: number, rowsWithAbsolute: Array<{
14
16
  absoluteRow: number;
@@ -19,4 +21,9 @@ export declare function applyBoundaryScrollCompensation(v: VirtuosoHandle, ancho
19
21
  scrollTopBefore: number;
20
22
  defaultItemHeight: number;
21
23
  mode: BoundaryScrollMode;
24
+ /**
25
+ * appendHeadSlice 专用:当前实时 scrollTop(从 scroller DOM 读取)。
26
+ * 优先于 scrollTopBefore 使用,避免请求期间用户继续滚动导致补偿基准过时。
27
+ */
28
+ currentScrollTop?: number;
22
29
  }): BoundaryScrollResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-terminal-viewer-cicd",
3
- "version": "3.0.0-beta.43",
3
+ "version": "3.0.0-beta.45",
4
4
  "author": "https://gitee.com/gitee-frontend",
5
5
  "license": "MIT",
6
6
  "keywords": [