mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
feat: wire up sidebar panels and REPL with IPC handlers, preload channels, and CSS
This commit is contained in:
@@ -1536,6 +1536,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom Panel (REPL Output) -->
|
||||||
|
<div class="bottom-panel collapsed" id="bottom-panel">
|
||||||
|
<div class="bottom-panel-header">
|
||||||
|
<span class="bottom-panel-title">Output</span>
|
||||||
|
<div class="bottom-panel-actions">
|
||||||
|
<button class="bottom-panel-btn" id="repl-clear" title="Clear output">Clear</button>
|
||||||
|
<button class="bottom-panel-btn" id="bottom-panel-toggle" title="Toggle panel">▼</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bottom-panel-content" id="repl-output"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="status-bar" id="status-bar">
|
<div class="status-bar" id="status-bar">
|
||||||
<div class="status-bar-left">
|
<div class="status-bar-left">
|
||||||
<span class="status-item" id="status-text">Ready</span>
|
<span class="status-item" id="status-text">Ready</span>
|
||||||
|
|||||||
+160
@@ -3926,3 +3926,163 @@ ipcMain.on('insert-generated-content', (event, content) => {
|
|||||||
mainWindow.webContents.send('insert-content', content);
|
mainWindow.webContents.send('insert-content', content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// File Explorer IPC Handlers
|
||||||
|
// ============================================
|
||||||
|
ipcMain.handle('list-directory', async (event, dirPath) => {
|
||||||
|
try {
|
||||||
|
if (!dirPath) {
|
||||||
|
const result = await dialog.showOpenDialog(mainWindow, {
|
||||||
|
properties: ['openDirectory']
|
||||||
|
});
|
||||||
|
if (result.canceled || !result.filePaths[0]) return null;
|
||||||
|
dirPath = result.filePaths[0];
|
||||||
|
}
|
||||||
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
|
||||||
|
.filter(e => !e.name.startsWith('.'))
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.isDirectory() && !b.isDirectory()) return -1;
|
||||||
|
if (!a.isDirectory() && b.isDirectory()) return 1;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
})
|
||||||
|
.map(e => ({
|
||||||
|
name: e.name,
|
||||||
|
isDirectory: e.isDirectory(),
|
||||||
|
path: path.join(dirPath, e.name)
|
||||||
|
}));
|
||||||
|
return { path: dirPath, entries };
|
||||||
|
} catch (err) {
|
||||||
|
console.error('list-directory error:', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open a file by path (from explorer panel)
|
||||||
|
ipcMain.on('open-file-path', (event, filePath) => {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(filePath)) return;
|
||||||
|
const stat = fs.statSync(filePath);
|
||||||
|
if (stat.size > MAX_FILE_SIZE) return;
|
||||||
|
currentFile = filePath;
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
mainWindow.webContents.send('file-opened', { path: filePath, content });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('open-file-path error:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Git IPC Handlers
|
||||||
|
// ============================================
|
||||||
|
const simpleGit = require('simple-git');
|
||||||
|
|
||||||
|
function getGitRepo() {
|
||||||
|
// Use the directory of the current file, or the app's working directory
|
||||||
|
const dir = currentFile ? path.dirname(currentFile) : process.cwd();
|
||||||
|
return simpleGit(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle('git-status', async () => {
|
||||||
|
try {
|
||||||
|
const git = getGitRepo();
|
||||||
|
return await git.status();
|
||||||
|
} catch (err) {
|
||||||
|
return { error: 'Not a git repository' };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('git-stage', async (event, { files }) => {
|
||||||
|
try {
|
||||||
|
const git = getGitRepo();
|
||||||
|
await git.add(files);
|
||||||
|
return await git.status();
|
||||||
|
} catch (err) {
|
||||||
|
return { error: err.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('git-commit', async (event, { message }) => {
|
||||||
|
try {
|
||||||
|
const git = getGitRepo();
|
||||||
|
return await git.commit(message);
|
||||||
|
} catch (err) {
|
||||||
|
return { error: err.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('git-log', async () => {
|
||||||
|
try {
|
||||||
|
const git = getGitRepo();
|
||||||
|
return await git.log({ maxCount: 20 });
|
||||||
|
} catch (err) {
|
||||||
|
return { error: err.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Snippets IPC Handlers
|
||||||
|
// ============================================
|
||||||
|
const snippetsPath = path.join(app.getPath('userData'), 'snippets.json');
|
||||||
|
|
||||||
|
function loadSnippets() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(snippetsPath)) {
|
||||||
|
return JSON.parse(fs.readFileSync(snippetsPath, 'utf-8'));
|
||||||
|
}
|
||||||
|
} catch (err) { console.error('Failed to load snippets:', err); }
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSnippetsFile(snippets) {
|
||||||
|
fs.writeFileSync(snippetsPath, JSON.stringify(snippets, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle('get-snippets', async () => loadSnippets());
|
||||||
|
|
||||||
|
ipcMain.handle('save-snippet', async (event, snippet) => {
|
||||||
|
const snippets = loadSnippets();
|
||||||
|
const existing = snippets.findIndex(s => s.id === snippet.id);
|
||||||
|
if (existing >= 0) snippets[existing] = snippet;
|
||||||
|
else snippets.push(snippet);
|
||||||
|
saveSnippetsFile(snippets);
|
||||||
|
return snippets;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('delete-snippet', async (event, id) => {
|
||||||
|
const snippets = loadSnippets().filter(s => s.id !== id);
|
||||||
|
saveSnippetsFile(snippets);
|
||||||
|
return snippets;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Code Execution (REPL) IPC Handler
|
||||||
|
// ============================================
|
||||||
|
ipcMain.handle('execute-code', async (event, { code, language }) => {
|
||||||
|
const timeout = 10000;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let cmd, args;
|
||||||
|
if (language === 'javascript' || language === 'js') {
|
||||||
|
cmd = 'node';
|
||||||
|
args = ['-e', code];
|
||||||
|
} else if (language === 'python' || language === 'py') {
|
||||||
|
cmd = process.platform === 'win32' ? 'python' : 'python3';
|
||||||
|
args = ['-c', code];
|
||||||
|
} else if (language === 'bash' || language === 'sh') {
|
||||||
|
cmd = process.platform === 'win32' ? 'cmd' : 'bash';
|
||||||
|
args = process.platform === 'win32' ? ['/c', code] : ['-c', code];
|
||||||
|
} else {
|
||||||
|
resolve({ error: `Unsupported language: ${language}` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
execFile(cmd, args, { timeout, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
||||||
|
resolve({
|
||||||
|
stdout: stdout || '',
|
||||||
|
stderr: stderr || '',
|
||||||
|
error: err?.killed ? 'Execution timed out (10s limit)' : (err?.message || null)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+21
-1
@@ -97,7 +97,27 @@ const ALLOWED_SEND_CHANNELS = [
|
|||||||
'save-pasted-image',
|
'save-pasted-image',
|
||||||
|
|
||||||
// Templates
|
// Templates
|
||||||
'load-template'
|
'load-template',
|
||||||
|
|
||||||
|
// File Explorer
|
||||||
|
'list-directory',
|
||||||
|
|
||||||
|
// Git
|
||||||
|
'git-status',
|
||||||
|
'git-stage',
|
||||||
|
'git-commit',
|
||||||
|
'git-log',
|
||||||
|
|
||||||
|
// Snippets
|
||||||
|
'get-snippets',
|
||||||
|
'save-snippet',
|
||||||
|
'delete-snippet',
|
||||||
|
|
||||||
|
// Code execution (REPL)
|
||||||
|
'execute-code',
|
||||||
|
|
||||||
|
// File open by path
|
||||||
|
'open-file-path'
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALLOWED_RECEIVE_CHANNELS = [
|
const ALLOWED_RECEIVE_CHANNELS = [
|
||||||
|
|||||||
+46
-3
@@ -12,6 +12,10 @@ const { createEditor } = require('./editor/codemirror-setup');
|
|||||||
const { undo, redo } = require('@codemirror/commands');
|
const { undo, redo } = require('@codemirror/commands');
|
||||||
const { SidebarManager } = require('./sidebar/sidebar-manager');
|
const { SidebarManager } = require('./sidebar/sidebar-manager');
|
||||||
const { renderTemplatesPanel } = require('./sidebar/templates-panel');
|
const { renderTemplatesPanel } = require('./sidebar/templates-panel');
|
||||||
|
const { renderExplorerPanel } = require('./sidebar/explorer-panel');
|
||||||
|
const { renderGitPanel } = require('./sidebar/git-panel');
|
||||||
|
const { renderSnippetsPanel } = require('./sidebar/snippets-panel');
|
||||||
|
const { ReplPanel } = require('./repl/repl-panel');
|
||||||
const { CommandPalette } = require('./command-palette');
|
const { CommandPalette } = require('./command-palette');
|
||||||
const { PrintPreview } = require('./print-preview');
|
const { PrintPreview } = require('./print-preview');
|
||||||
|
|
||||||
@@ -488,6 +492,26 @@ class TabManager {
|
|||||||
|
|
||||||
pre.parentElement.replaceChild(img, pre);
|
pre.parentElement.replaceChild(img, pre);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add Run buttons to executable code blocks (REPL)
|
||||||
|
const codeBlocks = preview.querySelectorAll('pre code[class*="language-"]');
|
||||||
|
codeBlocks.forEach(block => {
|
||||||
|
const lang = block.className.match(/language-(\w+)/)?.[1];
|
||||||
|
if (['javascript', 'js', 'python', 'py', 'bash', 'sh'].includes(lang)) {
|
||||||
|
const runBtn = document.createElement('button');
|
||||||
|
runBtn.className = 'code-run-btn';
|
||||||
|
runBtn.textContent = '\u25B6 Run';
|
||||||
|
runBtn.addEventListener('click', async () => {
|
||||||
|
if (replPanel) {
|
||||||
|
replPanel.show();
|
||||||
|
const result = await ipcRenderer.invoke('execute-code', { code: block.textContent, language: lang });
|
||||||
|
replPanel.appendOutput(`[${lang}]`, result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
block.parentElement.style.position = 'relative';
|
||||||
|
block.parentElement.appendChild(runBtn);
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error rendering preview:', error);
|
console.error('Error rendering preview:', error);
|
||||||
preview.innerHTML = '<p class="error">Error rendering preview. Please check your markdown syntax.</p>';
|
preview.innerHTML = '<p class="error">Error rendering preview. Please check your markdown syntax.</p>';
|
||||||
@@ -1118,23 +1142,42 @@ class TabManager {
|
|||||||
|
|
||||||
// Initialize tab manager
|
// Initialize tab manager
|
||||||
let tabManager;
|
let tabManager;
|
||||||
|
let replPanel;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
tabManager = new TabManager();
|
tabManager = new TabManager();
|
||||||
|
replPanel = new ReplPanel();
|
||||||
|
|
||||||
// Initialize sidebar
|
// Initialize sidebar
|
||||||
const sidebarManager = new SidebarManager();
|
const sidebarManager = new SidebarManager();
|
||||||
|
let explorerCurrentDir = null;
|
||||||
|
|
||||||
sidebarManager.registerPanel('explorer', {
|
sidebarManager.registerPanel('explorer', {
|
||||||
title: 'Explorer',
|
title: 'Explorer',
|
||||||
render: (container) => { container.innerHTML = '<p style="color:#888;padding:8px;">File explorer coming soon...</p>'; }
|
render: (container) => renderExplorerPanel(container, {
|
||||||
|
listDirectory: (dir) => ipcRenderer.invoke('list-directory', dir),
|
||||||
|
onFileOpen: (filePath) => ipcRenderer.send('open-file-path', filePath),
|
||||||
|
currentDir: explorerCurrentDir,
|
||||||
|
})
|
||||||
});
|
});
|
||||||
sidebarManager.registerPanel('git', {
|
sidebarManager.registerPanel('git', {
|
||||||
title: 'Git',
|
title: 'Git',
|
||||||
render: (container) => { container.innerHTML = '<p style="color:#888;padding:8px;">Git panel coming soon...</p>'; }
|
render: (container) => renderGitPanel(container, {
|
||||||
|
gitStatus: () => ipcRenderer.invoke('git-status'),
|
||||||
|
gitDiff: (file) => ipcRenderer.invoke('git-diff', { file }),
|
||||||
|
gitStage: (files) => ipcRenderer.invoke('git-stage', { files }),
|
||||||
|
gitCommit: (message) => ipcRenderer.invoke('git-commit', { message }),
|
||||||
|
gitLog: () => ipcRenderer.invoke('git-log'),
|
||||||
|
})
|
||||||
});
|
});
|
||||||
sidebarManager.registerPanel('snippets', {
|
sidebarManager.registerPanel('snippets', {
|
||||||
title: 'Snippets',
|
title: 'Snippets',
|
||||||
render: (container) => { container.innerHTML = '<p style="color:#888;padding:8px;">Snippets coming soon...</p>'; }
|
render: (container) => renderSnippetsPanel(container, {
|
||||||
|
getSnippets: () => ipcRenderer.invoke('get-snippets'),
|
||||||
|
saveSnippet: (s) => ipcRenderer.invoke('save-snippet', s),
|
||||||
|
deleteSnippet: (id) => ipcRenderer.invoke('delete-snippet', id),
|
||||||
|
onInsert: (code) => tabManager.insertAtCursor(code),
|
||||||
|
})
|
||||||
});
|
});
|
||||||
sidebarManager.registerPanel('templates', {
|
sidebarManager.registerPanel('templates', {
|
||||||
title: 'Templates',
|
title: 'Templates',
|
||||||
|
|||||||
@@ -161,3 +161,108 @@ body[class*="dark"] .panel-list-item-title {
|
|||||||
body[class*="dark"] .panel-list-item-desc {
|
body[class*="dark"] .panel-list-item-desc {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Explorer Panel */
|
||||||
|
.explorer-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.explorer-path {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid var(--gray-300, #d1d5db);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: white;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.explorer-browse-btn {
|
||||||
|
border: 1px solid var(--gray-300, #d1d5db);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
.tree-item {
|
||||||
|
padding: 3px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.tree-item:hover {
|
||||||
|
background: var(--gray-100, #f3f4f6);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.tree-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.tree-children {
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
.tree-folder.collapsed .tree-children {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tree-name {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Git Panel */
|
||||||
|
.git-section { margin-bottom: 16px; }
|
||||||
|
.git-section-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--gray-500); margin-bottom: 8px; }
|
||||||
|
.git-file { display: flex; align-items: center; padding: 4px 6px; border-radius: 4px; font-size: 13px; gap: 6px; }
|
||||||
|
.git-file:hover { background: var(--gray-100, #f3f4f6); }
|
||||||
|
.git-file-status { font-weight: 700; font-family: monospace; width: 16px; text-align: center; }
|
||||||
|
.git-file-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.git-stage-btn { border: none; background: var(--gray-200); border-radius: 4px; cursor: pointer; font-size: 14px; width: 22px; height: 22px; }
|
||||||
|
.git-commit-input { width: 100%; padding: 8px; border: 1px solid var(--gray-300); border-radius: 6px; font-size: 13px; font-family: inherit; resize: vertical; box-sizing: border-box; }
|
||||||
|
.git-commit-btn { width: 100%; margin-top: 8px; padding: 8px; background: var(--primary-dark, #5661b3); color: white; border: none; border-radius: 6px; font-size: 13px; cursor: pointer; }
|
||||||
|
.git-commit-btn:hover { opacity: 0.9; }
|
||||||
|
.git-log-entry { padding: 6px 0; border-bottom: 1px solid var(--gray-100); }
|
||||||
|
.git-log-msg { font-size: 13px; }
|
||||||
|
.git-log-meta { font-size: 11px; color: var(--gray-500); margin-top: 2px; }
|
||||||
|
.git-info { font-size: 13px; color: var(--gray-500); padding: 8px 0; }
|
||||||
|
.git-loading { font-size: 13px; color: var(--gray-400); }
|
||||||
|
|
||||||
|
/* Snippets Panel */
|
||||||
|
.snippets-toolbar { display: flex; gap: 4px; margin-bottom: 8px; }
|
||||||
|
.snippets-search { flex: 1; padding: 6px 10px; border: 1px solid var(--gray-300); border-radius: 6px; font-size: 13px; }
|
||||||
|
.snippets-add-btn { width: 32px; border: 1px solid var(--gray-300); border-radius: 6px; background: white; font-size: 18px; cursor: pointer; }
|
||||||
|
.snippet-item { padding: 8px; border: 1px solid var(--gray-200); border-radius: 6px; margin-bottom: 6px; }
|
||||||
|
.snippet-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
|
||||||
|
.snippet-name { font-size: 13px; font-weight: 500; }
|
||||||
|
.snippet-lang { font-size: 11px; background: var(--gray-100); padding: 2px 6px; border-radius: 4px; color: var(--gray-500); }
|
||||||
|
.snippet-preview { font-size: 12px; background: var(--gray-50); padding: 6px; border-radius: 4px; margin: 4px 0; overflow: hidden; max-height: 60px; }
|
||||||
|
.snippet-preview code { font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.snippet-actions { display: flex; gap: 4px; }
|
||||||
|
.snippet-insert { font-size: 12px; padding: 4px 8px; border: 1px solid var(--gray-300); border-radius: 4px; background: white; cursor: pointer; }
|
||||||
|
.snippet-delete { font-size: 14px; padding: 4px 8px; border: 1px solid var(--gray-300); border-radius: 4px; background: white; cursor: pointer; color: #ef4444; }
|
||||||
|
|
||||||
|
/* Dark theme for sidebar panels */
|
||||||
|
body[class*="dark"] .explorer-path,
|
||||||
|
body[class*="dark"] .explorer-browse-btn,
|
||||||
|
body[class*="dark"] .snippets-search,
|
||||||
|
body[class*="dark"] .snippets-add-btn,
|
||||||
|
body[class*="dark"] .snippet-insert,
|
||||||
|
body[class*="dark"] .snippet-delete {
|
||||||
|
background: #2d2d2d;
|
||||||
|
border-color: #444;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
body[class*="dark"] .git-commit-input {
|
||||||
|
background: #2d2d2d;
|
||||||
|
border-color: #444;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
body[class*="dark"] .snippet-item {
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
body[class*="dark"] .snippet-preview {
|
||||||
|
background: #2d2d2d;
|
||||||
|
}
|
||||||
|
body[class*="dark"] .tree-item:hover,
|
||||||
|
body[class*="dark"] .git-file:hover {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|||||||
@@ -290,6 +290,43 @@ body {
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bottom Panel (REPL Output) */
|
||||||
|
.bottom-panel { display: flex; flex-direction: column; border-top: 1px solid var(--gray-200, #e5e7eb); flex-shrink: 0; }
|
||||||
|
.bottom-panel.collapsed { height: 0; overflow: hidden; border: none; }
|
||||||
|
.bottom-panel:not(.collapsed) { height: 200px; }
|
||||||
|
.bottom-panel-header { display: flex; justify-content: space-between; align-items: center; padding: 6px 12px; background: var(--gray-50, #f9fafb); border-bottom: 1px solid var(--gray-200); }
|
||||||
|
.bottom-panel-title { font-size: 13px; font-weight: 600; color: var(--gray-600); }
|
||||||
|
.bottom-panel-actions { display: flex; gap: 4px; }
|
||||||
|
.bottom-panel-btn { font-size: 12px; padding: 4px 8px; border: 1px solid var(--gray-300); border-radius: 4px; background: white; cursor: pointer; }
|
||||||
|
.bottom-panel-content { flex: 1; overflow-y: auto; padding: 12px; font-family: 'JetBrains Mono', monospace; font-size: 13px; background: #1e1e1e; color: #d4d4d4; }
|
||||||
|
.repl-entry { margin-bottom: 8px; }
|
||||||
|
.repl-command { color: #569cd6; margin-bottom: 2px; }
|
||||||
|
.repl-stdout { color: #d4d4d4; white-space: pre-wrap; }
|
||||||
|
.repl-stderr { color: #f44747; white-space: pre-wrap; }
|
||||||
|
.repl-error { color: #f44747; }
|
||||||
|
|
||||||
|
/* Code Run Button */
|
||||||
|
.code-run-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: var(--primary-dark, #5661b3);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
pre:hover .code-run-btn { opacity: 1; }
|
||||||
|
|
||||||
|
/* Dark theme bottom panel */
|
||||||
|
body[class*="dark"] .bottom-panel-header { background: #1e1e1e; border-color: #333; }
|
||||||
|
body[class*="dark"] .bottom-panel-btn { background: #2d2d2d; border-color: #444; color: #ccc; }
|
||||||
|
body[class*="dark"] .bottom-panel-title { color: #ccc; }
|
||||||
|
|
||||||
/* Status Bar */
|
/* Status Bar */
|
||||||
.status-bar {
|
.status-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user