claude-opencode-viewer 2.6.47 → 2.6.49

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/index-pc.html CHANGED
@@ -410,6 +410,25 @@
410
410
  animation: loading-dots 1.2s steps(4, end) infinite;
411
411
  }
412
412
  #init-overlay.visible { display: flex; }
413
+ #reconnect-overlay {
414
+ display: none;
415
+ position: absolute;
416
+ top: 0; left: 0; right: 0; bottom: 0;
417
+ background: rgba(10, 10, 10, 0.85);
418
+ color: #f59e0b;
419
+ align-items: center;
420
+ justify-content: center;
421
+ font-size: 16px;
422
+ font-weight: 600;
423
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
424
+ letter-spacing: 1px;
425
+ z-index: 10;
426
+ }
427
+ #reconnect-overlay::after {
428
+ content: '';
429
+ animation: loading-dots 1.2s steps(4, end) infinite;
430
+ }
431
+ #reconnect-overlay.visible { display: flex; }
413
432
 
414
433
 
415
434
  /* 选择模式:原位文本层 */
@@ -721,6 +740,39 @@
721
740
  line-height: 1.6;
722
741
  color: #d4d4d4;
723
742
  }
743
+ .docs-md-content {
744
+ font-size: 13px; line-height: 1.7; color: #d4d4d4; word-break: break-word;
745
+ }
746
+ .docs-md-content h1, .docs-md-content h2, .docs-md-content h3 {
747
+ margin: 16px 0 8px; color: #e0e0e0; border-bottom: 1px solid #333; padding-bottom: 4px;
748
+ }
749
+ .docs-md-content h1 { font-size: 1.4em; }
750
+ .docs-md-content h2 { font-size: 1.2em; }
751
+ .docs-md-content h3 { font-size: 1.05em; }
752
+ .docs-md-content p { margin: 8px 0; }
753
+ .docs-md-content pre {
754
+ background: #1a1a1a; border: 1px solid #333; border-radius: 4px;
755
+ padding: 10px; overflow-x: auto; margin: 8px 0; white-space: pre-wrap; word-break: break-word;
756
+ }
757
+ .docs-md-content code {
758
+ background: #1a1a1a; padding: 1px 4px; border-radius: 3px; font-size: 12px;
759
+ font-family: Menlo, Monaco, monospace;
760
+ }
761
+ .docs-md-content pre code { background: none; padding: 0; }
762
+ .docs-md-content ul, .docs-md-content ol { margin: 8px 0; padding-left: 20px; }
763
+ .docs-md-content li { margin: 4px 0; }
764
+ .docs-md-content blockquote {
765
+ border-left: 3px solid #444; margin: 8px 0; padding: 4px 12px; color: #999;
766
+ }
767
+ .docs-md-content table { border-collapse: collapse; margin: 8px 0; width: 100%; }
768
+ .docs-md-content th, .docs-md-content td {
769
+ border: 1px solid #333; padding: 6px 10px; text-align: left;
770
+ }
771
+ .docs-md-content th { background: #1a1a1a; }
772
+ .docs-md-content a { color: #58a6ff; text-decoration: none; }
773
+ .docs-md-content a:hover { text-decoration: underline; }
774
+ .docs-md-content img { max-width: 100%; }
775
+ .docs-md-content hr { border: none; border-top: 1px solid #333; margin: 12px 0; }
724
776
  .docs-placeholder {
725
777
  flex: 1;
726
778
  display: flex;
@@ -786,6 +838,13 @@
786
838
  color: #ffa198;
787
839
  }
788
840
 
841
+ .diff-sign {
842
+ display: inline-block;
843
+ width: 12px;
844
+ font-weight: bold;
845
+ user-select: none;
846
+ }
847
+
789
848
  .diff-line-hunk {
790
849
  background: rgba(56, 139, 253, 0.1);
791
850
  }
@@ -1038,6 +1097,7 @@
1038
1097
  <div id="terminal" style="position:relative;">
1039
1098
  <div id="switch-overlay">正在切换</div>
1040
1099
  <div id="init-overlay"></div>
1100
+ <div id="reconnect-overlay">连接断开,正在重连</div>
1041
1101
  <div id="select-text-layer">
1042
1102
  <div id="select-hint">长按选择文本 · 点右上角 ✕ 返回终端</div>
1043
1103
  <pre id="select-text-pre"></pre>
@@ -1051,6 +1111,8 @@
1051
1111
  <div id="copy-toast">已复制</div>
1052
1112
  <script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
1053
1113
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0.18.0/lib/addon-webgl.min.js"></script>
1114
+ <script src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script>
1115
+ <script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.min.js"></script>
1054
1116
  <script>
1055
1117
  (function() {
1056
1118
  var isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
@@ -1059,6 +1121,9 @@
1059
1121
  var fontSize = isMobile ? 11 : 13;
1060
1122
  var currentMode = 'claude';
1061
1123
  var isTransitioning = false;
1124
+ var transitionEndTimer = null;
1125
+ var waitingInitData = false;
1126
+ var initDataTimer = null;
1062
1127
  var isBufferReplay = true; // 初始缓冲区回放中,不弹 toast
1063
1128
  var startupDialogShown = false;
1064
1129
 
@@ -1640,16 +1705,16 @@
1640
1705
 
1641
1706
  ws.onopen = function() {
1642
1707
  isBufferReplay = true;
1708
+ document.getElementById('reconnect-overlay').classList.remove('visible');
1643
1709
  resize();
1644
1710
  rebindTouchScroll();
1645
- setTimeout(function() { isBufferReplay = false; }, 500);
1711
+ setTimeout(function() { isBufferReplay = false; }, 2000);
1646
1712
  // 不在这里初始化,等 state 消息判断是否需要弹对话框
1647
1713
  };
1648
1714
 
1649
1715
  ws.onclose = function() {
1650
1716
  ws = null;
1651
- term.reset();
1652
- term.write('\r\n \x1b[33m连接断开,正在重连...\x1b[0m\r\n');
1717
+ document.getElementById('reconnect-overlay').classList.add('visible');
1653
1718
  setTimeout(connect, 2000);
1654
1719
  };
1655
1720
 
@@ -1657,7 +1722,23 @@
1657
1722
  try {
1658
1723
  var msg = JSON.parse(e.data);
1659
1724
  if (msg.type === 'data') {
1660
- if (!isCreatingNewSession && !isTransitioning) {
1725
+ if (isTransitioning) {
1726
+ // 模式切换:TUI 渲染会发送一连串 data,debounce 等稳定后移除覆盖层
1727
+ term.write(msg.data);
1728
+ clearTimeout(transitionEndTimer);
1729
+ transitionEndTimer = setTimeout(function() {
1730
+ terminalEl.classList.remove('transitioning');
1731
+ isTransitioning = false;
1732
+ }, 2000);
1733
+ } else if (waitingInitData) {
1734
+ // 启动/恢复:同样 debounce 等 TUI 渲染完再隐藏启动覆盖层
1735
+ throttledWrite(msg.data);
1736
+ clearTimeout(initDataTimer);
1737
+ initDataTimer = setTimeout(function() {
1738
+ hideInitOverlay();
1739
+ waitingInitData = false;
1740
+ }, 2000);
1741
+ } else if (!isCreatingNewSession) {
1661
1742
  throttledWrite(msg.data);
1662
1743
  }
1663
1744
  }
@@ -1671,7 +1752,9 @@
1671
1752
  if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
1672
1753
  writeBuffer = '';
1673
1754
  term.reset();
1674
- endTransition(msg.mode);
1755
+ // 更新模式但保留覆盖层,等首条 data 到达后再移除
1756
+ currentMode = msg.mode;
1757
+ modeSelect.value = msg.mode;
1675
1758
  if (msg.buffer) {
1676
1759
  term.write(msg.buffer);
1677
1760
  }
@@ -1683,6 +1766,11 @@
1683
1766
  term.clear();
1684
1767
  }
1685
1768
  else if (msg.type === 'state') {
1769
+ // 重连时清掉旧终端内容,防止重复叠加
1770
+ if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
1771
+ writeBuffer = '';
1772
+ term.reset();
1773
+ term.clear();
1686
1774
  if (msg.mode) {
1687
1775
  currentMode = msg.mode;
1688
1776
  modeSelect.value = msg.mode;
@@ -1698,7 +1786,8 @@
1698
1786
  }
1699
1787
  }
1700
1788
  else if (msg.type === 'restored') {
1701
- hideInitOverlay();
1789
+ // 不立即隐藏覆盖层,等 data debounce 后隐藏
1790
+ waitingInitData = true;
1702
1791
  if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
1703
1792
  writeBuffer = '';
1704
1793
  term.reset();
@@ -1711,12 +1800,13 @@
1711
1800
  term.write('恢复失败: ' + msg.error + '\r\n');
1712
1801
  }
1713
1802
  else if (msg.type === 'started') {
1714
- hideInitOverlay();
1803
+ // 不立即隐藏覆盖层,等 data debounce 后隐藏
1804
+ waitingInitData = true;
1715
1805
  rebindTouchScroll();
1716
1806
  preloadData();
1717
1807
  }
1718
1808
  else if (msg.type === 'new-session-ok') {
1719
- hideInitOverlay();
1809
+ waitingInitData = true;
1720
1810
  if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
1721
1811
  writeBuffer = '';
1722
1812
  term.reset();
@@ -1739,7 +1829,7 @@
1739
1829
  } else {
1740
1830
  cacheRestored = true;
1741
1831
  }
1742
- }, 800);
1832
+ }, 2000);
1743
1833
  }
1744
1834
 
1745
1835
  window.addEventListener('resize', resize);
@@ -1908,7 +1998,7 @@
1908
1998
  setTimeout(function() {
1909
1999
  sessions = sessions.filter(function(s) { return s.id !== sessionId; });
1910
2000
  renderSessions();
1911
- }, 200);
2001
+ }, 2000);
1912
2002
  } else {
1913
2003
  itemEl.style.opacity = '';
1914
2004
  itemEl.style.pointerEvents = '';
@@ -2108,7 +2198,7 @@
2108
2198
  itemEl.style.padding = '0 12px';
2109
2199
  itemEl.style.margin = '0';
2110
2200
  itemEl.style.opacity = '0';
2111
- setTimeout(function() { itemEl.remove(); }, 200);
2201
+ setTimeout(function() { itemEl.remove(); }, 2000);
2112
2202
  } else {
2113
2203
  itemEl.style.opacity = '';
2114
2204
  itemEl.style.pointerEvents = '';
@@ -2389,38 +2479,29 @@
2389
2479
 
2390
2480
  function loadDiffContent(file) {
2391
2481
  var area = document.getElementById('git-diff-content-area');
2392
- area.innerHTML = '<div class="git-diff-loading">加载 diff...</div>';
2393
-
2394
- fetch(basePath + '/api/git-diff?files=' + encodeURIComponent(file))
2395
- .then(function(r) { return r.json(); })
2396
- .then(function(data) {
2397
- if (!data.diffs || !data.diffs[0]) {
2398
- area.innerHTML = '<div class="git-diff-error">无 diff 数据</div>';
2399
- return;
2400
- }
2401
- var d = data.diffs[0];
2402
- var html = '<div class="git-diff-content-header">';
2403
- html += '<span class="git-diff-content-path">' + escapeHtml(d.file) + '</span>';
2404
- html += '<span class="git-diff-badge">' + (d.is_new ? 'NEW' : d.is_deleted ? 'DEL' : 'DIFF') + '</span>';
2405
- html += '</div>';
2406
- html += '<div class="git-diff-content-scroll">';
2407
-
2408
- if (d.is_binary) {
2409
- html += '<div class="git-diff-loading" style="font-style:italic;">二进制文件</div>';
2410
- } else if (d.is_large) {
2411
- html += '<div class="git-diff-loading" style="color:#e2c08d;">文件过大: ' + (d.size / (1024 * 1024)).toFixed(2) + ' MB</div>';
2412
- } else if (d.unified_diff) {
2413
- html += renderUnifiedDiff(d.unified_diff);
2414
- } else {
2415
- html += '<div class="git-diff-loading" style="color:#666;">无变更内容</div>';
2416
- }
2482
+ var d = diffChanges.find(function(c) { return c.file === file; });
2483
+ if (!d) {
2484
+ area.innerHTML = '<div class="git-diff-error">无 diff 数据</div>';
2485
+ return;
2486
+ }
2487
+ var html = '<div class="git-diff-content-header">';
2488
+ html += '<span class="git-diff-content-path">' + escapeHtml(d.file) + '</span>';
2489
+ html += '<span class="git-diff-badge">' + (d.is_new ? 'NEW' : d.is_deleted ? 'DEL' : 'DIFF') + '</span>';
2490
+ html += '</div>';
2491
+ html += '<div class="git-diff-content-scroll">';
2492
+
2493
+ if (d.is_binary) {
2494
+ html += '<div class="git-diff-loading" style="font-style:italic;">二进制文件</div>';
2495
+ } else if (d.is_large) {
2496
+ html += '<div class="git-diff-loading" style="color:#e2c08d;">文件过大: ' + (d.size / (1024 * 1024)).toFixed(2) + ' MB</div>';
2497
+ } else if (d.unified_diff) {
2498
+ html += renderUnifiedDiff(d.unified_diff);
2499
+ } else {
2500
+ html += '<div class="git-diff-loading" style="color:#666;">无变更内容</div>';
2501
+ }
2417
2502
 
2418
- html += '</div>';
2419
- area.innerHTML = html;
2420
- })
2421
- .catch(function(err) {
2422
- area.innerHTML = '<div class="git-diff-error">加载失败: ' + escapeHtml(err.message) + '</div>';
2423
- });
2503
+ html += '</div>';
2504
+ area.innerHTML = html;
2424
2505
  }
2425
2506
 
2426
2507
  function renderUnifiedDiff(diffText) {
@@ -2453,13 +2534,13 @@
2453
2534
  html += '<tr class="diff-line diff-line-add">';
2454
2535
  html += '<td class="diff-line-num"></td>';
2455
2536
  html += '<td class="diff-line-num">' + newLine + '</td>';
2456
- html += '<td class="diff-line-content">' + escapeHtml(line.substring(1)) + '</td></tr>';
2537
+ html += '<td class="diff-line-content"><span class="diff-sign">+</span>' + escapeHtml(line.substring(1)) + '</td></tr>';
2457
2538
  newLine++;
2458
2539
  } else if (line.startsWith('-')) {
2459
2540
  html += '<tr class="diff-line diff-line-del">';
2460
2541
  html += '<td class="diff-line-num">' + oldLine + '</td>';
2461
2542
  html += '<td class="diff-line-num"></td>';
2462
- html += '<td class="diff-line-content">' + escapeHtml(line.substring(1)) + '</td></tr>';
2543
+ html += '<td class="diff-line-content"><span class="diff-sign">-</span>' + escapeHtml(line.substring(1)) + '</td></tr>';
2463
2544
  oldLine++;
2464
2545
  } else if (line.startsWith(' ') || (line === '' && i < lines.length - 1)) {
2465
2546
  html += '<tr class="diff-line">';
@@ -2565,6 +2646,15 @@
2565
2646
  });
