codex-lens 0.1.25 → 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-DJ9sK-1n.js → main-BO4694Xj.js} +36 -36
- 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 +141 -136
- 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
|
@@ -12,59 +12,23 @@ 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
|
-
const saveCurrentFileRef = useRef(null);
|
|
19
17
|
|
|
20
18
|
useEffect(() => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
tabsRef.current = tabs;
|
|
26
|
-
}, [tabs]);
|
|
27
|
-
|
|
28
|
-
function saveCurrentFile() {
|
|
29
|
-
const currentTabId = activeTabIdRef.current;
|
|
30
|
-
const currentTabs = tabsRef.current;
|
|
31
|
-
const activeTab = currentTabs.find(t => t.id === currentTabId);
|
|
32
|
-
|
|
33
|
-
if (!activeTab || activeTab.isDiff) {
|
|
34
|
-
console.log('No active tab or is diff, skip save');
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
39
|
-
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
|
40
|
-
|
|
41
|
-
console.log('Saving file:', activeTab.path);
|
|
42
|
-
|
|
43
|
-
fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
|
|
44
|
-
method: 'POST',
|
|
45
|
-
headers: { 'Content-Type': 'application/json' },
|
|
46
|
-
body: JSON.stringify({ path: activeTab.path, content: activeTab.content })
|
|
47
|
-
})
|
|
48
|
-
.then(response => {
|
|
49
|
-
if (response.ok) {
|
|
50
|
-
console.log('File saved successfully');
|
|
51
|
-
setTabs(prevTabs => prevTabs.map(t =>
|
|
52
|
-
t.id === currentTabId ? { ...t, isModified: false } : t
|
|
53
|
-
));
|
|
54
|
-
} else {
|
|
55
|
-
response.json().then(error => {
|
|
56
|
-
console.error('Failed to save file:', error);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
.catch(error => {
|
|
61
|
-
console.error('Failed to save file:', error);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
19
|
+
fetchStatus();
|
|
20
|
+
connectWebSocket();
|
|
21
|
+
document.addEventListener('click', handleDocumentClick);
|
|
22
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
64
23
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
24
|
+
return () => {
|
|
25
|
+
document.removeEventListener('click', handleDocumentClick);
|
|
26
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
27
|
+
if (wsRef.current) {
|
|
28
|
+
wsRef.current.close();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
68
32
|
|
|
69
33
|
async function fetchStatus() {
|
|
70
34
|
try {
|
|
@@ -90,63 +54,26 @@ export function App() {
|
|
|
90
54
|
setContextMenu(null);
|
|
91
55
|
}
|
|
92
56
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
connectWebSocket();
|
|
57
|
+
function handleKeyDown(e) {
|
|
58
|
+
if (!activeTabId) return;
|
|
96
59
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
60
|
+
if (e.ctrlKey && e.key === 'w') {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
closeTab(activeTabId);
|
|
63
|
+
} else if (e.ctrlKey && e.key === 'Tab') {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
if (e.shiftKey) {
|
|
66
|
+
switchToPrevTab();
|
|
67
|
+
} else {
|
|
68
|
+
switchToNextTab();
|
|
105
69
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
closeTab(activeTabIdRef.current);
|
|
112
|
-
} else if (e.ctrlKey && e.key === 'Tab') {
|
|
113
|
-
e.preventDefault();
|
|
114
|
-
if (e.shiftKey) {
|
|
115
|
-
switchToPrevTab();
|
|
116
|
-
} else {
|
|
117
|
-
switchToNextTab();
|
|
118
|
-
}
|
|
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);
|
|
119
75
|
}
|
|
120
76
|
}
|
|
121
|
-
|
|
122
|
-
document.addEventListener('click', handleDocumentClick);
|
|
123
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
124
|
-
|
|
125
|
-
const preventBrowserSave = (e) => {
|
|
126
|
-
if (e.ctrlKey && e.key === 's') {
|
|
127
|
-
e.preventDefault();
|
|
128
|
-
e.stopPropagation();
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
window.addEventListener('keydown', preventBrowserSave, true);
|
|
132
|
-
|
|
133
|
-
return () => {
|
|
134
|
-
document.removeEventListener('click', handleDocumentClick);
|
|
135
|
-
document.removeEventListener('keydown', handleKeyDown);
|
|
136
|
-
window.removeEventListener('keydown', preventBrowserSave, true);
|
|
137
|
-
if (wsRef.current) {
|
|
138
|
-
wsRef.current.close();
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
}, []);
|
|
142
|
-
|
|
143
|
-
function handleContentChange(newContent) {
|
|
144
|
-
setTabs(prevTabs => prevTabs.map(t => {
|
|
145
|
-
if (t.id === activeTabId) {
|
|
146
|
-
return { ...t, content: newContent, isModified: true };
|
|
147
|
-
}
|
|
148
|
-
return t;
|
|
149
|
-
}));
|
|
150
77
|
}
|
|
151
78
|
|
|
152
79
|
function connectWebSocket() {
|
|
@@ -218,8 +145,10 @@ export function App() {
|
|
|
218
145
|
path: data.path,
|
|
219
146
|
name: fileName,
|
|
220
147
|
content: data.content,
|
|
148
|
+
originalContent: data.content,
|
|
221
149
|
diff: data.diff || null,
|
|
222
|
-
isDiff: !!data.diff
|
|
150
|
+
isDiff: !!data.diff,
|
|
151
|
+
modified: false
|
|
223
152
|
};
|
|
224
153
|
setActiveTabId(newTab.id);
|
|
225
154
|
return [...prevTabs, newTab];
|
|
@@ -234,7 +163,14 @@ export function App() {
|
|
|
234
163
|
const existingTab = prevTabs.find(t => t.path === data.path);
|
|
235
164
|
|
|
236
165
|
if (existingTab) {
|
|
237
|
-
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
|
+
};
|
|
238
174
|
setActiveTabId(existingTab.id);
|
|
239
175
|
return prevTabs.map(t =>
|
|
240
176
|
t.path === data.path ? updatedTab : t
|
|
@@ -245,8 +181,10 @@ export function App() {
|
|
|
245
181
|
path: data.path,
|
|
246
182
|
name: fileName,
|
|
247
183
|
content: data.newContent,
|
|
184
|
+
originalContent: data.newContent,
|
|
248
185
|
diff: data.diff,
|
|
249
|
-
isDiff: true
|
|
186
|
+
isDiff: true,
|
|
187
|
+
modified: false
|
|
250
188
|
};
|
|
251
189
|
setActiveTabId(newTab.id);
|
|
252
190
|
return [...prevTabs, newTab];
|
|
@@ -254,6 +192,51 @@ export function App() {
|
|
|
254
192
|
});
|
|
255
193
|
}
|
|
256
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
|
+
|
|
257
240
|
function handleFileClick(path) {
|
|
258
241
|
const existingTab = tabs.find(t => t.path === path);
|
|
259
242
|
if (existingTab) {
|
|
@@ -266,10 +249,17 @@ export function App() {
|
|
|
266
249
|
}
|
|
267
250
|
|
|
268
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
|
+
|
|
269
260
|
setTabs(prev => {
|
|
270
261
|
const newTabs = prev.filter(t => t.id !== tabId);
|
|
271
|
-
|
|
272
|
-
if (currentActiveId === tabId) {
|
|
262
|
+
if (activeTabId === tabId) {
|
|
273
263
|
const newActiveId = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
|
|
274
264
|
setActiveTabId(newActiveId);
|
|
275
265
|
}
|
|
@@ -278,11 +268,30 @@ export function App() {
|
|
|
278
268
|
}
|
|
279
269
|
|
|
280
270
|
function closeOtherTabs(tabId) {
|
|
281
|
-
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
|
+
});
|
|
282
283
|
setActiveTabId(tabId);
|
|
283
284
|
}
|
|
284
285
|
|
|
285
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
|
+
});
|
|
286
295
|
setTabs([]);
|
|
287
296
|
setActiveTabId(null);
|
|
288
297
|
}
|
|
@@ -292,24 +301,20 @@ export function App() {
|
|
|
292
301
|
}
|
|
293
302
|
|
|
294
303
|
function switchToNextTab() {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
setActiveTabId(
|
|
300
|
-
} else if (currentTabs.length > 0) {
|
|
301
|
-
setActiveTabId(currentTabs[0].id);
|
|
304
|
+
const currentIndex = tabs.findIndex(t => t.id === activeTabId);
|
|
305
|
+
if (currentIndex < tabs.length - 1) {
|
|
306
|
+
setActiveTabId(tabs[currentIndex + 1].id);
|
|
307
|
+
} else if (tabs.length > 0) {
|
|
308
|
+
setActiveTabId(tabs[0].id);
|
|
302
309
|
}
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
function switchToPrevTab() {
|
|
306
|
-
const
|
|
307
|
-
const currentActiveId = activeTabIdRef.current;
|
|
308
|
-
const currentIndex = currentTabs.findIndex(t => t.id === currentActiveId);
|
|
313
|
+
const currentIndex = tabs.findIndex(t => t.id === activeTabId);
|
|
309
314
|
if (currentIndex > 0) {
|
|
310
|
-
setActiveTabId(
|
|
311
|
-
} else if (
|
|
312
|
-
setActiveTabId(
|
|
315
|
+
setActiveTabId(tabs[currentIndex - 1].id);
|
|
316
|
+
} else if (tabs.length > 0) {
|
|
317
|
+
setActiveTabId(tabs[tabs.length - 1].id);
|
|
313
318
|
}
|
|
314
319
|
}
|
|
315
320
|
|
|
@@ -367,8 +372,7 @@ export function App() {
|
|
|
367
372
|
diff={activeTab.diff}
|
|
368
373
|
isDiff={activeTab.isDiff}
|
|
369
374
|
filePath={activeTab.path}
|
|
370
|
-
onChange={handleContentChange}
|
|
371
|
-
isModified={activeTab.isModified}
|
|
375
|
+
onChange={(value) => handleContentChange(activeTabId, value)}
|
|
372
376
|
/>
|
|
373
377
|
)}
|
|
374
378
|
</div>
|
|
@@ -377,12 +381,9 @@ export function App() {
|
|
|
377
381
|
<ContextMenu
|
|
378
382
|
x={contextMenu.x}
|
|
379
383
|
y={contextMenu.y}
|
|
380
|
-
|
|
384
|
+
tab={tabs.find(t => t.id === contextMenu.tabId)}
|
|
385
|
+
saving={saving}
|
|
381
386
|
onClose={() => setContextMenu(null)}
|
|
382
|
-
onSave={() => {
|
|
383
|
-
saveCurrentFile();
|
|
384
|
-
setContextMenu(null);
|
|
385
|
-
}}
|
|
386
387
|
onCloseTab={() => {
|
|
387
388
|
closeTab(contextMenu.tabId);
|
|
388
389
|
setContextMenu(null);
|
|
@@ -395,6 +396,10 @@ export function App() {
|
|
|
395
396
|
closeAllTabs();
|
|
396
397
|
setContextMenu(null);
|
|
397
398
|
}}
|
|
399
|
+
onSave={() => {
|
|
400
|
+
saveFile(contextMenu.tabId);
|
|
401
|
+
setContextMenu(null);
|
|
402
|
+
}}
|
|
398
403
|
/>
|
|
399
404
|
)}
|
|
400
405
|
<div className="panel right-panel">
|
|
@@ -413,12 +418,12 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
413
418
|
{tabs.map(tab => (
|
|
414
419
|
<div
|
|
415
420
|
key={tab.id}
|
|
416
|
-
className={`tab ${activeTabId === tab.id ? 'active' : ''}`}
|
|
421
|
+
className={`tab ${activeTabId === tab.id ? 'active' : ''} ${tab.modified ? 'modified' : ''}`}
|
|
417
422
|
onClick={() => onTabClick(tab.id)}
|
|
418
423
|
onContextMenu={(e) => onContextMenu(e, tab.id)}
|
|
419
424
|
>
|
|
420
425
|
<span className="tab-name">
|
|
421
|
-
{tab.
|
|
426
|
+
{tab.modified && <span className="tab-modified-mark">●</span>}
|
|
422
427
|
{tab.name}
|
|
423
428
|
</span>
|
|
424
429
|
<button
|
|
@@ -436,14 +441,14 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
436
441
|
);
|
|
437
442
|
}
|
|
438
443
|
|
|
439
|
-
function ContextMenu({ x, y,
|
|
440
|
-
const tabs = tabsRef.current;
|
|
441
|
-
const tab = tabs.find(t => t.id === tabId);
|
|
442
|
-
const canSave = tab && !tab.isDiff;
|
|
443
|
-
|
|
444
|
+
function ContextMenu({ x, y, tab, saving, onClose, onCloseTab, onCloseOtherTabs, onCloseAllTabs, onSave }) {
|
|
444
445
|
return (
|
|
445
446
|
<div className="context-menu" style={{ left: x, top: y }} onClick={(e) => e.stopPropagation()}>
|
|
446
|
-
{
|
|
447
|
+
{tab?.modified && (
|
|
448
|
+
<div className="context-menu-item" onClick={onSave} style={{ color: '#4ade80' }}>
|
|
449
|
+
{saving ? '保存中...' : '保存'}
|
|
450
|
+
</div>
|
|
451
|
+
)}
|
|
447
452
|
<div className="context-menu-item" onClick={onCloseTab}>关闭</div>
|
|
448
453
|
<div className="context-menu-item" onClick={onCloseOtherTabs}>关闭其他</div>
|
|
449
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%);
|