v3.0.2: server-v2 모듈 분리 — Arduino·PPTX 라우트 추출 (-2923줄)
- routes-arduino.ts: Arduino 컴파일/라이브러리/QEMU/ESP8266 에뮬 전체 - routes-pptx.ts: PPTX 생성/편집/PDF/이미지 API 전체 - mocks-esp32: WiFi.STA(_STAProxy), scanNetworks, DELETE() 수정 - mocks-esp8266: DELETE() 수정 - server-v2.ts: 16106 → 13183줄 (–18%) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+184
-17
@@ -762,8 +762,8 @@ function createXtermShell(container, shellId) {
|
||||
fontFamily: "'IBM Plex Mono', 'D2Coding', 'NanumGothicCoding', 'Malgun Gothic', 'AppleGothic', monospace",
|
||||
fontSize: 13, lineHeight: 1.4, cursorBlink: true, scrollback: 5000,
|
||||
});
|
||||
const fitAddon = new AddonFit.FitAddon();
|
||||
const webLinksAddon = new AddonWebLinks.WebLinksAddon();
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
const webLinksAddon = new WebLinksAddon.WebLinksAddon();
|
||||
term.loadAddon(fitAddon); term.loadAddon(webLinksAddon);
|
||||
term.open(sub); fitAddon.fit();
|
||||
// Intercept Ctrl+` inside xterm to toggle the terminal panel.
|
||||
@@ -1514,10 +1514,86 @@ function codeRunWith(mode) {
|
||||
}, codeTermOpen ? 0 : 500);
|
||||
}
|
||||
|
||||
function _boardSpecificHints(code = '') {
|
||||
const fqbn = _csbSelBoard.fqbn || '';
|
||||
const arch = fqbn.split(':')[1] || '';
|
||||
const usesWifi = /WiFi|ESP8266WiFi|HTTPClient|WebServer/i.test(code);
|
||||
if (arch === 'esp32') {
|
||||
const lines = [
|
||||
'ESP32 코딩 규칙 (반드시 준수):',
|
||||
'- analogWrite() 없음 → ledcAttach(pin, freq, res) + ledcWrite(pin, duty) 사용',
|
||||
'- loop()에서 오래 blocking하면 watchdog(TG1WDT) 리셋 발생 → delay() 또는 yield() 필수',
|
||||
'- SPIFFS 대신 LittleFS 권장 (esp32 3.x 코어)',
|
||||
];
|
||||
if (usesWifi) lines.splice(1, 0,
|
||||
'- WiFi는 SoC 내장 — #include <SPI.h> 절대 추가하지 말 것',
|
||||
'- #include <WiFi.h> 하나로 충분 (esp32 코어 제공)',
|
||||
'- WiFi.firmwareVersion() 없음 (WiFiNINA 전용)');
|
||||
return '\n' + lines.join('\n');
|
||||
}
|
||||
if (arch === 'esp8266') {
|
||||
const lines = [
|
||||
'ESP8266 코딩 규칙 (반드시 준수):',
|
||||
'- analogWrite() 있음 (PWM, 0-1023)',
|
||||
'- loop()에서 yield() 또는 delay() 호출 필수 (watchdog 방지)',
|
||||
'- SPI.h / ESP32Servo.h / ledcAttach 등 ESP32 전용 API 사용 금지',
|
||||
];
|
||||
if (usesWifi) lines.splice(1, 0,
|
||||
'- WiFi: #include <ESP8266WiFi.h> — #include <WiFi.h> 절대 사용 금지',
|
||||
'- WiFi 암호화 상수: ENC_TYPE_NONE / ENC_TYPE_CCMP / ENC_TYPE_TKIP 사용 (WIFI_AUTH_OPEN 등 ESP32 상수 금지)',
|
||||
'- WiFi 스캔: WiFi.scanNetworks() / WiFi.getNetworkInfo() / WiFi.getScanInfoByIndex() / bss_info 사용');
|
||||
return '\n' + lines.join('\n');
|
||||
}
|
||||
if (arch === 'avr') {
|
||||
const lines = ['AVR(Arduino) 코딩 규칙:'];
|
||||
if (usesWifi) lines.push('- WiFi 실드: #include <SPI.h> + #include <WiFiNINA.h> 또는 #include <WiFi.h>');
|
||||
return '\n' + lines.join('\n');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async function csbCompileOnly() {
|
||||
if (!_csbSelBoard) { codeAiAppend('system', '⚠ 보드를 먼저 선택하세요.'); return; }
|
||||
const file = codeFiles.find(f => f.id === activeCodeFileId);
|
||||
if (!file || !file.name.endsWith('.ino')) { codeAiAppend('system', '⚠ .ino 파일을 선택하세요.'); return; }
|
||||
const code = file.model?.getValue() || file.content || '';
|
||||
const btn = document.getElementById('code-compile-btn');
|
||||
if (btn) { btn.textContent = '⏳ 컴파일 중…'; btn.disabled = true; }
|
||||
codeAiAppend('system', `🔨 컴파일 중... [${_csbSelBoard.name}]`);
|
||||
try {
|
||||
const r = await fetch('/api/arduino/compile', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||||
body: JSON.stringify({ code, fqbn: _csbSelBoard.fqbn }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.error) {
|
||||
sendCompileErrorToAI(data.error, file.name, code);
|
||||
} else {
|
||||
const kb = data.size ? (data.size / 1024).toFixed(1) : null;
|
||||
const extra = data.autoInstalled?.length ? `\n자동 설치된 라이브러리: ${data.autoInstalled.join(', ')}` : '';
|
||||
codeAiAppend('system', `✅ 컴파일 성공${kb ? ` (${kb} KB)` : ''} — 오류 없음.${extra}`);
|
||||
}
|
||||
} catch (e) {
|
||||
codeAiAppend('system', `컴파일 오류: ${e.message}`);
|
||||
} finally {
|
||||
if (btn) { btn.textContent = '🔨 컴파일'; btn.disabled = false; }
|
||||
}
|
||||
}
|
||||
|
||||
function sendCompileErrorToAI(error, filename, code) {
|
||||
const inp = document.getElementById('code-ai-input');
|
||||
if (!inp) return;
|
||||
inp.value = `아두이노 컴파일 오류가 발생했습니다. Read로 ${filename}을 읽은 뒤 Edit 또는 Write 도구로 파일을 직접 수정해줘.\n\n오류:\n\`\`\`\n${error}\n\`\`\``;
|
||||
const boardInfo = `대상 보드: ${_csbSelBoard.name} (FQBN: ${_csbSelBoard.fqbn})`;
|
||||
const hints = _boardSpecificHints(code);
|
||||
// Library conflict (redefinition) — not fixable by editing the .ino file
|
||||
if (/redefinition of/.test(error)) {
|
||||
const m = error.match(/\/sketch\/(\S+?\.h)/);
|
||||
const conflictHeader = m ? m[1] : 'unknown.h';
|
||||
inp.value = `아두이노 컴파일 오류: **라이브러리 충돌**입니다. ${boardInfo}${hints}\n\n원인: \`${conflictHeader}\`가 코어 내장 헤더와 중복 정의(redefinition) 충돌을 일으키고 있습니다. ${filename} 코드의 \`#include\` 방향을 조정하거나 외부 라이브러리를 교체해야 합니다.\n\n오류:\n\`\`\`\n${error}\n\`\`\`\n\n해결 방법을 설명해주세요. 파일 검색은 하지 않아도 됩니다.`;
|
||||
} else {
|
||||
inp.value = `아두이노 컴파일 오류가 발생했습니다. ${boardInfo}${hints}\n\nRead로 ${filename}을 읽은 뒤 Edit 또는 Write 도구로 파일을 직접 수정해줘.\n\n오류:\n\`\`\`\n${error}\n\`\`\``;
|
||||
}
|
||||
codeAiSend();
|
||||
}
|
||||
|
||||
@@ -2711,6 +2787,9 @@ async function _codeAiOneRound(messages, onToken) {
|
||||
function _buildCodeSystemPrompt() {
|
||||
const localNote = localDirHandle ? `로컬 폴더 "${localDirHandle.name}"이 연결되어 있습니다.` : '로컬 폴더가 연결되지 않았습니다.';
|
||||
const summaryBlock = _historySummary ? `\n[이전 대화 요약]\n${_historySummary}\n` : '';
|
||||
const _activeCode = (() => { try { const f = codeFiles.find(f => f.id === activeCodeFileId); return f?.model?.getValue() || f?.content || ''; } catch { return ''; } })();
|
||||
const boardHints = _boardSpecificHints(_activeCode);
|
||||
const boardBlock = boardHints ? `\n## 대상 보드: ${_csbSelBoard.name} (${_csbSelBoard.fqbn})${boardHints}\n` : '';
|
||||
const karpathyBlock = _karpathyMode ? `
|
||||
## Karpathy 코딩 가이드라인 (필수)
|
||||
1. Think Before Coding — 가정을 명시하고, 모호하면 질문하세요. 여러 해석 가능 시 제시 후 선택하세요.
|
||||
@@ -2718,7 +2797,7 @@ function _buildCodeSystemPrompt() {
|
||||
3. Surgical Changes — 사용자 요청과 직접 관련 없는 코드는 건드리지 마세요. 기존 스타일을 그대로 따르세요. 내 변경으로 인한 고아 코드만 제거하세요.
|
||||
4. Goal-Driven Execution — 작업을 검증 가능한 목표로 변환하세요. "버그 수정" → "재현 테스트 작성 → 통과".
|
||||
` : '';
|
||||
return `당신은 전문 코드 어시스턴트입니다. ${localNote}${summaryBlock}${karpathyBlock}
|
||||
return `당신은 전문 코드 어시스턴트입니다. ${localNote}${summaryBlock}${boardBlock}${karpathyBlock}
|
||||
|
||||
## 툴 사용 지침
|
||||
- 파일을 수정하기 전에 반드시 Read로 현재 내용을 확인하세요.
|
||||
@@ -2726,6 +2805,7 @@ function _buildCodeSystemPrompt() {
|
||||
- **Write**: 수정이 3곳 이상이거나 전체 리팩토링·일괄 변경 시 사용. Read로 읽은 내용을 수정한 뒤 Write로 교체하세요. Edit 반복보다 훨씬 빠릅니다.
|
||||
- Glob으로 파일 목록 검색, Grep으로 코드 내용 검색이 가능합니다.
|
||||
- Bash는 companion.py 연결 시 터미널 명령을 실행합니다.
|
||||
- **파일을 2번 검색해도 없으면 즉시 멈추세요.** 같은 패턴으로 반복 검색하지 말고, 파일이 없다는 사실을 사용자에게 설명한 뒤 다른 해결책을 제안하세요.
|
||||
|
||||
## 파일 경로 규칙 (중요)
|
||||
- 파일 경로에 절대경로·Windows 경로를 절대 사용하지 마세요: Documents/, Desktop/, C:\\, /home/ 등 금지.
|
||||
@@ -4145,6 +4225,8 @@ function setCodeSbTab(tab) {
|
||||
|
||||
// ── 라이브러리/보드 매니저 (code editor sidebar) ──────────────────────────────
|
||||
let _csbLibCurrentTab = 'installed';
|
||||
let _csbLibFilterMode = 'all'; // 'all' | 'compat' | 'incompat'
|
||||
let _csbLibCache = [];
|
||||
let _csbArdSection = 'lib';
|
||||
|
||||
function csbArdSection(sec) {
|
||||
@@ -4167,11 +4249,26 @@ function csbLibTab(tab) {
|
||||
});
|
||||
const sr = document.getElementById('csb-lib-search-row');
|
||||
if (sr) sr.style.display = tab === 'search' ? 'flex' : 'none';
|
||||
const fr = document.getElementById('csb-lib-filter-row');
|
||||
if (fr) fr.style.display = tab === 'installed' ? 'flex' : 'none';
|
||||
document.getElementById('csb-lib-status').textContent = '';
|
||||
if (tab === 'installed') csbLibLoad();
|
||||
else _csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">키워드를 입력하세요.</div>';
|
||||
}
|
||||
|
||||
function csbLibFilter(mode) {
|
||||
_csbLibFilterMode = mode;
|
||||
['all','compat','incompat'].forEach(m => {
|
||||
const btn = document.getElementById('csb-libf-' + m);
|
||||
if (!btn) return;
|
||||
const active = m === mode;
|
||||
btn.style.background = active ? 'var(--brand)' : 'var(--panel-2)';
|
||||
btn.style.color = active ? '#fff' : 'var(--muted)';
|
||||
btn.style.borderColor = active ? 'var(--brand)' : 'var(--line)';
|
||||
});
|
||||
_csbLibRenderInstalled(_csbLibCache);
|
||||
}
|
||||
|
||||
function _csbLibListEl() { return document.getElementById('csb-lib-list'); }
|
||||
function _csbLibStatus(msg) { const el = document.getElementById('csb-lib-status'); if (el) el.textContent = msg; }
|
||||
function _csbEsc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
|
||||
@@ -4179,36 +4276,95 @@ function _csbEsc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<'
|
||||
async function csbLibLoad() {
|
||||
_csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>';
|
||||
try {
|
||||
const r = await fetch('/api/arduino/libs', { headers: authHeaders() });
|
||||
const fqbn = _csbSelBoard?.fqbn ? '&fqbn=' + encodeURIComponent(_csbSelBoard.fqbn) : '';
|
||||
const r = await fetch('/api/arduino/libs?all=1' + fqbn, { headers: authHeaders() });
|
||||
const data = await r.json();
|
||||
_csbLibRenderInstalled(data.libs || []);
|
||||
_csbLibCache = data.libs || [];
|
||||
_csbLibRenderInstalled(_csbLibCache);
|
||||
} catch { _csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
|
||||
}
|
||||
|
||||
function _csbArchBadge(architectures) {
|
||||
const arch = _csbSelBoard.fqbn.split(':')[1] || '';
|
||||
const archs = architectures || [];
|
||||
const isAll = archs.includes('*') || archs.length === 0;
|
||||
const compat = isAll || archs.some(a => a === arch || a.startsWith(arch) || arch.startsWith(a));
|
||||
if (isAll) return `<span style="font-size:9px;padding:1px 5px;border-radius:3px;background:#1a2a1a;color:#6ee7b7;border:1px solid #2d4a2d">모든 보드</span>`;
|
||||
const label = archs.slice(0,3).join(', ') + (archs.length>3?'…':'');
|
||||
const color = compat ? '#6ee7b7' : '#f87171';
|
||||
const bg = compat ? '#1a2a1a' : '#2a1a1a';
|
||||
const bd = compat ? '#2d4a2d' : '#4a2d2d';
|
||||
const icon = compat ? '✓' : '✗';
|
||||
return `<span style="font-size:9px;padding:1px 5px;border-radius:3px;background:${bg};color:${color};border:1px solid ${bd}" title="호환 아키텍처: ${archs.join(', ')}">${icon} ${_csbEsc(label)}</span>`;
|
||||
}
|
||||
|
||||
function _csbIsCompat(l) {
|
||||
const arch = _csbSelBoard.fqbn.split(':')[1] || '';
|
||||
const archs = l.architectures || [];
|
||||
return archs.includes('*') || archs.length === 0 || archs.some(a => a === arch || a.startsWith(arch) || arch.startsWith(a));
|
||||
}
|
||||
|
||||
function _csbLibRenderInstalled(libs) {
|
||||
const el = _csbLibListEl();
|
||||
if (!libs.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">설치된 라이브러리 없음</div>'; return; }
|
||||
el.innerHTML = libs.map(l => {
|
||||
|
||||
// Apply filter
|
||||
let filtered = libs;
|
||||
if (_csbLibFilterMode === 'compat') filtered = libs.filter(l => _csbIsCompat(l));
|
||||
if (_csbLibFilterMode === 'incompat') filtered = libs.filter(l => !_csbIsCompat(l));
|
||||
|
||||
// Sort: platform/core first, then compatible, then incompatible
|
||||
if (_csbLibFilterMode === 'all') {
|
||||
filtered = [...libs].sort((a, b) => {
|
||||
const pa = a.location === 'platform', pb = b.location === 'platform';
|
||||
if (pa && !pb) return -1;
|
||||
if (!pa && pb) return 1;
|
||||
const ca = _csbIsCompat(a), cb = _csbIsCompat(b);
|
||||
if (ca && !cb) return -1;
|
||||
if (!ca && cb) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
|
||||
if (!filtered.length) {
|
||||
const msg = _csbLibFilterMode === 'compat' ? `${_csbSelBoard.name}과 호환되는 라이브러리 없음` : '비호환 라이브러리 없음';
|
||||
el.innerHTML = `<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">${msg}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const fqbn = _csbSelBoard?.fqbn || '';
|
||||
el.innerHTML = filtered.map(l => {
|
||||
const sid = 'csbex_' + l.name.replace(/\W/g,'_');
|
||||
const archs = l.architectures || [];
|
||||
const compat = _csbIsCompat(l);
|
||||
const isPlatform = l.location === 'platform';
|
||||
const incompatWarn = !compat ? `<div style="font-size:10px;color:#f87171;margin-top:3px">⚠ 현재 보드(${_csbEsc(_csbSelBoard.name)})와 호환되지 않을 수 있음</div>` : '';
|
||||
const platformBadge = isPlatform ? `<span style="font-size:9px;padding:1px 5px;border-radius:3px;background:#1a2a3a;color:#60a5fa;border:1px solid #2d4a6a">코어</span>` : '';
|
||||
const exRows = (l.examples||[]).map(ex =>
|
||||
`<div style="display:flex;align-items:center;padding:3px 0;gap:6px">
|
||||
<span style="font-size:10px;color:var(--muted);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">📄 ${_csbEsc(ex)}</span>
|
||||
<button onclick="csbLibOpenExample('${_csbEsc(l.name)}','${_csbEsc(ex)}')"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:var(--brand);border-radius:4px;cursor:pointer;font-family:inherit;white-space:nowrap">열기</button>
|
||||
<button onclick="csbLibOpenExample('${_csbEsc(l.name)}','${_csbEsc(ex)}',${compat},'${_csbEsc(fqbn)}')"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:${compat?'var(--brand)':'#f87171'};border-radius:4px;cursor:pointer;font-family:inherit;white-space:nowrap"
|
||||
title="${compat?'':'⚠ 비호환 예제 — 열면 AI가 자동으로 변환해드립니다'}">
|
||||
${compat?'열기':'열기 ⚠'}</button>
|
||||
</div>`).join('');
|
||||
const hasEx = l.examples?.length > 0;
|
||||
return `<div style="padding:8px 10px;border-bottom:1px solid var(--line)">
|
||||
return `<div style="padding:8px 10px;border-bottom:1px solid var(--line)${!compat?';border-left:2px solid #7f1d1d':''}">
|
||||
<div style="display:flex;align-items:flex-start;gap:6px">
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(l.name)}</div>
|
||||
<div style="display:flex;align-items:center;gap:5px;flex-wrap:wrap">
|
||||
<span style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(l.name)}</span>
|
||||
${platformBadge}${_csbArchBadge(archs)}
|
||||
</div>
|
||||
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(l.author||'')} · v${_csbEsc(l.version||'?')}${l.category?' · '+_csbEsc(l.category):''}</div>
|
||||
${l.sentence?`<div style="font-size:10px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${_csbEsc(l.sentence)}</div>`:''}
|
||||
${incompatWarn}
|
||||
</div>
|
||||
<div style="display:flex;gap:4px;flex-shrink:0">
|
||||
${hasEx?`<button onclick="document.getElementById('${sid}').style.display=document.getElementById('${sid}').style.display==='none'?'block':'none'"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:var(--muted);border-radius:4px;cursor:pointer;font-family:inherit">예제</button>`:''}
|
||||
<button onclick="csbLibUninstall('${_csbEsc(l.name)}')"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:#f87171;border-radius:4px;cursor:pointer;font-family:inherit">삭제</button>
|
||||
${isPlatform?'':`<button onclick="csbLibUninstall('${_csbEsc(l.name)}')"
|
||||
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:#f87171;border-radius:4px;cursor:pointer;font-family:inherit">삭제</button>`}
|
||||
</div>
|
||||
</div>
|
||||
${hasEx?`<div id="${sid}" style="display:none;margin-top:5px;padding:5px 8px;background:var(--panel-2);border-radius:5px">${exRows}</div>`:''}
|
||||
@@ -4216,15 +4372,25 @@ function _csbLibRenderInstalled(libs) {
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function csbLibOpenExample(libName, exName) {
|
||||
async function csbLibOpenExample(libName, exName, isCompat = true, fqbn = '') {
|
||||
_csbLibStatus(`예제 로딩: ${exName}...`);
|
||||
try {
|
||||
const r = await fetch(`/api/arduino/libs/example?lib=${encodeURIComponent(libName)}&example=${encodeURIComponent(exName)}`, { headers: authHeaders() });
|
||||
const fqbnParam = (fqbn || _csbSelBoard?.fqbn || '');
|
||||
const r = await fetch(`/api/arduino/libs/example?lib=${encodeURIComponent(libName)}&example=${encodeURIComponent(exName)}&fqbn=${encodeURIComponent(fqbnParam)}`, { headers: authHeaders() });
|
||||
const data = await r.json();
|
||||
if (data.error) { _csbLibStatus('오류: ' + data.error); return; }
|
||||
_csbLibStatus('');
|
||||
codeNewFile(data.name, data.code);
|
||||
setCodeSbTab('files');
|
||||
if (!isCompat) {
|
||||
// Auto-send to AI for board adaptation
|
||||
const hints = _boardSpecificHints(data.code);
|
||||
const inp = document.getElementById('code-ai-input');
|
||||
if (inp) {
|
||||
inp.value = `이 예제는 ${libName} 라이브러리용이지만 현재 보드(${_csbSelBoard.name}, FQBN: ${_csbSelBoard.fqbn})와 호환되지 않을 수 있어.\n${hints}\n\nRead로 ${data.name}을 읽은 뒤 이 보드에서 동작하도록 코드를 수정해줘. 호환되지 않는 API는 이 보드에 맞는 방식으로 교체해.`;
|
||||
codeAiSend();
|
||||
}
|
||||
}
|
||||
} catch(e) { _csbLibStatus('오류: ' + e.message); }
|
||||
}
|
||||
|
||||
@@ -4246,9 +4412,10 @@ function _csbLibRenderSearch(libs) {
|
||||
el.innerHTML = libs.map(l => `
|
||||
<div style="padding:8px 10px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;gap:6px">
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="display:flex;align-items:center;gap:5px">
|
||||
<div style="display:flex;align-items:center;gap:5px;flex-wrap:wrap">
|
||||
<span style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(l.name)}</span>
|
||||
${l.category?`<span style="font-size:9px;padding:1px 5px;border:1px solid var(--line);border-radius:3px;color:var(--muted)">${_csbEsc(l.category)}</span>`:''}
|
||||
${_csbArchBadge(l.architectures||[])}
|
||||
</div>
|
||||
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(l.author||'')} · v${_csbEsc(l.version||'?')}</div>
|
||||
${l.sentence?`<div style="font-size:10px;color:var(--muted);margin-top:2px">${_csbEsc(l.sentence)}</div>`:''}
|
||||
@@ -5027,7 +5194,7 @@ function _applyHideBlankLines() {
|
||||
if (l.includes(blockEnd)) inBlock = false;
|
||||
} else if (l === '') {
|
||||
hide = true;
|
||||
} else if (/^(\/\/|#|--|;)/.test(l)) {
|
||||
} else if (/^(\/\/|--|;)/.test(l) || /^#(?!include|define|ifdef|ifndef|endif|else|elif|pragma|undef|error|warning)/.test(l)) {
|
||||
hide = true;
|
||||
} else if (l.startsWith('/*')) {
|
||||
hide = true;
|
||||
|
||||
Reference in New Issue
Block a user