codex-lens 0.1.24 → 0.1.26
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/dist/aggregator.js +1 -1
- package/dist/public/assets/{main-fEtdvo0X.js → main-BO4694Xj.js} +37 -37
- package/dist/public/assets/{main-CYNmzqDG.css → main-DNXrKVO-.css} +1 -1
- package/dist/public/index.html +2 -2
- package/package.json +1 -1
- package/src/aggregator.js +1 -1
- package/src/components/App.jsx +114 -96
- package/src/components/CodeViewer.jsx +11 -19
- package/src/global.css +10 -6
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
* The original design remains. The terminal itself
|
|
30
30
|
* has been extended to include xterm CSI codes, among
|
|
31
31
|
* other features.
|
|
32
|
-
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--bg-primary: #000000;--bg-secondary: #0A0A0A;--bg-tertiary: #141414;--bg-elevated: #1A1A1A;--bg-surface: #0F0F0F;--text-primary: #F8FAFC;--text-secondary: #94A3B8;--text-muted: #64748B;--border-color: rgba(148, 163, 184, .15);--border-glow: rgba(34, 197, 94, .3);--accent-color: #22C55E;--accent-hover: #16A34A;--accent-glow: rgba(34, 197, 94, .4);--accent-soft: rgba(34, 197, 94, .1);--diff-add-bg: rgba(34, 197, 94, .12);--diff-add-text: #4ADE80;--diff-add-border: #22C55E;--diff-remove-bg: rgba(239, 68, 68, .12);--diff-remove-text: #F87171;--diff-remove-border: #EF4444;--danger-color: #EF4444;--danger-hover: #DC2626;--success-color: #22C55E;--success-hover: #16A34A;--warning-color: #F59E0B;--info-color: #3B82F6;--font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-display: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Consolas", monospace;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .2);--shadow-sm: 0 2px 4px rgba(0, 0, 0, .2);--shadow-md: 0 4px 12px rgba(0, 0, 0, .3);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .4);--shadow-xl: 0 16px 48px rgba(0, 0, 0, .5);--shadow-glow: 0 0 24px var(--accent-glow);--shadow-glow-sm: 0 0 12px var(--accent-glow);--radius-xs: 4px;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 14px;--radius-xl: 20px;--transition-fast: .12s ease;--transition-normal: .2s ease;--transition-slow: .3s ease;--blur-sm: 8px;--blur-md: 16px;--blur-lg: 24px}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}*{margin:0;padding:0;box-sizing:border-box}html,body,#root{height:100%;width:100%;overflow:hidden}body{font-family:var(--font-family);background:var(--bg-primary);color:var(--text-primary);font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app-container{display:flex;flex-direction:column;height:100vh;width:100vw;background:var(--bg-primary)}.top-bar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:linear-gradient(180deg,#1e293bf2,#1e293bd9);-webkit-backdrop-filter:blur(var(--blur-md));backdrop-filter:blur(var(--blur-md));border-bottom:1px solid var(--border-color);min-height:52px;position:relative;z-index:100}.top-bar:after{content:"";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent-color),transparent);opacity:.3}.top-bar-left,.top-bar-center,.top-bar-right{display:flex;align-items:center;gap:16px}.top-bar-left{width:260px}.top-bar-center{flex:1;justify-content:center}.top-bar-right{width:40%;justify-content:flex-end}.top-bar-title{font-family:var(--font-display);font-weight:600;font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:flex;align-items:center;gap:8px}.top-bar-title:before{content:"";display:inline-block;width:3px;height:14px;background:var(--accent-color);border-radius:2px}.main-content{display:flex;flex:1;overflow:hidden;gap:1px;background:var(--border-color)}.panel{display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(180deg,var(--bg-secondary) 0%,rgba(30,41,59,.95) 100%)}.left-panel{width:260px;flex-shrink:0}.middle-panel{flex:1;min-width:300px}.right-panel{width:40%;min-width:300px;max-width:60%}.panel-content{flex:1;overflow:auto;padding:8px}.file-tree{padding:4px}.file-item{padding:8px 12px;cursor:pointer;border-radius:var(--radius-sm);display:flex;align-items:center;gap:10px;transition:all var(--transition-fast);position:relative;margin:2px 0}.file-item:before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:0;background:var(--accent-color);border-radius:0 2px 2px 0;transition:height var(--transition-fast)}.file-item:hover{background:#94a3b814}.file-item:hover:before{height:60%}.file-item:active{transform:scale(.98)}.file-item.active{background:var(--accent-soft);color:var(--text-primary)}.file-item.active:before{height:70%}.file-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:14px;opacity:.9}.file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.diff-line{font-family:var(--font-mono);font-size:13px;padding:3px 16px 3px 20px;white-space:pre;border-left:3px solid transparent;transition:background var(--transition-fast);position:relative}.diff-line.added{background:var(--diff-add-bg);color:var(--diff-add-text);border-left-color:var(--diff-add-border)}.diff-line.removed{background:var(--diff-remove-bg);color:var(--diff-remove-text);border-left-color:var(--diff-remove-border)}.diff-line:before{display:inline-block;width:16px;margin-left:-16px;text-align:center;font-weight:600;font-size:12px}.diff-line.added:before{content:"+";color:var(--diff-add-text)}.diff-line.removed:before{content:"-";color:var(--diff-remove-text)}.tab-bar{display:flex;background:#0f172a99;border-bottom:1px solid var(--border-color);overflow-x:auto;min-height:40px;scrollbar-width:none}.tab-bar::-webkit-scrollbar{display:none}.tab{display:flex;align-items:center;gap:8px;padding:10px 16px;background:transparent;border-right:1px solid var(--border-color);cursor:pointer;min-width:100px;max-width:180px;font-size:13px;color:var(--text-muted);transition:all var(--transition-fast);position:relative}.tab:hover{background:#94a3b80d;color:var(--text-secondary)}.tab.active{background:var(--bg-secondary);color:var(--text-primary)}.tab.active:after{content:"";position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--accent-color)}.tab-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-modified{color:var(--accent-color);font-weight:700;margin-right:2px}.tab-close{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:2px 6px;border-radius:var(--radius-xs);line-height:1;transition:all var(--transition-fast);opacity:.6}.tab-close:hover{background:#ef444433;color:var(--danger-color);opacity:1}.context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:160px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}@keyframes contextMenuIn{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}.context-menu-item{padding:10px 14px;cursor:pointer;font-size:13px;color:var(--text-primary);border-radius:var(--radius-sm);transition:all var(--transition-fast)}.context-menu-item:hover{background:var(--accent-soft);color:var(--accent-color)}.file-context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:200px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}.ws-status{display:inline-block;width:8px;height:8px;border-radius:50%;margin-left:8px;transition:all var(--transition-normal)}.ws-status.connected{background:var(--success-color);box-shadow:0 0 8px var(--success-color);animation:pulse 2s infinite}.ws-status.disconnected{background:var(--danger-color);box-shadow:0 0 8px var(--danger-color)}@keyframes pulse{0%,to{opacity:1;box-shadow:0 0 8px var(--success-color)}50%{opacity:.7;box-shadow:0 0 4px var(--success-color)}}.version-info{display:flex;align-items:center;gap:10px}.version-number{font-size:11px;color:var(--text-muted);font-weight:500;font-family:var(--font-mono);padding:3px 8px;background:#94a3b81a;border-radius:var(--radius-xs)}.update-badge{font-size:10px;padding:4px 10px;background:linear-gradient(135deg,var(--warning-color) 0%,#FBBF24 100%);color:var(--bg-primary);border-radius:var(--radius-sm);cursor:pointer;font-weight:600;transition:all var(--transition-fast)}.update-badge:hover{transform:translateY(-1px);box-shadow:0 4px 12px #f59e0b4d}.terminal-wrapper{flex:1;overflow:hidden;min-height:0}.section{margin-bottom:8px}.section-title{padding:10px 12px 6px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.8px}.empty-state{padding:40px 24px;text-align:center;color:var(--text-muted);font-size:13px;display:flex;flex-direction:column;align-items:center;gap:12px}.empty-state:before{content:"";width:48px;height:48px;background:linear-gradient(135deg,var(--bg-tertiary) 0%,var(--bg-elevated) 100%);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.code-panel{font-family:var(--font-mono)}.code-viewer-codemirror{height:100%;width:100%;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-theme{flex:1;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-editor{flex:1;min-width:0;min-height:0}.code-viewer-codemirror .cm-scroller{flex:1;overflow:auto!important;min-height:0}.code-content{padding:16px;white-space:pre-wrap;word-break:break-all;line-height:1.7;font-size:13px}.diff-container{font-family:var(--font-mono);font-size:13px;padding:8px 0}.task-btn{padding:8px 18px;border:none;border-radius:var(--radius-sm);font-size:12px;font-weight:600;cursor:pointer;transition:all var(--transition-fast);font-family:var(--font-family);display:inline-flex;align-items:center;gap:6px}.task-btn:hover{transform:translateY(-1px)}.task-btn:active{transform:translateY(0)}.task-btn-clear{background:transparent;color:var(--text-secondary);border:1px solid var(--border-color)}.task-btn-clear:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color);box-shadow:var(--shadow-glow-sm)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-elevated);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}::-webkit-scrollbar-corner{background:transparent}@media (max-width: 1024px){.left-panel{width:220px}.right-panel{width:45%;min-width:250px}.top-bar-left{width:220px}}@media (max-width: 768px){.main-content{flex-direction:column}.left-panel,.right-panel{width:100%;max-width:none;min-width:auto}.middle-panel{min-height:300px}.top-bar{flex-wrap:wrap;padding:10px 16px}.top-bar-left,.top-bar-center,.top-bar-right{width:auto}.top-bar-center{order:3;width:100%;justify-content:flex-start;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-color)}}
|
|
32
|
+
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--bg-primary: #000000;--bg-secondary: #0A0A0A;--bg-tertiary: #141414;--bg-elevated: #1A1A1A;--bg-surface: #0F0F0F;--text-primary: #F8FAFC;--text-secondary: #94A3B8;--text-muted: #64748B;--border-color: rgba(148, 163, 184, .15);--border-glow: rgba(34, 197, 94, .3);--accent-color: #22C55E;--accent-hover: #16A34A;--accent-glow: rgba(34, 197, 94, .4);--accent-soft: rgba(34, 197, 94, .1);--diff-add-bg: rgba(34, 197, 94, .12);--diff-add-text: #4ADE80;--diff-add-border: #22C55E;--diff-remove-bg: rgba(239, 68, 68, .12);--diff-remove-text: #F87171;--diff-remove-border: #EF4444;--danger-color: #EF4444;--danger-hover: #DC2626;--success-color: #22C55E;--success-hover: #16A34A;--warning-color: #F59E0B;--info-color: #3B82F6;--font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-display: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Consolas", monospace;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .2);--shadow-sm: 0 2px 4px rgba(0, 0, 0, .2);--shadow-md: 0 4px 12px rgba(0, 0, 0, .3);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .4);--shadow-xl: 0 16px 48px rgba(0, 0, 0, .5);--shadow-glow: 0 0 24px var(--accent-glow);--shadow-glow-sm: 0 0 12px var(--accent-glow);--radius-xs: 4px;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 14px;--radius-xl: 20px;--transition-fast: .12s ease;--transition-normal: .2s ease;--transition-slow: .3s ease;--blur-sm: 8px;--blur-md: 16px;--blur-lg: 24px}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}*{margin:0;padding:0;box-sizing:border-box}html,body,#root{height:100%;width:100%;overflow:hidden}body{font-family:var(--font-family);background:var(--bg-primary);color:var(--text-primary);font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app-container{display:flex;flex-direction:column;height:100vh;width:100vw;background:var(--bg-primary)}.top-bar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:linear-gradient(180deg,#1e293bf2,#1e293bd9);-webkit-backdrop-filter:blur(var(--blur-md));backdrop-filter:blur(var(--blur-md));border-bottom:1px solid var(--border-color);min-height:52px;position:relative;z-index:100}.top-bar:after{content:"";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent-color),transparent);opacity:.3}.top-bar-left,.top-bar-center,.top-bar-right{display:flex;align-items:center;gap:16px}.top-bar-left{width:260px}.top-bar-center{flex:1;justify-content:center}.top-bar-right{width:40%;justify-content:flex-end}.top-bar-title{font-family:var(--font-display);font-weight:600;font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:flex;align-items:center;gap:8px}.top-bar-title:before{content:"";display:inline-block;width:3px;height:14px;background:var(--accent-color);border-radius:2px}.main-content{display:flex;flex:1;overflow:hidden;gap:1px;background:var(--border-color)}.panel{display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(180deg,var(--bg-secondary) 0%,rgba(30,41,59,.95) 100%)}.left-panel{width:260px;flex-shrink:0}.middle-panel{flex:1;min-width:300px}.right-panel{width:40%;min-width:300px;max-width:60%}.panel-content{flex:1;overflow:auto;padding:8px}.file-tree{padding:4px}.file-item{padding:8px 12px;cursor:pointer;border-radius:var(--radius-sm);display:flex;align-items:center;gap:10px;transition:all var(--transition-fast);position:relative;margin:2px 0}.file-item:before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:0;background:var(--accent-color);border-radius:0 2px 2px 0;transition:height var(--transition-fast)}.file-item:hover{background:#94a3b814}.file-item:hover:before{height:60%}.file-item:active{transform:scale(.98)}.file-item.active{background:var(--accent-soft);color:var(--text-primary)}.file-item.active:before{height:70%}.file-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:14px;opacity:.9}.file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.diff-line{font-family:var(--font-mono);font-size:13px;padding:3px 16px 3px 20px;white-space:pre;border-left:3px solid transparent;transition:background var(--transition-fast);position:relative}.diff-line.added{background:var(--diff-add-bg);color:var(--diff-add-text);border-left-color:var(--diff-add-border)}.diff-line.removed{background:var(--diff-remove-bg);color:var(--diff-remove-text);border-left-color:var(--diff-remove-border)}.diff-line:before{display:inline-block;width:16px;margin-left:-16px;text-align:center;font-weight:600;font-size:12px}.diff-line.added:before{content:"+";color:var(--diff-add-text)}.diff-line.removed:before{content:"-";color:var(--diff-remove-text)}.tab-bar{display:flex;background:#0f172a99;border-bottom:1px solid var(--border-color);overflow-x:auto;min-height:40px;scrollbar-width:none}.tab-bar::-webkit-scrollbar{display:none}.tab{display:flex;align-items:center;gap:8px;padding:10px 16px;background:transparent;border-right:1px solid var(--border-color);cursor:pointer;min-width:100px;max-width:180px;font-size:13px;color:var(--text-muted);transition:all var(--transition-fast);position:relative}.tab:hover{background:#94a3b80d;color:var(--text-secondary)}.tab.active{background:var(--bg-secondary);color:var(--text-primary)}.tab.active:after{content:"";position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--accent-color)}.tab-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:2px 6px;border-radius:var(--radius-xs);line-height:1;transition:all var(--transition-fast);opacity:.6}.tab-close:hover{background:#ef444433;color:var(--danger-color);opacity:1}.tab.modified .tab-name{color:var(--accent-color)}.tab-modified-mark{color:var(--accent-color);margin-right:4px;font-size:10px}.context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:160px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}@keyframes contextMenuIn{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}.context-menu-item{padding:10px 14px;cursor:pointer;font-size:13px;color:var(--text-primary);border-radius:var(--radius-sm);transition:all var(--transition-fast)}.context-menu-item:hover{background:var(--accent-soft);color:var(--accent-color)}.file-context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:200px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}.ws-status{display:inline-block;width:8px;height:8px;border-radius:50%;margin-left:8px;transition:all var(--transition-normal)}.ws-status.connected{background:var(--success-color);box-shadow:0 0 8px var(--success-color);animation:pulse 2s infinite}.ws-status.disconnected{background:var(--danger-color);box-shadow:0 0 8px var(--danger-color)}@keyframes pulse{0%,to{opacity:1;box-shadow:0 0 8px var(--success-color)}50%{opacity:.7;box-shadow:0 0 4px var(--success-color)}}.version-info{display:flex;align-items:center;gap:10px}.version-number{font-size:11px;color:var(--text-muted);font-weight:500;font-family:var(--font-mono);padding:3px 8px;background:#94a3b81a;border-radius:var(--radius-xs)}.update-badge{font-size:10px;padding:4px 10px;background:linear-gradient(135deg,var(--warning-color) 0%,#FBBF24 100%);color:var(--bg-primary);border-radius:var(--radius-sm);cursor:pointer;font-weight:600;transition:all var(--transition-fast)}.update-badge:hover{transform:translateY(-1px);box-shadow:0 4px 12px #f59e0b4d}.terminal-wrapper{flex:1;overflow:hidden;min-height:0}.section{margin-bottom:8px}.section-title{padding:10px 12px 6px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.8px}.empty-state{padding:40px 24px;text-align:center;color:var(--text-muted);font-size:13px;display:flex;flex-direction:column;align-items:center;gap:12px}.empty-state:before{content:"";width:48px;height:48px;background:linear-gradient(135deg,var(--bg-tertiary) 0%,var(--bg-elevated) 100%);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.code-panel{font-family:var(--font-mono)}.code-viewer-codemirror{height:100%;width:100%;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-theme{flex:1;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-editor{flex:1;min-width:0;min-height:0}.code-viewer-codemirror .cm-scroller{flex:1;overflow:auto!important;min-height:0}.code-content{padding:16px;white-space:pre-wrap;word-break:break-all;line-height:1.7;font-size:13px}.diff-container{font-family:var(--font-mono);font-size:13px;padding:8px 0}.task-btn{padding:8px 18px;border:none;border-radius:var(--radius-sm);font-size:12px;font-weight:600;cursor:pointer;transition:all var(--transition-fast);font-family:var(--font-family);display:inline-flex;align-items:center;gap:6px}.task-btn:hover{transform:translateY(-1px)}.task-btn:active{transform:translateY(0)}.task-btn-clear{background:transparent;color:var(--text-secondary);border:1px solid var(--border-color)}.task-btn-clear:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color);box-shadow:var(--shadow-glow-sm)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-elevated);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}::-webkit-scrollbar-corner{background:transparent}@media (max-width: 1024px){.left-panel{width:220px}.right-panel{width:45%;min-width:250px}.top-bar-left{width:220px}}@media (max-width: 768px){.main-content{flex-direction:column}.left-panel,.right-panel{width:100%;max-width:none;min-width:auto}.middle-panel{min-height:300px}.top-bar{flex-wrap:wrap;padding:10px 16px}.top-bar-left,.top-bar-center,.top-bar-right{width:auto}.top-bar-center{order:3;width:100%;justify-content:flex-start;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-color)}}
|
package/dist/public/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Codex Lens</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/main-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/main-
|
|
7
|
+
<script type="module" crossorigin src="./assets/main-BO4694Xj.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/main-DNXrKVO-.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/aggregator.js
CHANGED
|
@@ -121,7 +121,7 @@ class Aggregator {
|
|
|
121
121
|
logger.info(`File saved: ${filePath}`);
|
|
122
122
|
res.json({ success: true, path: filePath });
|
|
123
123
|
} catch (error) {
|
|
124
|
-
logger.error(`Failed to save file: ${
|
|
124
|
+
logger.error(`Failed to save file: ${error.message}`);
|
|
125
125
|
res.status(500).json({ error: error.message });
|
|
126
126
|
}
|
|
127
127
|
});
|
package/src/components/App.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useRef
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { TerminalPanel } from './TerminalPanel';
|
|
3
3
|
import { CodeViewer } from './CodeViewer';
|
|
4
4
|
|
|
@@ -12,17 +12,8 @@ export function App() {
|
|
|
12
12
|
const [latestVersion, setLatestVersion] = useState(null);
|
|
13
13
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
14
14
|
const [projectName, setProjectName] = useState('');
|
|
15
|
+
const [saving, setSaving] = useState(false);
|
|
15
16
|
const wsRef = useRef(null);
|
|
16
|
-
const activeTabIdRef = useRef(null);
|
|
17
|
-
const tabsRef = useRef([]);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
activeTabIdRef.current = activeTabId;
|
|
21
|
-
}, [activeTabId]);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
tabsRef.current = tabs;
|
|
25
|
-
}, [tabs]);
|
|
26
17
|
|
|
27
18
|
useEffect(() => {
|
|
28
19
|
fetchStatus();
|
|
@@ -30,23 +21,14 @@ export function App() {
|
|
|
30
21
|
document.addEventListener('click', handleDocumentClick);
|
|
31
22
|
document.addEventListener('keydown', handleKeyDown);
|
|
32
23
|
|
|
33
|
-
const preventBrowserSave = (e) => {
|
|
34
|
-
if (e.ctrlKey && e.key === 's') {
|
|
35
|
-
e.preventDefault();
|
|
36
|
-
e.stopPropagation();
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
window.addEventListener('keydown', preventBrowserSave, true);
|
|
40
|
-
|
|
41
24
|
return () => {
|
|
42
25
|
document.removeEventListener('click', handleDocumentClick);
|
|
43
26
|
document.removeEventListener('keydown', handleKeyDown);
|
|
44
|
-
window.removeEventListener('keydown', preventBrowserSave, true);
|
|
45
27
|
if (wsRef.current) {
|
|
46
28
|
wsRef.current.close();
|
|
47
29
|
}
|
|
48
30
|
};
|
|
49
|
-
}, [
|
|
31
|
+
}, []);
|
|
50
32
|
|
|
51
33
|
async function fetchStatus() {
|
|
52
34
|
try {
|
|
@@ -72,56 +54,12 @@ export function App() {
|
|
|
72
54
|
setContextMenu(null);
|
|
73
55
|
}
|
|
74
56
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const currentTabs = tabsRef.current;
|
|
78
|
-
const activeTab = currentTabs.find(t => t.id === currentTabId);
|
|
79
|
-
|
|
80
|
-
if (!activeTab || activeTab.isDiff) {
|
|
81
|
-
console.log('No active tab or is diff, skip save');
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
86
|
-
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
|
87
|
-
|
|
88
|
-
console.log('Saving file:', activeTab.path);
|
|
89
|
-
|
|
90
|
-
fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
|
|
91
|
-
method: 'POST',
|
|
92
|
-
headers: { 'Content-Type': 'application/json' },
|
|
93
|
-
body: JSON.stringify({ path: activeTab.path, content: activeTab.content })
|
|
94
|
-
})
|
|
95
|
-
.then(response => {
|
|
96
|
-
if (response.ok) {
|
|
97
|
-
console.log('File saved successfully');
|
|
98
|
-
setTabs(prevTabs => prevTabs.map(t =>
|
|
99
|
-
t.id === currentTabId ? { ...t, isModified: false } : t
|
|
100
|
-
));
|
|
101
|
-
} else {
|
|
102
|
-
response.json().then(error => {
|
|
103
|
-
console.error('Failed to save file:', error);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
.catch(error => {
|
|
108
|
-
console.error('Failed to save file:', error);
|
|
109
|
-
});
|
|
110
|
-
}, []);
|
|
111
|
-
|
|
112
|
-
const handleKeyDown = useCallback((e) => {
|
|
113
|
-
if (e.ctrlKey && e.key === 's') {
|
|
114
|
-
e.preventDefault();
|
|
115
|
-
console.log('Ctrl+S pressed, activeTabId:', activeTabIdRef.current);
|
|
116
|
-
saveCurrentFile();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!activeTabIdRef.current) return;
|
|
57
|
+
function handleKeyDown(e) {
|
|
58
|
+
if (!activeTabId) return;
|
|
121
59
|
|
|
122
60
|
if (e.ctrlKey && e.key === 'w') {
|
|
123
61
|
e.preventDefault();
|
|
124
|
-
closeTab(
|
|
62
|
+
closeTab(activeTabId);
|
|
125
63
|
} else if (e.ctrlKey && e.key === 'Tab') {
|
|
126
64
|
e.preventDefault();
|
|
127
65
|
if (e.shiftKey) {
|
|
@@ -129,16 +67,13 @@ export function App() {
|
|
|
129
67
|
} else {
|
|
130
68
|
switchToNextTab();
|
|
131
69
|
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (t.id === activeTabId) {
|
|
138
|
-
return { ...t, content: newContent, isModified: true };
|
|
70
|
+
} else if (e.ctrlKey && e.key === 's') {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
const activeTab = tabs.find(t => t.id === activeTabId);
|
|
73
|
+
if (activeTab && activeTab.modified) {
|
|
74
|
+
saveFile(activeTabId);
|
|
139
75
|
}
|
|
140
|
-
|
|
141
|
-
}));
|
|
76
|
+
}
|
|
142
77
|
}
|
|
143
78
|
|
|
144
79
|
function connectWebSocket() {
|
|
@@ -210,8 +145,10 @@ export function App() {
|
|
|
210
145
|
path: data.path,
|
|
211
146
|
name: fileName,
|
|
212
147
|
content: data.content,
|
|
148
|
+
originalContent: data.content,
|
|
213
149
|
diff: data.diff || null,
|
|
214
|
-
isDiff: !!data.diff
|
|
150
|
+
isDiff: !!data.diff,
|
|
151
|
+
modified: false
|
|
215
152
|
};
|
|
216
153
|
setActiveTabId(newTab.id);
|
|
217
154
|
return [...prevTabs, newTab];
|
|
@@ -226,7 +163,14 @@ export function App() {
|
|
|
226
163
|
const existingTab = prevTabs.find(t => t.path === data.path);
|
|
227
164
|
|
|
228
165
|
if (existingTab) {
|
|
229
|
-
const updatedTab = {
|
|
166
|
+
const updatedTab = {
|
|
167
|
+
...existingTab,
|
|
168
|
+
content: data.newContent,
|
|
169
|
+
originalContent: data.newContent,
|
|
170
|
+
diff: data.diff,
|
|
171
|
+
isDiff: true,
|
|
172
|
+
modified: false
|
|
173
|
+
};
|
|
230
174
|
setActiveTabId(existingTab.id);
|
|
231
175
|
return prevTabs.map(t =>
|
|
232
176
|
t.path === data.path ? updatedTab : t
|
|
@@ -237,8 +181,10 @@ export function App() {
|
|
|
237
181
|
path: data.path,
|
|
238
182
|
name: fileName,
|
|
239
183
|
content: data.newContent,
|
|
184
|
+
originalContent: data.newContent,
|
|
240
185
|
diff: data.diff,
|
|
241
|
-
isDiff: true
|
|
186
|
+
isDiff: true,
|
|
187
|
+
modified: false
|
|
242
188
|
};
|
|
243
189
|
setActiveTabId(newTab.id);
|
|
244
190
|
return [...prevTabs, newTab];
|
|
@@ -246,6 +192,51 @@ export function App() {
|
|
|
246
192
|
});
|
|
247
193
|
}
|
|
248
194
|
|
|
195
|
+
function handleContentChange(tabId, newContent) {
|
|
196
|
+
setTabs(prevTabs => prevTabs.map(tab => {
|
|
197
|
+
if (tab.id === tabId) {
|
|
198
|
+
const modified = newContent !== tab.originalContent;
|
|
199
|
+
return { ...tab, content: newContent, modified };
|
|
200
|
+
}
|
|
201
|
+
return tab;
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function saveFile(tabId) {
|
|
206
|
+
const tab = tabs.find(t => t.id === tabId);
|
|
207
|
+
if (!tab || !tab.modified) return;
|
|
208
|
+
|
|
209
|
+
setSaving(true);
|
|
210
|
+
try {
|
|
211
|
+
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
212
|
+
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
|
213
|
+
const response = await fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: { 'Content-Type': 'application/json' },
|
|
216
|
+
body: JSON.stringify({ path: tab.path, content: tab.content })
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (response.ok) {
|
|
220
|
+
setTabs(prevTabs => prevTabs.map(t => {
|
|
221
|
+
if (t.id === tabId) {
|
|
222
|
+
return { ...t, originalContent: t.content, modified: false };
|
|
223
|
+
}
|
|
224
|
+
return t;
|
|
225
|
+
}));
|
|
226
|
+
console.log('File saved:', tab.path);
|
|
227
|
+
} else {
|
|
228
|
+
const error = await response.json();
|
|
229
|
+
console.error('Failed to save file:', error.message);
|
|
230
|
+
alert('保存失败: ' + error.message);
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('Failed to save file:', error);
|
|
234
|
+
alert('保存失败: ' + error.message);
|
|
235
|
+
} finally {
|
|
236
|
+
setSaving(false);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
249
240
|
function handleFileClick(path) {
|
|
250
241
|
const existingTab = tabs.find(t => t.path === path);
|
|
251
242
|
if (existingTab) {
|
|
@@ -258,6 +249,14 @@ export function App() {
|
|
|
258
249
|
}
|
|
259
250
|
|
|
260
251
|
function closeTab(tabId) {
|
|
252
|
+
const tab = tabs.find(t => t.id === tabId);
|
|
253
|
+
if (tab?.modified) {
|
|
254
|
+
const confirmed = window.confirm(`文件 "${tab.name}" 已修改,是否保存?`);
|
|
255
|
+
if (confirmed) {
|
|
256
|
+
saveFile(tabId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
261
260
|
setTabs(prev => {
|
|
262
261
|
const newTabs = prev.filter(t => t.id !== tabId);
|
|
263
262
|
if (activeTabId === tabId) {
|
|
@@ -269,11 +268,30 @@ export function App() {
|
|
|
269
268
|
}
|
|
270
269
|
|
|
271
270
|
function closeOtherTabs(tabId) {
|
|
272
|
-
setTabs(prev =>
|
|
271
|
+
setTabs(prev => {
|
|
272
|
+
const tabsToClose = prev.filter(t => t.id !== tabId);
|
|
273
|
+
tabsToClose.forEach(t => {
|
|
274
|
+
if (t.modified) {
|
|
275
|
+
const confirmed = window.confirm(`文件 "${t.name}" 已修改,是否保存?`);
|
|
276
|
+
if (confirmed) {
|
|
277
|
+
saveFile(t.id);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
return prev.filter(t => t.id === tabId);
|
|
282
|
+
});
|
|
273
283
|
setActiveTabId(tabId);
|
|
274
284
|
}
|
|
275
285
|
|
|
276
286
|
function closeAllTabs() {
|
|
287
|
+
tabs.forEach(t => {
|
|
288
|
+
if (t.modified) {
|
|
289
|
+
const confirmed = window.confirm(`文件 "${t.name}" 已修改,是否保存?`);
|
|
290
|
+
if (confirmed) {
|
|
291
|
+
saveFile(t.id);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
277
295
|
setTabs([]);
|
|
278
296
|
setActiveTabId(null);
|
|
279
297
|
}
|
|
@@ -354,8 +372,7 @@ export function App() {
|
|
|
354
372
|
diff={activeTab.diff}
|
|
355
373
|
isDiff={activeTab.isDiff}
|
|
356
374
|
filePath={activeTab.path}
|
|
357
|
-
onChange={handleContentChange}
|
|
358
|
-
isModified={activeTab.isModified}
|
|
375
|
+
onChange={(value) => handleContentChange(activeTabId, value)}
|
|
359
376
|
/>
|
|
360
377
|
)}
|
|
361
378
|
</div>
|
|
@@ -364,12 +381,9 @@ export function App() {
|
|
|
364
381
|
<ContextMenu
|
|
365
382
|
x={contextMenu.x}
|
|
366
383
|
y={contextMenu.y}
|
|
367
|
-
|
|
384
|
+
tab={tabs.find(t => t.id === contextMenu.tabId)}
|
|
385
|
+
saving={saving}
|
|
368
386
|
onClose={() => setContextMenu(null)}
|
|
369
|
-
onSave={() => {
|
|
370
|
-
saveCurrentFile();
|
|
371
|
-
setContextMenu(null);
|
|
372
|
-
}}
|
|
373
387
|
onCloseTab={() => {
|
|
374
388
|
closeTab(contextMenu.tabId);
|
|
375
389
|
setContextMenu(null);
|
|
@@ -382,6 +396,10 @@ export function App() {
|
|
|
382
396
|
closeAllTabs();
|
|
383
397
|
setContextMenu(null);
|
|
384
398
|
}}
|
|
399
|
+
onSave={() => {
|
|
400
|
+
saveFile(contextMenu.tabId);
|
|
401
|
+
setContextMenu(null);
|
|
402
|
+
}}
|
|
385
403
|
/>
|
|
386
404
|
)}
|
|
387
405
|
<div className="panel right-panel">
|
|
@@ -400,12 +418,12 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
400
418
|
{tabs.map(tab => (
|
|
401
419
|
<div
|
|
402
420
|
key={tab.id}
|
|
403
|
-
className={`tab ${activeTabId === tab.id ? 'active' : ''}`}
|
|
421
|
+
className={`tab ${activeTabId === tab.id ? 'active' : ''} ${tab.modified ? 'modified' : ''}`}
|
|
404
422
|
onClick={() => onTabClick(tab.id)}
|
|
405
423
|
onContextMenu={(e) => onContextMenu(e, tab.id)}
|
|
406
424
|
>
|
|
407
425
|
<span className="tab-name">
|
|
408
|
-
{tab.
|
|
426
|
+
{tab.modified && <span className="tab-modified-mark">●</span>}
|
|
409
427
|
{tab.name}
|
|
410
428
|
</span>
|
|
411
429
|
<button
|
|
@@ -423,14 +441,14 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
423
441
|
);
|
|
424
442
|
}
|
|
425
443
|
|
|
426
|
-
function ContextMenu({ x, y,
|
|
427
|
-
const tabs = tabsRef.current;
|
|
428
|
-
const tab = tabs.find(t => t.id === tabId);
|
|
429
|
-
const canSave = tab && !tab.isDiff;
|
|
430
|
-
|
|
444
|
+
function ContextMenu({ x, y, tab, saving, onClose, onCloseTab, onCloseOtherTabs, onCloseAllTabs, onSave }) {
|
|
431
445
|
return (
|
|
432
446
|
<div className="context-menu" style={{ left: x, top: y }} onClick={(e) => e.stopPropagation()}>
|
|
433
|
-
{
|
|
447
|
+
{tab?.modified && (
|
|
448
|
+
<div className="context-menu-item" onClick={onSave} style={{ color: '#4ade80' }}>
|
|
449
|
+
{saving ? '保存中...' : '保存'}
|
|
450
|
+
</div>
|
|
451
|
+
)}
|
|
434
452
|
<div className="context-menu-item" onClick={onCloseTab}>关闭</div>
|
|
435
453
|
<div className="context-menu-item" onClick={onCloseOtherTabs}>关闭其他</div>
|
|
436
454
|
<div className="context-menu-item" onClick={onCloseAllTabs}>关闭所有</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useMemo, useRef
|
|
1
|
+
import React, { useMemo, useRef } from 'react';
|
|
2
2
|
import CodeMirror from '@uiw/react-codemirror';
|
|
3
|
-
import { EditorView, Decoration, ViewPlugin,
|
|
3
|
+
import { EditorView, Decoration, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
|
4
4
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
5
5
|
import { tags as t } from '@lezer/highlight';
|
|
6
6
|
import { RangeSetBuilder } from '@codemirror/state';
|
|
@@ -215,11 +215,8 @@ const darkHighlightStyle = HighlightStyle.define([
|
|
|
215
215
|
|
|
216
216
|
const syntaxTheme = syntaxHighlighting(darkHighlightStyle);
|
|
217
217
|
|
|
218
|
-
export function CodeViewer({ content, diff, isDiff, filePath, onChange
|
|
218
|
+
export function CodeViewer({ content, diff, isDiff, filePath, onChange }) {
|
|
219
219
|
const editorRef = useRef(null);
|
|
220
|
-
const [localContent, setLocalContent] = useState(content || '');
|
|
221
|
-
|
|
222
|
-
const isEditable = !isDiff;
|
|
223
220
|
|
|
224
221
|
const extensions = useMemo(() => {
|
|
225
222
|
const exts = [
|
|
@@ -257,27 +254,23 @@ export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModifi
|
|
|
257
254
|
return content || '';
|
|
258
255
|
}, [content, diff, isDiff]);
|
|
259
256
|
|
|
260
|
-
const
|
|
261
|
-
setLocalContent(value);
|
|
262
|
-
if (onChange) {
|
|
263
|
-
onChange(value);
|
|
264
|
-
}
|
|
265
|
-
}, [onChange]);
|
|
257
|
+
const editable = !isDiff;
|
|
266
258
|
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
259
|
+
function handleChange(value) {
|
|
260
|
+
if (onChange && editable) {
|
|
261
|
+
onChange(value);
|
|
270
262
|
}
|
|
271
|
-
}
|
|
263
|
+
}
|
|
272
264
|
|
|
273
265
|
return (
|
|
274
266
|
<div className="code-viewer-codemirror">
|
|
275
267
|
<CodeMirror
|
|
276
|
-
value={
|
|
268
|
+
value={code}
|
|
277
269
|
height="100%"
|
|
278
270
|
theme={darkTheme}
|
|
279
271
|
extensions={extensions}
|
|
280
|
-
editable={
|
|
272
|
+
editable={editable}
|
|
273
|
+
onChange={handleChange}
|
|
281
274
|
basicSetup={{
|
|
282
275
|
lineNumbers: true,
|
|
283
276
|
foldGutter: false,
|
|
@@ -285,7 +278,6 @@ export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModifi
|
|
|
285
278
|
highlightSelectionMatches: false,
|
|
286
279
|
bracketMatching: true,
|
|
287
280
|
}}
|
|
288
|
-
onChange={isEditable ? handleChange : undefined}
|
|
289
281
|
onCreateEditor={(view) => {
|
|
290
282
|
editorRef.current = view;
|
|
291
283
|
}}
|
package/src/global.css
CHANGED
|
@@ -357,12 +357,6 @@ body {
|
|
|
357
357
|
white-space: nowrap;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
.tab-modified {
|
|
361
|
-
color: var(--accent-color);
|
|
362
|
-
font-weight: bold;
|
|
363
|
-
margin-right: 2px;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
360
|
.tab-close {
|
|
367
361
|
background: none;
|
|
368
362
|
border: none;
|
|
@@ -382,6 +376,16 @@ body {
|
|
|
382
376
|
opacity: 1;
|
|
383
377
|
}
|
|
384
378
|
|
|
379
|
+
.tab.modified .tab-name {
|
|
380
|
+
color: var(--accent-color);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.tab-modified-mark {
|
|
384
|
+
color: var(--accent-color);
|
|
385
|
+
margin-right: 4px;
|
|
386
|
+
font-size: 10px;
|
|
387
|
+
}
|
|
388
|
+
|
|
385
389
|
.context-menu {
|
|
386
390
|
position: fixed;
|
|
387
391
|
background: linear-gradient(180deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
|