2566
2647
  }
2567
2648
 
2649
+ function formatDocContent(text) {
2650
+ if (!text) return '';
2651
+ try {
2652
+ return DOMPurify.sanitize(marked.parse(text, { breaks: true }));
2653
+ } catch (e) {
2654
+ return '<pre>' + escapeHtml(text) + '</pre>';
2655
+ }
2656
+ }
2657
+
2568
2658
  function loadDocContent(file) {
2569
2659
  var area = document.getElementById('docs-content-area');
2570
2660
  area.innerHTML = '<div class="docs-loading">加载中...</div>';
@@ -2575,7 +2665,7 @@
2575
2665
  area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">' + escapeHtml(data.error) + '</div>';
2576
2666
  return;
2577
2667
  }
2578
- area.innerHTML = '<pre>' + escapeHtml(data.content) + '</pre>';
2668
+ area.innerHTML = '<div class="docs-md-content">' + formatDocContent(data.content) + '</div>';
2579
2669
  })
2580
2670
  .catch(function(err) {
2581
2671
  area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">加载失败</div>';
package/index.html CHANGED
@@ -809,6 +809,39 @@
809
809
  line-height: 1.6;
810
810
  color: #d4d4d4;
811
811
  }
812
+ .docs-md-content {
813
+ font-size: 13px; line-height: 1.7; color: #d4d4d4; word-break: break-word;
814
+ }
815
+ .docs-md-content h1, .docs-md-content h2, .docs-md-content h3 {
816
+ margin: 16px 0 8px; color: #e0e0e0; border-bottom: 1px solid #333; padding-bottom: 4px;
817
+ }
818
+ .docs-md-content h1 { font-size: 1.4em; }
819
+ .docs-md-content h2 { font-size: 1.2em; }
820
+ .docs-md-content h3 { font-size: 1.05em; }
821
+ .docs-md-content p { margin: 8px 0; }
822
+ .docs-md-content pre {
823
+ background: #1a1a1a; border: 1px solid #333; border-radius: 4px;
824
+ padding: 10px; overflow-x: auto; margin: 8px 0; white-space: pre-wrap; word-break: break-word;
825
+ }
826
+ .docs-md-content code {
827
+ background: #1a1a1a; padding: 1px 4px; border-radius: 3px; font-size: 12px;
828
+ font-family: Menlo, Monaco, monospace;
829
+ }
830
+ .docs-md-content pre code { background: none; padding: 0; }
831
+ .docs-md-content ul, .docs-md-content ol { margin: 8px 0; padding-left: 20px; }
832
+ .docs-md-content li { margin: 4px 0; }
833
+ .docs-md-content blockquote {
834
+ border-left: 3px solid #444; margin: 8px 0; padding: 4px 12px; color: #999;
835
+ }
836
+ .docs-md-content table { border-collapse: collapse; margin: 8px 0; width: 100%; }
837
+ .docs-md-content th, .docs-md-content td {
838
+ border: 1px solid #333; padding: 6px 10px; text-align: left;
839
+ }
840
+ .docs-md-content th { background: #1a1a1a; }
841
+ .docs-md-content a { color: #58a6ff; text-decoration: none; }
842
+ .docs-md-content a:hover { text-decoration: underline; }
843
+ .docs-md-content img { max-width: 100%; }
844
+ .docs-md-content hr { border: none; border-top: 1px solid #333; margin: 12px 0; }
812
845
  .docs-placeholder {
813
846
  flex: 1;
814
847
  display: flex;
@@ -874,6 +907,13 @@
874
907
  color: #ffa198;
875
908
  }
876
909
 
910
+ .diff-sign {
911
+ display: inline-block;
912
+ width: 12px;
913
+ font-weight: bold;
914
+ user-select: none;
915
+ }
916
+
877
917
  .diff-line-hunk {
878
918
  background: rgba(56, 139, 253, 0.1);
879
919
  }
@@ -1120,6 +1160,7 @@
1120
1160
  var fontSize = isMobile ? 11 : 13;
1121
1161
  var currentMode = 'claude';
1122
1162
  var isTransitioning = false;
1163
+ var transitionEndTimer = null;
1123
1164
  var mobileInitSent = false;
1124
1165
 
1125
1166
  var term = new Terminal({
@@ -1681,7 +1722,14 @@
1681
1722
  var msg = JSON.parse(e.data);
1682
1723
  if (msg.type === 'data') {
1683
1724
  hideLoading();
1684
- if (!isCreatingNewSession && !isTransitioning) {
1725
+ if (isTransitioning) {
1726
+ term.write(msg.data);
1727
+ clearTimeout(transitionEndTimer);
1728
+ transitionEndTimer = setTimeout(function() {
1729
+ terminalEl.classList.remove('transitioning');
1730
+ isTransitioning = false;
1731
+ }, 2000);
1732
+ } else if (!isCreatingNewSession) {
1685
1733
  throttledWrite(msg.data);
1686
1734
  }
1687
1735
  }
@@ -1737,7 +1785,8 @@
1737
1785
  if (writeTimer) { cancelAnimationFrame(writeTimer); writeTimer = null; }
1738
1786
  writeBuffer = '';
1739
1787
  term.reset();
1740
- endTransition(msg.mode);
1788
+ currentMode = msg.mode;
1789
+ modeSelect.value = msg.mode;
1741
1790
  if (msg.buffer) {
1742
1791
  term.write(msg.buffer);
1743
1792
  }
@@ -1792,7 +1841,7 @@
1792
1841
  } else {
1793
1842
  cacheRestored = true;
1794
1843
  }
1795
- }, 800);
1844
+ }, 2000);
1796
1845
  }
1797
1846
 
1798
1847
  window.addEventListener('resize', resize);
@@ -2031,7 +2080,7 @@
2031
2080
  setTimeout(function() {
2032
2081
  sessions = sessions.filter(function(s) { return s.id !== sessionId; });
2033
2082
  renderSessions();
2034
- }, 200);
2083
+ }, 2000);
2035
2084
  } else {
2036
2085
  itemEl.style.opacity = '';
2037
2086
  itemEl.style.pointerEvents = '';
@@ -2234,7 +2283,7 @@
2234
2283
  itemEl.style.padding = '0 12px';
2235
2284
  itemEl.style.margin = '0';
2236
2285
  itemEl.style.opacity = '0';
2237
- setTimeout(function() { itemEl.remove(); }, 200);
2286
+ setTimeout(function() { itemEl.remove(); }, 2000);
2238
2287
  } else {
2239
2288
  itemEl.style.opacity = '';
2240
2289
  itemEl.style.pointerEvents = '';
@@ -2624,38 +2673,29 @@
2624
2673
 
2625
2674
  function loadDiffContent(file) {
2626
2675
  var area = document.getElementById('git-diff-content-area');
2627
- area.innerHTML = '<div class="git-diff-loading">加载 diff...</div>';
2628
-
2629
- fetch(basePath + '/api/git-diff?files=' + encodeURIComponent(file))
2630
- .then(function(r) { return r.json(); })
2631
- .then(function(data) {
2632
- if (!data.diffs || !data.diffs[0]) {
2633
- area.innerHTML = '<div class="git-diff-error">无 diff 数据</div>';
2634
- return;
2635
- }
2636
- var d = data.diffs[0];
2637
- var html = '<div class="git-diff-content-header">';
2638
- html += '<span class="git-diff-content-path">' + escapeHtml(d.file) + '</span>';
2639
- html += '<span class="git-diff-badge">' + (d.is_new ? 'NEW' : d.is_deleted ? 'DEL' : 'DIFF') + '</span>';
2640
- html += '</div>';
2641
- html += '<div class="git-diff-content-scroll">';
2642
-
2643
- if (d.is_binary) {
2644
- html += '<div class="git-diff-loading" style="font-style:italic;">二进制文件</div>';
2645
- } else if (d.is_large) {
2646
- html += '<div class="git-diff-loading" style="color:#e2c08d;">文件过大: ' + (d.size / (1024 * 1024)).toFixed(2) + ' MB</div>';
2647
- } else if (d.unified_diff) {
2648
- html += renderUnifiedDiff(d.unified_diff);
2649
- } else {
2650
- html += '<div class="git-diff-loading" style="color:#666;">无变更内容</div>';
2651
- }
2676
+ var d = diffChanges.find(function(c) { return c.file === file; });
2677
+ if (!d) {
2678
+ area.innerHTML = '<div class="git-diff-error">无 diff 数据</div>';
2679
+ return;
2680
+ }
2681
+ var html = '<div class="git-diff-content-header">';
2682
+ html += '<span class="git-diff-content-path">' + escapeHtml(d.file) + '</span>';
2683
+ html += '<span class="git-diff-badge">' + (d.is_new ? 'NEW' : d.is_deleted ? 'DEL' : 'DIFF') + '</span>';
2684
+ html += '</div>';
2685
+ html += '<div class="git-diff-content-scroll">';
2686
+
2687
+ if (d.is_binary) {
2688
+ html += '<div class="git-diff-loading" style="font-style:italic;">二进制文件</div>';
2689
+ } else if (d.is_large) {
2690
+ html += '<div class="git-diff-loading" style="color:#e2c08d;">文件过大: ' + (d.size / (1024 * 1024)).toFixed(2) + ' MB</div>';
2691
+ } else if (d.unified_diff) {
2692
+ html += renderUnifiedDiff(d.unified_diff);
2693
+ } else {
2694
+ html += '<div class="git-diff-loading" style="color:#666;">无变更内容</div>';
2695
+ }
2652
2696
 
2653
- html += '</div>';
2654
- area.innerHTML = html;
2655
- })
2656
- .catch(function(err) {
2657
- area.innerHTML = '<div class="git-diff-error">加载失败: ' + escapeHtml(err.message) + '</div>';
2658
- });
2697
+ html += '</div>';
2698
+ area.innerHTML = html;
2659
2699
  }
2660
2700
 
2661
2701
  function renderUnifiedDiff(diffText) {
@@ -2688,13 +2728,13 @@
2688
2728
  html += '<tr class="diff-line diff-line-add">';
2689
2729
  html += '<td class="diff-line-num"></td>';
2690
2730
  html += '<td class="diff-line-num">' + newLine + '</td>';
2691
- html += '<td class="diff-line-content">' + escapeHtml(line.substring(1)) + '</td></tr>';
2731
+ html += '<td class="diff-line-content"><span class="diff-sign">+</span>' + escapeHtml(line.substring(1)) + '</td></tr>';
2692
2732
  newLine++;
2693
2733
  } else if (line.startsWith('-')) {
2694
2734
  html += '<tr class="diff-line diff-line-del">';
2695
2735
  html += '<td class="diff-line-num">' + oldLine + '</td>';
2696
2736
  html += '<td class="diff-line-num"></td>';
2697
- html += '<td class="diff-line-content">' + escapeHtml(line.substring(1)) + '</td></tr>';
2737
+ html += '<td class="diff-line-content"><span class="diff-sign">-</span>' + escapeHtml(line.substring(1)) + '</td></tr>';
2698
2738
  oldLine++;
2699
2739
  } else if (line.startsWith(' ') || (line === '' && i < lines.length - 1)) {
2700
2740
  html += '<tr class="diff-line">';
@@ -2802,6 +2842,15 @@
2802
2842
  });
