CoolFace
Apppublic

LZ-SG/embedded-dev-tutorials

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
patch_spa.py215 linesDownload Raw Back to root
1#!/usr/bin/env python3
2import re
3
4path = r"C:\Users\LYS\.qclaw\workspace\embedded-dev-site\static\index.html"
5with open(path, 'r', encoding='utf-8') as f:
6    content = f.read()
7
8# 1. Add "time" field to each cardsData entry
9# Replace: , size:'112KB' → , time:'~60分钟', size:'112KB'
10time_map = {
11    "main": "~60分钟", "arm-gcc": "~20分钟", "cmake": "~20分钟",
12    "makefile": "~25分钟", "flash-debug": "~30分钟", "serial": "~20分钟",
13    "stm32": "~35分钟", "esp32": "~20分钟", "freertos": "~40分钟",
14    "protocols": "~20分钟", "practice-projects": "~10分钟", "bci-2026": "~15分钟",
15    "proj-led-button": "~15分钟", "proj-uart-hello": "~15分钟",
16    "proj-oled-display": "~15分钟", "proj-dht11-monitor": "~12分钟",
17    "proj-pwm-motor": "~18分钟", "proj-servo-control": "~18分钟",
18    "proj-ir-remote": "~15分钟", "proj-ultrasonic": "~15分钟",
19    "proj-stepper-motor": "~18分钟", "proj-freertos-station": "~20分钟",
20    "proj-smart-car": "~25分钟", "proj-can-bus": "~20分钟",
21    "proj-sd-logger": "~20分钟", "proj-esp32-iot": "~20分钟",
22    "proj-low-power": "~18分钟", "proj-bootloader-iap": "~20分钟",
23    "proj-dma-adc": "~18分钟", "proj-usb-hid": "~20分钟",
24    "proj-s32k144-auto": "~20分钟", "proj-oscilloscope-debug": "~15分钟"
25}
26
27for page_id, time_val in time_map.items():
28    # Match: page:'xxx', ... other fields ... but before size:
29    pattern = rf"(page:'{page_id}',[^}}]*?), size:"
30    replacement = rf"\1, time:'{time_val}', size:"
31    content = re.sub(pattern, replacement, content)
32
33# 2. Update card rendering to show time
34content = content.replace(
35    "<div class=\"size\">${c.size}</div>",
36    "<div class=\"size\">${c.size} <span class=\"read-time\">⏱ ${c.time||''}</span></div>"
37)
38
39# 3. Add sort bar before home-grid rendering
40old_sort = "function renderCardGroup(title, items) {"
41new_sort_block = """let sortMode = 'default';
42function renderSortBar() {
43  return `<div class="sort-bar"><span class="label">排序:</span>
44    <button class="sort-btn active" onclick="sortCards('default',this)">默认</button>
45    <button class="sort-btn" onclick="sortCards('time-asc',this)">⏱ 短→长</button>
46    <button class="sort-btn" onclick="sortCards('time-desc',this)">⏱ 长→短</button>
47    <button class="sort-btn" onclick="sortCards('diff-asc',this)">📊 简单→难</button>
48    <button class="sort-btn" onclick="sortCards('name',this)">🔤 名称</button>
49  </div>`;
50}
51function sortCards(mode, btn) {
52  document.querySelectorAll('.sort-btn').forEach(b=>b.classList.remove('active'));
53  if(btn) btn.classList.add('active');
54  sortMode = mode;
55  loadPage('home');
56}
57function getSorted(items) {
58  if (sortMode === 'time-asc') return [...items].sort((a,b)=>parseInt(a.time||'99')-parseInt(b.time||'99'));
59  if (sortMode === 'time-desc') return [...items].sort((a,b)=>parseInt(b.time||'99')-parseInt(a.time||'99'));
60  if (sortMode === 'diff-asc') return [...items].sort((a,b)=>(a.diff||0)-(b.diff||0));
61  if (sortMode === 'name') return [...items].sort((a,b)=>a.title.localeCompare(b.title,'zh'));
62  return items;
63}
64function renderCardGroup(title, items) {
65  items = getSorted(items);"""
66
67content = content.replace(old_sort, new_sort_block)
68
69# 4. Add sort bar after home-stats div in renderHome
70old_stats_end = "`;</div>`;\n\n  // 分组"
71if old_stats_end in content:
72    content = content.replace(
73        old_stats_end,
74        "`;</div>`;\n\n  html += renderSortBar();\n\n  // 分组"
75    )
76
77# 5. Add bookmark JS functions before // === 初始化 ===
78bookmark_js = """// === 收藏夹 ===
79function getBookmarks() {
80  try { return JSON.parse(localStorage.getItem('tutorial_bookmarks')||'[]'); }
81  catch(e) { return []; }
82}
83function saveBookmarks(list) { localStorage.setItem('tutorial_bookmarks', JSON.stringify(list)); }
84function toggleBookmark(pageId) {
85  const bm = getBookmarks();
86  const idx = bm.indexOf(pageId);
87  if (idx >= 0) bm.splice(idx, 1);
88  else bm.push(pageId);
89  saveBookmarks(bm);
90  updateBookmarkUI();
91  renderBookmarksList();
92}
93function isBookmarked(pageId) { return getBookmarks().includes(pageId); }
94function updateBookmarkUI() {
95  const btn = document.getElementById('bmToggle');
96  if (!btn) return;
97  if (isBookmarked(currentPage)) { btn.classList.add('active'); btn.textContent = '⭐'; }
98  else { btn.classList.remove('active'); btn.textContent = '☆'; }
99}
100function renderBookmarksList() {
101  const bm = getBookmarks();
102  const section = document.getElementById('bookmarksSection');
103  const list = document.getElementById('bookmarkList');
104  const countEl = document.getElementById('bmCount');
105  if (bm.length === 0) { section.style.display = 'none'; return; }
106  section.style.display = 'block';
107  countEl.textContent = bm.length;
108  let html = '';
109  bm.forEach(id => {
110    const cfg = PAGES[id];
111    if (cfg) html += `<div class="bm-item" onclick="loadPage('${id}')">${cfg.title}<span class="bm-remove" onclick="event.stopPropagation();removeBookmark('${id}')">✕</span></div>`;
112  });
113  list.innerHTML = html;
114}
115function removeBookmark(pageId) {
116  const bm = getBookmarks().filter(id => id !== pageId);
117  saveBookmarks(bm);
118  renderBookmarksList();
119  updateBookmarkUI();
120}
121
122// === 学习进度 ===
123function markViewed(pageId) {
124  if (pageId === 'home') return;
125  try {
126    const viewed = JSON.parse(localStorage.getItem('tutorial_viewed')||'{}');
127    viewed[pageId] = new Date().toISOString();
128    localStorage.setItem('tutorial_viewed', JSON.stringify(viewed));
129  } catch(e) {}
130}
131function getViewedCount() {
132  try { return Object.keys(JSON.parse(localStorage.getItem('tutorial_viewed')||'{}')).length; }
133  catch(e) { return 0; }
134}
135function getProgressPercent() {
136  const total = Object.keys(PAGES).length - 1; // exclude home
137  return Math.round((getViewedCount() / total) * 100);
138}
139function progressIndicator() {
140  const pct = getProgressPercent();
141  return pct > 0 ? `<div class="progress-indicator" title="已读 ${pct}%"><div class="pi-bar"><div class="pi-fill" style="width:${pct}%"></div></div>${pct}%</div>` : '';
142}
143function updateHomeStats() {
144  const viewed = getViewedCount();
145  const progressHtml = progressIndicator();
146  // Update stats if on home page
147  const statsDiv = document.querySelector('.home-stats');
148  if (statsDiv) {
149    const existing = statsDiv.querySelector('.home-stat.progress-stat');
150    if (!existing && viewed > 0) {
151      const statEl = document.createElement('div');
152      statEl.className = 'home-stat progress-stat';
153      statEl.innerHTML = `<div class="num">${viewed}</div><div class="label">已学习</div><div class="progress-indicator" style="margin-top:4px"><div class="pi-bar"><div class="pi-fill" style="width:${getProgressPercent()}%"></div></div>${getProgressPercent()}%</div>`;
154      statsDiv.appendChild(statEl);
155    } else if (existing && viewed > 0) {
156      existing.querySelector('.num').textContent = viewed;
157      existing.querySelector('.pi-fill').style.width = getProgressPercent()+'%';
158      existing.querySelector('.progress-indicator').childNodes[2].textContent = getProgressPercent()+'%';
159    }
160  }
161}
162
163"""
164
165insert_at = "// === 初始化 ==="
166content = content.replace(insert_at, bookmark_js + "\n" + insert_at)
167
168# 6. Update loadPage to add bookmark button, mark viewed, and render bookmarks list
169# Add bookmark button after breadcrumb update
170old_bm = "// 关闭移动端侧边栏\n  document.getElementById('sidebar').classList.remove('open');"
171new_bm = """// 关闭移动端侧边栏
172  document.getElementById('sidebar').classList.remove('open');
173
174  // 更新收藏按钮
175  const bmToggle = document.getElementById('bmToggle');
176  if (bmToggle) {
177    if (pageId === 'home') { bmToggle.style.display = 'none'; }
178    else { bmToggle.style.display = ''; updateBookmarkUI(); }
179  }
180
181  // 标记已读
182  if (pageId !== 'home') markViewed(pageId);"""
183content = content.replace(old_bm, new_bm)
184
185# 7. Add bookmark toggle button in topbar (after search-box)
186old_search = '<input type="text" id="searchInput" placeholder="搜索教程内容..." oninput="handleSearch(this.value)">\n    </div>'
187new_search = '<input type="text" id="searchInput" placeholder="搜索教程内容..." oninput="handleSearch(this.value)">\n    </div>\n    <button class="bookmark-btn" id="bmToggle" onclick="toggleBookmark(currentPage)" title="收藏当前教程" style="display:none">☆</button>'
188content = content.replace(old_search, new_search)
189
190# 8. Update init to load bookmarks
191old_init = "loadPage('home');"
192new_init = "loadPage('home');\n  renderBookmarksList();\n  updateHomeStats();"
193content = content.replace(old_init, new_init)
194
195# 9. Add renderBookmarksList + updateHomeStats calls in loadPage for home
196old_home_return = "history.pushState({page:pageId}, '', `#/${pageId}`);\n    return;"
197new_home_return = "history.pushState({page:pageId}, '', `#/${pageId}`);\n    updateHomeStats();\n    setTimeout(() => updateHomeStats(), 200);\n    return;"
198content = content.replace(old_home_return, new_home_return)
199
200# 10. After tutorial loads, call updateBookmarkUI and show bookmark button
201old_postload = "content.appendChild(navHtml);"
202new_postload = "content.appendChild(navHtml);\n    // 显示收藏按钮\n    const bmBtn = document.getElementById('bmToggle');\n    if (bmBtn) { bmBtn.style.display = ''; updateBookmarkUI(); }"
203content = content.replace(old_postload, new_postload)
204
205with open(path, 'w', encoding='utf-8', newline='') as f:
206    f.write(content)
207
208print("Done! Statistics:")
209print(f"  Total chars: {len(content)}")
210print(f"  Has bookmark JS: {'getBookmarks' in content}")
211print(f"  Has time fields: {'time:' in content}")
212print(f"  Has sort bar: {'renderSortBar' in content}")
213print(f"  Has bmToggle: {'bmToggle' in content}")
214print(f"  Has markViewed: {'markViewed' in content}")
215