CoolFace
Apppublic

LZ-SG/embedded-dev-tutorials

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
upgrade_v170.py239 linesDownload Raw Back to root
1#!/usr/bin/env python32"""SPA v1.7.0: Syntax highlighting + breadcrumb + related tutorials + copy toast"""3import re4 5PATH = r"C:\Users\LYS\.qclaw\workspace\embedded-dev-site\static\index.html"6with open(PATH, 'r', encoding='utf-8') as f:7    spa = f.read()8 9# ====== 1. Add CSS for new features ======10new_css = '''11/* ══ v1.7.0: 语法高亮 ══ */12.syn-kw{color:#ff7b72}        /* 关键字 */13.syn-pp{color:#ff7b72}        /* 预处理 */14.syn-type{color:#ffa657}      /* 类型 */15.syn-str{color:#a5d6ff}       /* 字符串 */16.syn-com{color:#8b949e;font-style:italic} /* 注释 */17.syn-num{color:#79c0ff}       /* 数字 */18.syn-fn{color:#d2a8ff}        /* 函数 */19.syn-op{color:#ff7b72}        /* 运算符 */20/* ══ v1.7.0: 面包屑 ══ */21.breadcrumb{display:flex;align-items:center;gap:6px;font-size:.78em;color:var(--t2);margin:0 0 16px;padding:6px 0;flex-wrap:wrap}22.breadcrumb a{color:var(--a)}23.breadcrumb .sep{color:var(--br)}24/* ══ v1.7.0: 复制提示 ══ */25.copy-toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:var(--g);color:#fff;padding:8px 20px;border-radius:8px;font-size:.85em;z-index:400;pointer-events:none;opacity:0;transition:opacity .3s,transform .3s}26.copy-toast.show{opacity:1;transform:translateX(-50%) translateY(4px)}27/* ══ v1.7.0: 相关教程 ══ */28.related-tutorials{margin:24px 0;padding:16px 0;border-top:1px solid var(--br)}29.related-tutorials h3{font-size:.95em;color:var(--a);margin-bottom:10px}30.related-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px}31.related-card{background:var(--bg2);border:1px solid var(--br);border-radius:8px;padding:10px 14px;cursor:pointer;transition:border-color .2s;font-size:.82em}32.related-card:hover{border-color:var(--a)}33.related-card .rt-tag{display:inline-block;font-size:.65em;padding:1px 6px;border-radius:4px;margin-right:4px;background:var(--bg3);color:var(--t2)}34@media(max-width:600px){.related-grid{grid-template-columns:1fr}}35'''36 37spa = spa.replace('/* ══ v1.6.4: 快捷键面板 ══ */', new_css + '\n/* ══ v1.6.4: 快捷键面板 ══ */')38 39# ====== 2. Add copy toast + breadcrumb HTML ======40toast_html = '<div class="copy-toast" id="copyToast">✅ 代码已复制!</div>\n'41spa = spa.replace('<div class="shortcut-hint"', toast_html + '<div class="shortcut-hint"')42 43# ====== 3. Add JS functions ======44 45# ---- 3a. Syntax highlighting ----46syntax_js = '''47// ══ v1.7.0: 语法高亮 ══48const C_KEYWORDS = new Set(['auto','break','case','char','const','continue','default','do','double','else','enum','extern','float','for','goto','if','inline','int','long','register','return','short','signed','sizeof','static','struct','switch','typedef','union','unsigned','void','volatile','while']);49const C_TYPES = new Set(['uint8_t','uint16_t','uint32_t','uint64_t','int8_t','int16_t','int32_t','int64_t','size_t','ssize_t','bool','true','false','NULL','HAL_StatusTypeDef','esp_err_t','QueueHandle_t','SemaphoreHandle_t','TimerHandle_t','TaskHandle_t','GPIO_TypeDef','ADC_HandleTypeDef','UART_HandleTypeDef','I2C_HandleTypeDef','SPI_HandleTypeDef','TIM_HandleTypeDef','DMA_HandleTypeDef','RTC_HandleTypeDef','uintptr_t','float32_t']);50const C_PP = new Set(['#include','#define','#ifdef','#ifndef','#if','#else','#elif','#endif','#pragma','#error','#warning','#undef']);51const MAKEFILE_KW = new Set(['all','clean','flash','debug','release','.PHONY','ifeq','ifneq','ifdef','ifndef','else','endif','include','export','override','define','endef','eval','call','foreach','wildcard','patsubst','notdir','dir','addprefix','addsuffix','shell','subst','filter','strip','sort','word','words','firstword','lastword','error','warning','info']);52 53function highlightCodeBlocks(container) {54  container.querySelectorAll('pre code').forEach(el => {55    if (el.classList.contains('hl-done')) return;56    el.classList.add('hl-done');57    let text = el.innerHTML;58    if (text.includes('<span class="syn-')) return; // already highlighted59    60    const firstLine = text.split('\n')[0] || '';61    const isMakefile = firstLine.includes('# ') && (text.includes(' = ') || text.includes(':\t') || text.includes('$(')) && !text.includes('{');62    const isCPP = !isMakefile && (text.includes('#include') || text.includes('HAL_') || text.includes('void ') || text.includes('uint8_t') || text.includes('typedef') || text.includes('int main'));63    64    if (isMakefile) {65      text = text.replace(/(^|\n)(\s*)([.\/\\\w-]+)\s*:/gm, '$1$2<span class="syn-fn">$3</span>:');66      text = text.replace(/\$\((\w+)\)/g, '<span class="syn-kw">$$($1)</span>');67      text = text.replace(/\$[@<^+?*]/g, '<span class="syn-num">$&</span>');68      text = text.replace(/(^|\n)(\s*)(\w+)\t/g, '$1$2$3\t');69      text = text.replace(/^(\s*)(#.*)$/gm, '$1<span class="syn-com">$2</span>');70    } else if (isCPP) {71      // Strings72      text = text.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g, '<span class="syn-str">$&</span>');73      // Comments74      text = text.replace(/(\/\/.*$)/gm, '<span class="syn-com">$1</span>');75      text = text.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="syn-com">$1</span>');76      // Preprocessor77      text = text.replace(/^(\s*#\s*\w+)/gm, '<span class="syn-pp">$1</span>');78      // Numbers79      text = text.replace(/\b(0x[0-9a-fA-F]+|\d+\.?\d*(?:[eE][+-]?\d+)?[uUlLfF]*)\b/g, '<span class="syn-num">$1</span>');80      // Types81      C_TYPES.forEach(t => { const r = new RegExp('\\b'+t+'\\b','g'); text = text.replace(r, '<span class="syn-type">'+t+'</span>'); });82      // Keywords83      C_KEYWORDS.forEach(k => { const r = new RegExp('\\b'+k+'\\b','g'); text = text.replace(r, '<span class="syn-kw">'+k+'</span>'); });84      // Functions (identifier followed by opening paren)85      text = text.replace(/\b([a-zA-Z_]\w+)\s*\(/g, (m, name) => {86        if (['if','for','while','switch','return','sizeof','typeof'].includes(name)) return m;87        return `<span class="syn-fn">${name}</span>(`;88      });89    }90    el.innerHTML = text;91  });92}93'''94 95# Insert after searchCache declaration96spa = spa.replace("let searchCache = {};", "let searchCache = {};\n" + syntax_js)97 98# ---- 3b. Breadcrumb ----99breadcrumb_js = '''100// ══ v1.7.0: 面包屑 ══101const BREADCRUMB_MAP = {};102function buildBreadcrumbMap() {103  for (let i=0; i<=13; i++) {104    const id = `guide-ch${String(i).padStart(2,'0')}`;105    BREADCRUMB_MAP[id] = { section: '小白完全指南', title: `第${i}章` };106  }107  // Also add from cardsData108  if (typeof cardsData !== 'undefined') cardsData.forEach(c => {109    if (!BREADCRUMB_MAP[c.id]) BREADCRUMB_MAP[c.id] = { section: c.section || '教程', title: c.title };110  });111}112function renderBreadcrumb(pageId) {113  if (!BREADCRUMB_MAP[pageId]) return '';114  const b = BREADCRUMB_MAP[pageId];115  return `<div class="breadcrumb"><a href="#" onclick="loadPage('home')">🏠 首页</a><span class="sep">›</span><span>${b.section}</span><span class="sep">›</span><span>${b.title}</span></div>`;116}117setTimeout(buildBreadcrumbMap, 3000);118'''119 120spa = spa.replace("setTimeout(buildSearchIndex, 2500);", "setTimeout(buildSearchIndex, 2500);\nsetTimeout(buildBreadcrumbMap, 3500);" + breadcrumb_js)121 122# ---- 3c. Related tutorials ----123related_js = '''124// ══ v1.7.0: 相关教程推荐 ══125const RELATED_TAGS = {126  'guide-ch00': ['arm-gcc','cmake','makefile'],127  'guide-ch01': ['guide-ch02','guide-ch03','cmake','makefile'],128  'guide-ch02': ['guide-ch01','guide-ch03','makefile'],129  'guide-ch03': ['guide-ch02','cmake'],130  'guide-ch04': ['proj-uart-hello','protocols','serial'],131  'guide-ch05': ['flash-debug','proj-bootloader-iap'],132  'guide-ch06': ['guide-ch04','guide-ch05','guide-ch08','proj-led-button'],133  'guide-ch07': ['guide-ch06','proj-esp32-iot','bci-2026'],134  'guide-ch08': ['guide-ch06','proj-freertos-station','freertos'],135  'guide-ch09': ['protocols','proj-can-bus','proj-i2c'],136  'guide-ch10': ['guide-ch11','proj-low-power'],137  'guide-ch11': ['guide-ch10','proj-low-power'],138  'guide-ch12': ['proj-dht11-monitor','guide-ch09'],139  'guide-ch13': ['proj-smart-car','proj-freertos-station','practice-projects'],140  'proj-led-button': ['guide-ch06','proj-uart-hello'],141  'proj-uart-hello': ['guide-ch04','proj-led-button'],142  'proj-freertos-station': ['guide-ch08','freertos'],143  'proj-smart-car': ['guide-ch13','proj-pwm-motor'],144  'proj-esp32-iot': ['guide-ch07','bci-2026'],145  'bci-2026': ['guide-ch07','practice-projects'],146};147function renderRelated(pageId) {148  const ids = RELATED_TAGS[pageId];149  if (!ids) return '';150  const cards = ids.map(id => {151    const c = (typeof cardsData !== 'undefined' && cardsData.find(x => x.id === id))152           || (typeof cfgMap !== 'undefined' && cfgMap[id]);153    const title = c ? (c.title || c.label || id) : id;154    const tag = (id.startsWith('guide-') ? '指南' : id.startsWith('proj-') ? '实战' : '专题');155    return `<div class="related-card" onclick="loadPage('${id}')"><span class="rt-tag">${tag}</span>${title}</div>`;156  }).join('');157  return `<div class="related-tutorials"><h3>📎 相关教程</h3><div class="related-grid">${cards}</div></div>`;158}159'''160 161spa = spa.replace("function renderBreadcrumb(pageId) {", related_js + "\nfunction renderBreadcrumb(pageId) {")162 163# ---- 3d. Copy toast ----164# Find setupCopyButtons and enhance165old_copy = '''function setupCopyButtons(container) {166  container.querySelectorAll('pre').forEach(pre => {167    if (pre.querySelector('.copy-btn')) return;168    const btn = document.createElement('button');169    btn.className = 'copy-btn';170    btn.textContent = '📋';171    btn.title = '复制代码';172    btn.onclick = function() {173      const code = pre.querySelector('code');174      const text = code ? code.textContent : pre.textContent;175      navigator.clipboard.writeText(text).then(() => {176        btn.textContent = '✅';177        setTimeout(() => btn.textContent = '📋', 2000);178      });179    };180    pre.appendChild(btn);181  });182}'''183 184new_copy = '''function setupCopyButtons(container) {185  container.querySelectorAll('pre').forEach(pre => {186    if (pre.querySelector('.copy-btn')) return;187    const btn = document.createElement('button');188    btn.className = 'copy-btn';189    btn.textContent = '📋';190    btn.title = '复制代码';191    btn.onclick = function() {192      const code = pre.querySelector('code');193      const text = code ? code.textContent : pre.textContent;194      navigator.clipboard.writeText(text).then(() => {195        btn.textContent = '✅';196        const toast = document.getElementById('copyToast');197        if (toast) { toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 1500); }198        setTimeout(() => btn.textContent = '📋', 2000);199      }).catch(() => {200        // Fallback for older browsers201        const ta = document.createElement('textarea');202        ta.value = text; ta.style.position='fixed'; ta.style.left='-9999px';203        document.body.appendChild(ta); ta.select();204        document.execCommand('copy'); document.body.removeChild(ta);205        btn.textContent = '✅';206        const toast = document.getElementById('copyToast');207        if (toast) { toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 1500); }208        setTimeout(() => btn.textContent = '📋', 2000);209      });210    };211    pre.appendChild(btn);212  });213}'''214 215spa = spa.replace(old_copy, new_copy)216 217# ---- 3e. Wire highlights + breadcrumb + related into loadContent ----218# Find where content is rendered and add our calls219old_render_line = "div.innerHTML = html;"220if old_render_line in spa:221    spa = spa.replace(old_render_line, 222        'div.innerHTML = html;\n      // v1.7.0: apply enhancements\n      setTimeout(() => {\n        setupCopyButtons(div);\n        highlightCodeBlocks(div);\n        const bc = renderBreadcrumb(currentPage); const related = renderRelated(currentPage);\n        const bcEl = div.querySelector(\'.breadcrumb\');\n        if (bc && !bcEl) { const tmp = document.createElement(\'div\'); tmp.innerHTML = bc; div.insertBefore(tmp.firstElementChild, div.firstElementChild); }\n        if (related) { const relEl = document.createElement(\'div\'); relEl.innerHTML = related; div.appendChild(relEl.firstElementChild); }\n      }, 100);')223else:224    # Try to find the right place - after content.innerHTML = html225    alt = 'contentDiv.innerHTML = html;'226    if alt in spa:227        spa = spa.replace(alt, 228            'contentDiv.innerHTML = html;\n      setTimeout(() => {\n        setupCopyButtons(contentDiv);\n        highlightCodeBlocks(contentDiv);\n        const bc = renderBreadcrumb(currentPage); const related = renderRelated(currentPage);\n        if (bc) { const tmp = document.createElement(\'div\'); tmp.innerHTML = bc; if (contentDiv.firstElementChild) contentDiv.insertBefore(tmp.firstElementChild, contentDiv.firstElementChild); }\n        if (related) { const tmp = document.createElement(\'div\'); tmp.innerHTML = related; contentDiv.appendChild(tmp.firstElementChild); }\n      }, 100);')229 230# ---- 3f. Update version ----231spa = spa.replace('v1.6.1 ·', 'v1.7.0 ·')232spa = spa.replace('v1.6.4:', 'v1.7.0:')233 234with open(PATH, 'w', encoding='utf-8', newline='') as f:235    f.write(spa)236 237print(f"SPA v1.7.0 written: {len(spa)/1024:.1f}KB")238print("Features: syntax highlighting + breadcrumb + related tutorials + copy toast")239