2803
2843
  }
2804
2844
 
2845
+ function formatDocContent(text) {
2846
+ if (!text) return '';
2847
+ try {
2848
+ return DOMPurify.sanitize(marked.parse(text, { breaks: true }));
2849
+ } catch (e) {
2850
+ return '<pre>' + escapeHtml(text) + '</pre>';
2851
+ }
2852
+ }
2853
+
2805
2854
  function loadDocContent(file) {
2806
2855
  var area = document.getElementById('docs-content-area');
2807
2856
  area.innerHTML = '<div class="docs-loading">加载中...</div>';
@@ -2812,7 +2861,7 @@
2812
2861
  area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">' + escapeHtml(data.error) + '</div>';
2813
2862
  return;
2814
2863
  }
2815
- area.innerHTML = '<pre>' + escapeHtml(data.content) + '</pre>';
2864
+ area.innerHTML = '<div class="docs-md-content">' + formatDocContent(data.content) + '</div>';
2816
2865
  })
2817
2866
  .catch(function() {
2818
2867
  area.innerHTML = '<div class="docs-loading" style="color:#ff6b6b;">加载失败</div>';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-opencode-viewer",
3
- "version": "2.6.47",
3
+ "version": "2.6.49",
4
4
  "description": "A unified terminal viewer for Claude Code and OpenCode with seamless switching",
5
5
  "type": "module",
6
6
  "main": "server.js",