about.js (5333B)
1 import { getWakaTimeData, subscribeWakaTimeRefresh } from "./wakatime.js"; 2 3 let unsubscribeAboutRefresh = null; 4 5 export function initAbout() { 6 initSSHTipCopy(); 7 8 const statsContainer = document.getElementById("activity-stats"); 9 const section = document.getElementById("activity-section"); 10 if (!statsContainer || !section) return; 11 12 function hideActivity() { 13 section.style.display = "none"; 14 } 15 16 async function loadActivityData(data = null) { 17 if (!section.isConnected) { 18 if (unsubscribeAboutRefresh) { 19 unsubscribeAboutRefresh(); 20 unsubscribeAboutRefresh = null; 21 } 22 return; 23 } 24 25 try { 26 const stats = data || (await getWakaTimeData()); 27 28 if (!stats || !stats.Weekly || !Array.isArray(stats.Weekly.Days)) { 29 hideActivity(); 30 return; 31 } 32 33 const totalText = stats.Weekly.TotalText; 34 const dayBars = stats.Weekly.Days; 35 if (!totalText || dayBars.length === 0) { 36 hideActivity(); 37 return; 38 } 39 40 const days = dayBars.map((bar) => ({ 41 dayText: (bar.DayText || "").toLowerCase(), 42 timeText: bar.TimeText || "", 43 barWidthPercent: 44 bar.BarWidthPercent && typeof bar.BarWidthPercent === "string" 45 ? bar.BarWidthPercent.trim() 46 : "0%", 47 })); 48 49 renderWeeklyStats(statsContainer, totalText, days); 50 } catch { 51 hideActivity(); 52 } 53 } 54 55 loadActivityData(); 56 57 if (unsubscribeAboutRefresh) { 58 unsubscribeAboutRefresh(); 59 } 60 unsubscribeAboutRefresh = subscribeWakaTimeRefresh((data) => { 61 void loadActivityData(data); 62 }); 63 } 64 65 function initSSHTipCopy() { 66 const copyButton = document.querySelector("[data-ssh-copy]"); 67 const commandElement = document.querySelector("[data-ssh-command]"); 68 if (!copyButton || !commandElement || copyButton.dataset.bound === "true") return; 69 70 copyButton.dataset.bound = "true"; 71 copyButton.addEventListener("click", async () => { 72 const command = (commandElement.textContent || "").trim(); 73 if (!command) return; 74 75 const copied = await copyToClipboard(command); 76 const originalLabel = copyButton.textContent || "copy"; 77 copyButton.textContent = copied ? "copied" : "failed"; 78 copyButton.classList.toggle("copied", copied); 79 80 window.setTimeout(() => { 81 if (!copyButton.isConnected) return; 82 copyButton.textContent = originalLabel; 83 copyButton.classList.remove("copied"); 84 }, 1200); 85 }); 86 } 87 88 async function copyToClipboard(text) { 89 if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") { 90 try { 91 await navigator.clipboard.writeText(text); 92 return true; 93 } catch { 94 return false; 95 } 96 } 97 98 const input = document.createElement("textarea"); 99 input.value = text; 100 input.setAttribute("readonly", ""); 101 input.style.position = "absolute"; 102 input.style.left = "-9999px"; 103 document.body.appendChild(input); 104 input.select(); 105 const copied = document.execCommand("copy"); 106 document.body.removeChild(input); 107 return copied; 108 } 109 110 function renderWeeklyStats(statsContainer, totalText, days) { 111 statsContainer.innerHTML = ""; 112 113 const width = 24; 114 const pre = document.createElement("pre"); 115 pre.className = "weekly-ascii"; 116 117 const totalLabel = document.createElement("span"); 118 totalLabel.className = "weekly-total-label"; 119 totalLabel.textContent = "total: "; 120 121 const totalValue = document.createElement("span"); 122 totalValue.className = "weekly-total-value"; 123 totalValue.textContent = totalText; 124 125 pre.appendChild(totalLabel); 126 pre.appendChild(totalValue); 127 pre.appendChild(document.createTextNode("\n\n")); 128 129 days.forEach((day, index) => { 130 const label = (day.dayText || "").slice(0, 3).padEnd(3, " "); 131 const percent = clampPercent(day.barWidthPercent); 132 const { filled, empty } = asciiBarSegments(percent, width); 133 134 const dayLabel = document.createElement("span"); 135 dayLabel.className = "weekly-day-label"; 136 dayLabel.textContent = `${label} `; 137 138 const barFrame = document.createElement("span"); 139 barFrame.className = "weekly-day-bar-frame"; 140 barFrame.appendChild(document.createTextNode("|")); 141 142 const barFilled = document.createElement("span"); 143 barFilled.className = "weekly-day-bar-filled"; 144 barFilled.textContent = filled; 145 146 const barEmpty = document.createElement("span"); 147 barEmpty.className = "weekly-day-bar-empty"; 148 barEmpty.textContent = empty; 149 150 const dayTime = document.createElement("span"); 151 dayTime.className = "weekly-day-time"; 152 dayTime.textContent = ` ${day.timeText}`; 153 154 barFrame.appendChild(barFilled); 155 barFrame.appendChild(barEmpty); 156 barFrame.appendChild(document.createTextNode("|")); 157 158 pre.appendChild(dayLabel); 159 pre.appendChild(barFrame); 160 pre.appendChild(dayTime); 161 162 if (index < days.length - 1) { 163 pre.appendChild(document.createTextNode("\n")); 164 } 165 }); 166 167 statsContainer.appendChild(pre); 168 } 169 170 function clampPercent(value) { 171 const parsed = Number.parseFloat((value || "0").replace("%", "")); 172 if (!Number.isFinite(parsed)) return 0; 173 if (parsed < 0) return 0; 174 if (parsed > 100) return 100; 175 return parsed; 176 } 177 178 function asciiBarSegments(percent, width) { 179 const filled = Math.round((percent / 100) * width); 180 return { 181 filled: "#".repeat(filled), 182 empty: "-".repeat(width - filled), 183 }; 184 }