/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/main.ts var main_exports = {}; __export(main_exports, { ContributionsView: () => ContributionsView, default: () => GitHubContributionsPlugin }); module.exports = __toCommonJS(main_exports); var import_obsidian = require("obsidian"); var VIEW_TYPE = "github-contributions"; var GITHUB_GRAPHQL = "https://api.github.com/graphql"; var GITHUB_DEVICE_URL = "https://github.com/login/device/code"; var GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"; var GITHUB_USER_URL = "https://api.github.com/user"; var GITHUB_CLIENT_ID = "Ov23litfj5GbQ8mw81VV"; var MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; var AUTO_REFRESH_COOLDOWN_MS = 5 * 60 * 1e3; var DEFAULT_SETTINGS = { authMethod: "oauth", githubToken: "", githubUsername: "", dataSource: "github", localRepoRoot: "", scanDepth: 2, demoMode: false, showLegend: true, tooltipStyle: "standard", selectedYear: new Date().getFullYear(), sizePreset: "medium", defaultView: "year", palette: "classic", statsStyle: "default", dailyNoteFolder: "", dailyNoteDateFormat: "YYYY-MM-DD" }; var PRESET_SIZES = { "ultra-compact": { cell: 7, gap: 1 }, "compact": { cell: 9, gap: 1 }, "medium": { cell: 11, gap: 2 }, "large": { cell: 14, gap: 3 }, "fit": { cell: 0, gap: 2 } // sentinel - calculated at render time }; var PALETTES = { "classic": { dark: ["var(--background-modifier-border)", "#0e4429", "#006d32", "#26a641", "#39d353"], light: ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"] }, "high-contrast": { dark: ["var(--background-modifier-border)", "#1a5e2a", "#21a045", "#32d463", "#7fffb0"], light: ["#ebedf0", "#b6f0c2", "#3dd668", "#1a9e40", "#0a5c25"] }, "cobalt": { dark: ["var(--background-modifier-border)", "#0a3a6b", "#0e6eb5", "#1ab3d8", "#57e8f5"], light: ["#edf4fb", "#b3d4f0", "#4aa8e0", "#1478c8", "#064a8a"] }, "neon": { dark: ["var(--background-modifier-border)", "#2d0a4e", "#7b00c2", "#e0008c", "#ffe600"], light: ["#f5eaff", "#c97df5", "#9400d3", "#d4006a", "#c8a800"] }, "ember": { dark: ["var(--background-modifier-border)", "#3d1f00", "#b45200", "#e8820c", "#ffc832"], light: ["#f5f0e8", "#f5d5a0", "#e8820c", "#b45200", "#7a3200"] } }; function debounce(fn, ms) { let timer = 0; return (...args) => { window.clearTimeout(timer); timer = window.setTimeout(() => fn(...args), ms); }; } async function requestDeviceCode() { let res; try { res = await (0, import_obsidian.requestUrl)({ url: GITHUB_DEVICE_URL, method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, scope: "read:user" }) }); } catch (e) { throw new Error("Could not reach GitHub. Check your internet connection."); } if (res.status !== 200) throw new Error(`GitHub returned an error (${res.status}). Try again.`); return res.json; } async function pollForToken(deviceCode, intervalSecs, expiresIn, onCancel) { var _a, _b; const delay = (ms) => new Promise((r) => window.setTimeout(r, ms)); let currentInterval = Math.max(intervalSecs, 5) * 1e3; const deadline = Date.now() + expiresIn * 1e3; while (true) { if (onCancel()) throw new Error("Cancelled"); await delay(currentInterval); if (onCancel()) throw new Error("Cancelled"); if (Date.now() > deadline) throw new Error("Code expired. Please try again."); let data; try { const res = await (0, import_obsidian.requestUrl)({ url: GITHUB_TOKEN_URL, method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, device_code: deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }) }); data = res.json; } catch (e) { await delay(5e3); if (onCancel()) throw new Error("Cancelled"); continue; } if (data.access_token) return data.access_token; switch (data.error) { case "authorization_pending": continue; case "slow_down": currentInterval += 5e3; continue; case "expired_token": throw new Error("Code expired. Please try again."); case "access_denied": throw new Error("Access was denied. You can try again anytime."); case "incorrect_device_code": throw new Error("Invalid device code. Please try again."); default: throw new Error((_b = (_a = data.error_description) != null ? _a : data.error) != null ? _b : "Unknown OAuth error"); } } } async function fetchGitHubUsername(token) { const res = await (0, import_obsidian.requestUrl)({ url: GITHUB_USER_URL, headers: { Authorization: `bearer ${token}` } }); if (res.status !== 200) throw new Error("Could not fetch GitHub username"); return res.json.login; } async function fetchGitHubContributions(username, token, year) { var _a, _b, _c, _d; const from = `${year}-01-01T00:00:00Z`; const to = `${year}-12-31T23:59:59Z`; const query = `query($login:String!,$from:DateTime!,$to:DateTime!){user(login:$login){contributionsCollection(from:$from,to:$to){contributionCalendar{weeks{contributionDays{date contributionCount}}}}}}`; const res = await (0, import_obsidian.requestUrl)({ url: GITHUB_GRAPHQL, method: "POST", headers: { Authorization: `bearer ${token}`, "Content-Type": "application/json" }, body: JSON.stringify({ query, variables: { login: username, from, to } }) }); if (res.status !== 200) throw new Error(`GitHub API error: ${res.status}`); const json = res.json; if (json.errors && json.errors.length > 0) throw new Error(json.errors[0].message); const weeks = (_d = (_c = (_b = (_a = json.data) == null ? void 0 : _a.user) == null ? void 0 : _b.contributionsCollection) == null ? void 0 : _c.contributionCalendar) == null ? void 0 : _d.weeks; if (!weeks) throw new Error("No data returned. Check your username."); const map = /* @__PURE__ */ new Map(); for (const week of weeks) { for (const day of week.contributionDays) { map.set(day.date, day.contributionCount); } } return map; } var isDesktop = typeof window.require === "function"; function electronRequire(moduleName) { return window.require(moduleName); } function runGit(args, cwd) { if (!isDesktop) return ""; try { const { execSync } = electronRequire("child_process"); return execSync(`git ${args}`, { cwd, timeout: 8e3, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }); } catch (e) { return ""; } } async function discoverRepos(rootPath, maxDepth = 2) { if (!isDesktop) return []; const repos = []; try { const fs = electronRequire("fs"); const path = electronRequire("path"); const scanDir = (dir, depth) => { if (maxDepth !== 0 && depth > maxDepth) return; let entries; try { entries = fs.readdirSync(dir); } catch (e) { return; } if (entries.includes(".git")) { const name = path.basename(dir); const lastCommitRaw = runGit(`log -1 --format=%ai`, dir).trim(); const lastCommit = lastCommitRaw ? lastCommitRaw.split(" ")[0] : null; repos.push({ name, path: dir, lastCommit }); return; } for (const entry of entries) { if (entry.startsWith(".")) continue; const full = path.join(dir, entry); try { if (fs.statSync(full).isDirectory()) scanDir(full, depth + 1); } catch (e) { } } }; scanDir(rootPath, 0); } catch (e) { console.error("gh-contributions: repo discovery failed", e); } return repos; } function fetchLocalCommits(repo, year) { var _a; const map = /* @__PURE__ */ new Map(); const after = `${year - 1}-12-31`; const before = `${year + 1}-01-01`; const raw = runGit( `log --after="${after}" --before="${before}" --format=%ad --date=format:%Y-%m-%d`, repo.path ); if (!raw) return map; for (const line of raw.split("\n")) { const date = line.trim(); if (!date || !date.startsWith(String(year))) continue; map.set(date, ((_a = map.get(date)) != null ? _a : 0) + 1); } return map; } async function buildDayMap(settings, year, repos) { var _a; const days = /* @__PURE__ */ new Map(); const getOrCreate = (date) => { if (!days.has(date)) days.set(date, { date, count: 0, githubCount: 0, localCount: 0, repos: {} }); return days.get(date); }; let totalGH = 0, totalLocal = 0; if (settings.dataSource === "github" || settings.dataSource === "both") { if (settings.githubToken && settings.githubUsername) { const ghMap = await fetchGitHubContributions(settings.githubUsername, settings.githubToken, year); for (const [date, count] of ghMap) { const d = getOrCreate(date); d.githubCount = count; d.count += count; totalGH += count; } } } if (settings.dataSource === "local" || settings.dataSource === "both") { for (const repo of repos) { const commits = fetchLocalCommits(repo, year); for (const [date, count] of commits) { const d = getOrCreate(date); d.localCount += count; d.count += count; d.repos[repo.name] = ((_a = d.repos[repo.name]) != null ? _a : 0) + count; totalLocal += count; } } } return { days, totalGH, totalLocal, repoList: repos }; } function buildYearWeeks(year, days) { var _a; const jan1 = new Date(year, 0, 1); const startOffset = jan1.getDay(); const weeks = []; let week = []; for (let i = 0; i < startOffset; i++) week.push({ date: "", count: 0, githubCount: 0, localCount: 0, repos: {} }); const isLeap = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; const daysInYear = isLeap ? 366 : 365; for (let i = 0; i < daysInYear; i++) { const d = new Date(year, 0, i + 1); const dateStr = `${year}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; week.push((_a = days.get(dateStr)) != null ? _a : { date: dateStr, count: 0, githubCount: 0, localCount: 0, repos: {} }); if (week.length === 7) { weeks.push(week); week = []; } } if (week.length > 0) { while (week.length < 7) week.push({ date: "", count: 0, githubCount: 0, localCount: 0, repos: {} }); weeks.push(week); } return weeks; } function buildMonthDays(year, month, days) { var _a; const firstDay = new Date(year, month, 1).getDay(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const weeks = []; let week = []; for (let i = 0; i < firstDay; i++) week.push({ date: "", count: 0, githubCount: 0, localCount: 0, repos: {} }); for (let i = 1; i <= daysInMonth; i++) { const dateStr = `${year}-${String(month + 1).padStart(2, "0")}-${String(i).padStart(2, "0")}`; week.push((_a = days.get(dateStr)) != null ? _a : { date: dateStr, count: 0, githubCount: 0, localCount: 0, repos: {} }); if (week.length === 7) { weeks.push(week); week = []; } } if (week.length > 0) { while (week.length < 7) week.push({ date: "", count: 0, githubCount: 0, localCount: 0, repos: {} }); weeks.push(week); } return weeks; } function calculateStreaks(days, year) { var _a, _b, _c, _d, _e, _f; const isLeap = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; const daysInYear = isLeap ? 366 : 365; const today = (0, import_obsidian.moment)().format("YYYY-MM-DD"); const allDates = []; for (let i = 0; i < daysInYear; i++) { const d = new Date(year, 0, i + 1); allDates.push(`${year}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`); } let longest = 0, run = 0; for (const date of allDates) { const count = (_b = (_a = days.get(date)) == null ? void 0 : _a.count) != null ? _b : 0; if (count > 0) { run++; if (run > longest) longest = run; } else run = 0; } let current = 0; const past = allDates.filter((d) => d <= today).reverse(); let skipFirst = past.length > 0 && ((_d = (_c = days.get(past[0])) == null ? void 0 : _c.count) != null ? _d : 0) === 0; for (const date of past) { if (skipFirst) { skipFirst = false; continue; } if (((_f = (_e = days.get(date)) == null ? void 0 : _e.count) != null ? _f : 0) > 0) current++; else break; } return { current, longest }; } function daysSinceLastCommit(days) { const today = (0, import_obsidian.moment)().format("YYYY-MM-DD"); const sorted = [...days.entries()].filter(([d]) => d <= today && d !== "").sort((a, b) => b[0].localeCompare(a[0])); for (const [date, data] of sorted) { if (data.count > 0) { return (0, import_obsidian.moment)(today).diff((0, import_obsidian.moment)(date), "days"); } } return null; } function buildDemoData(year) { var _a, _b; const days = /* @__PURE__ */ new Map(); const isLeap = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; const daysInYear = isLeap ? 366 : 365; let seed = 42; const rand = () => { seed = seed * 1664525 + 1013904223 & 4294967295; return (seed >>> 0) / 4294967295; }; const ghRepos = ["my-project", "obsidian-plugin"]; const localRepos = ["dotfiles", "personal-vault"]; for (let i = 0; i < daysInYear; i++) { const d = new Date(year, 0, i + 1); const dateStr = `${year}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; const recencyBoost = i / daysInYear; if (rand() >= 0.45 + recencyBoost * 0.3) continue; const totalCount = Math.floor(rand() * 10) + 1; const ghCount = Math.floor(rand() * totalCount); const localCount = totalCount - ghCount; const repos = {}; if (ghCount > 0) { let rem = ghCount; for (let ri = 0; ri < ghRepos.length; ri++) { if (rem <= 0) break; const n = ri === ghRepos.length - 1 ? rem : Math.min(rem, Math.floor(rand() * rem) + 1); if (n > 0 && rand() > 0.3) { repos[ghRepos[ri]] = n; rem -= n; } } if (rem > 0) repos[ghRepos[0]] = ((_a = repos[ghRepos[0]]) != null ? _a : 0) + rem; } if (localCount > 0) { let rem = localCount; for (let ri = 0; ri < localRepos.length; ri++) { if (rem <= 0) break; const n = ri === localRepos.length - 1 ? rem : Math.min(rem, Math.floor(rand() * rem) + 1); if (n > 0 && rand() > 0.3) { repos[localRepos[ri]] = n; rem -= n; } } if (rem > 0) repos[localRepos[0]] = ((_b = repos[localRepos[0]]) != null ? _b : 0) + rem; } days.set(dateStr, { date: dateStr, count: ghCount + localCount, githubCount: ghCount, localCount, repos }); } return days; } function mostRecentRepo(repos) { var _a, _b; if (!repos.length) return null; const sorted = [...repos].filter((r) => r.lastCommit).sort((a, b) => { var _a2, _b2; return ((_a2 = b.lastCommit) != null ? _a2 : "").localeCompare((_b2 = a.lastCommit) != null ? _b2 : ""); }); return (_b = (_a = sorted[0]) == null ? void 0 : _a.name) != null ? _b : null; } var ContributionsView = class extends import_obsidian.ItemView { constructor(leaf, plugin) { super(leaf); this.tooltipEl = null; this.resizeObserver = null; this.panelWidth = 0; this.lastFetchTime = 0; // cachedData holds the last successfully fetched result so cooldown-skipped // renders (e.g. refocusing the panel) can redraw from memory instead of // re-fetching from GitHub or re-scanning the filesystem. this.cachedData = null; this.plugin = plugin; this.displayYear = plugin.settings.selectedYear; this.displayMonth = new Date().getMonth(); this.viewMode = plugin.settings.defaultView; } getViewType() { return VIEW_TYPE; } getDisplayText() { return "GitHub Contributions"; } getIcon() { return "github"; } async onOpen() { this.resizeObserver = new ResizeObserver(() => { const w = this.containerEl.clientWidth; if (Math.abs(w - this.panelWidth) > 5) { this.panelWidth = w; if (this.plugin.settings.sizePreset === "fit") void this.render(false); } }); this.resizeObserver.observe(this.containerEl); this.panelWidth = this.containerEl.clientWidth; this.registerEvent( this.app.workspace.on("active-leaf-change", (leaf) => { if (leaf === this.leaf) void this.render(false); }) ); await this.render(); } async onClose() { var _a, _b; (_a = this.tooltipEl) == null ? void 0 : _a.remove(); this.tooltipEl = null; (_b = this.resizeObserver) == null ? void 0 : _b.disconnect(); } async refresh() { this.displayYear = this.plugin.settings.selectedYear; this.viewMode = this.plugin.settings.defaultView; await this.render(); } getCellSize() { const preset = this.plugin.settings.sizePreset; if (preset !== "fit") return PRESET_SIZES[preset]; const cols = this.viewMode === "year" ? 53 : 7; const gap = 2; const available = Math.max(100, (this.panelWidth || 260) - 28); const cell = Math.max(5, Math.floor((available - gap * (cols - 1)) / cols)); return { cell, gap }; } // force=true bypasses the auto-refresh cooldown (used by the manual refresh // button and the very first render). force=false is used when the panel // regains focus - if data was fetched recently, the cached result is reused. async render(force = true) { var _a; const container = this.containerEl.children[1]; container.empty(); container.addClass("gh-contributions-view"); if (!this.tooltipEl) { this.tooltipEl = activeDocument.body.createDiv({ cls: "gh-tooltip" }); } const s = this.plugin.settings; const needsGH = s.dataSource === "github" || s.dataSource === "both"; const needsLocal = s.dataSource === "local" || s.dataSource === "both"; if (needsGH && (!s.githubToken || !s.githubUsername)) { this.renderEmpty(container, "github"); return; } if (needsLocal && !s.localRepoRoot) { this.renderEmpty(container, "local"); return; } const withinCooldown = Date.now() - this.lastFetchTime < AUTO_REFRESH_COOLDOWN_MS; const canUseCache = !force && withinCooldown && this.cachedData; if (!canUseCache) this.renderSkeleton(container); try { let repos; let days; let totalGH, totalLocal; if (canUseCache && this.cachedData) { ({ days, totalGH, totalLocal, repos } = this.cachedData); } else { repos = []; if (needsLocal && s.localRepoRoot) { repos = await discoverRepos(s.localRepoRoot, s.scanDepth); } if (s.demoMode) { days = buildDemoData(this.displayYear); totalGH = [...days.values()].reduce((a, d) => a + d.githubCount, 0); totalLocal = 0; } else { const result = await buildDayMap(s, this.displayYear, repos); days = result.days; totalGH = result.totalGH; totalLocal = result.totalLocal; } this.cachedData = { days, totalGH, totalLocal, repos }; this.lastFetchTime = Date.now(); } container.empty(); const streaks = calculateStreaks(days, this.displayYear); const sinceCommit = daysSinceLastCommit(days); const recentRepo = s.demoMode ? "my-project" : mostRecentRepo(repos); this.renderHeader(container); this.renderStats(container, { totalGH, totalLocal, streaks, sinceCommit, recentRepo }); const style = (_a = this.plugin.settings.statsStyle) != null ? _a : "default"; if (recentRepo && style === "default") { const repoLine = container.createDiv({ cls: "gh-repo-line" }); repoLine.createEl("span", { cls: "gh-repo-line-icon", text: "\u{1F4C1} " }); repoLine.createEl("span", { cls: "gh-repo-line-name", text: recentRepo }); } if (this.viewMode === "year") { const weeks = buildYearWeeks(this.displayYear, days); this.renderYearGrid(container, weeks); } else { const weeks = buildMonthDays(this.displayYear, this.displayMonth, days); this.renderMonthGrid(container, weeks); } if (this.plugin.settings.showLegend) this.renderLegend(container); } catch (e) { container.empty(); this.renderError(container, e.message); } } renderHeader(container) { const s = this.plugin.settings; const header = container.createDiv({ cls: "gh-header" }); if (s.githubUsername) header.createEl("span", { cls: "gh-username", text: s.githubUsername }); const nav = header.createDiv({ cls: "gh-nav" }); const currentYear = new Date().getFullYear(); if (this.viewMode === "year") { const prev = nav.createEl("button", { cls: "gh-nav-btn", text: "\u2039" }); nav.createEl("span", { cls: "gh-nav-label", text: String(this.displayYear) }); const next = nav.createEl("button", { cls: "gh-nav-btn", text: "\u203A" }); next.disabled = this.displayYear >= currentYear; prev.onclick = async () => { this.displayYear--; await this.render(); }; next.onclick = async () => { if (this.displayYear < currentYear) { this.displayYear++; await this.render(); } }; } else { const prev = nav.createEl("button", { cls: "gh-nav-btn", text: "\u2039" }); nav.createEl("span", { cls: "gh-nav-label", text: `${MONTHS_SHORT[this.displayMonth]} ${this.displayYear}` }); const next = nav.createEl("button", { cls: "gh-nav-btn", text: "\u203A" }); const isLatest = this.displayYear >= currentYear && this.displayMonth >= new Date().getMonth(); next.disabled = isLatest; prev.onclick = async () => { if (this.displayMonth === 0) { this.displayMonth = 11; this.displayYear--; } else this.displayMonth--; await this.render(); }; next.onclick = async () => { if (isLatest) return; if (this.displayMonth === 11) { this.displayMonth = 0; this.displayYear++; } else this.displayMonth++; await this.render(); }; } const refreshBtn = nav.createEl("button", { cls: "gh-nav-btn gh-refresh-icon", text: "\u21BB" }); refreshBtn.title = "Refresh"; refreshBtn.onclick = () => { void this.render(); }; } renderStats(container, info) { var _a; const s = this.plugin.settings; const style = (_a = s.statsStyle) != null ? _a : "default"; const total = info.totalGH + info.totalLocal; const showLocal = s.dataSource === "local" || s.dataSource === "both"; if (style === "compact") { const row = container.createDiv({ cls: "gh-stats-compact" }); const items = [ ["\u2191", String(total), "Total contributions"], ["\u{1F525}", info.streaks.current + "d", "Current streak"], ["\u2B50", info.streaks.longest + "d", "Best streak"] ]; if (showLocal && info.sinceCommit !== null) { const sinceLabel = info.sinceCommit === 0 ? "committed today" : info.sinceCommit + "d ago"; items.push(["\u23F1", info.sinceCommit === 0 ? "today" : info.sinceCommit + "d", sinceLabel]); } if (info.recentRepo) items.push(["\u{1F4C1}", info.recentRepo, info.recentRepo]); for (const [icon, val, title] of items) { const chip = row.createEl("span", { cls: "gh-chip", title }); chip.createEl("span", { cls: "gh-chip-icon", text: icon }); chip.createEl("span", { cls: "gh-chip-val", text: val }); } } else if (style === "grid") { const stats = container.createDiv({ cls: "gh-stats gh-stats--grid" }); this.pill(stats, String(total), "contributions"); this.pill(stats, info.streaks.current + "d", "streak"); this.pill(stats, info.streaks.longest + "d", "best"); if (showLocal && info.sinceCommit !== null) this.pill(stats, info.sinceCommit === 0 ? "today" : info.sinceCommit + "d ago", info.sinceCommit === 0 ? "committed" : "since commit"); if (info.recentRepo) { const maxLen = 16; const name = info.recentRepo.length > maxLen ? info.recentRepo.slice(0, maxLen - 1) + "\u2026" : info.recentRepo; const pill = stats.createDiv({ cls: "gh-stat gh-stat--wide" }); pill.title = info.recentRepo; pill.createEl("span", { cls: "gh-stat-val", text: name }); pill.createEl("span", { cls: "gh-stat-lbl", text: "recent repo" }); } } else { const list = container.createDiv({ cls: "gh-stats-list" }); const items = [ ["\u2191", String(total), "contributions"], ["\u{1F525}", info.streaks.current + "d", "streak"], ["\u2B50", info.streaks.longest + "d", "best"] ]; if (showLocal && info.sinceCommit !== null) { const val = info.sinceCommit === 0 ? "today" : info.sinceCommit + "d ago"; const lbl = info.sinceCommit === 0 ? "committed today" : "since commit"; items.push(["\u23F1", val, lbl]); } for (const [icon, val, label] of items) { const row = list.createDiv({ cls: "gh-stats-list-row" }); row.createEl("span", { cls: "gh-stats-list-icon", text: icon }); row.createEl("span", { cls: "gh-stats-list-val", text: val }); row.createEl("span", { cls: "gh-stats-list-lbl", text: " " + label }); } } } pill(parent, value, label) { const p = parent.createDiv({ cls: "gh-stat" }); p.createEl("span", { cls: "gh-stat-val", text: value }); p.createEl("span", { cls: "gh-stat-lbl", text: label }); } renderYearGrid(container, weeks) { const { cell, gap } = this.getCellSize(); const graphWrap = container.createDiv({ cls: "gh-graph-wrap" }); graphWrap.setCssStyles({ overflowX: "auto" }); const monthRow = graphWrap.createDiv({ cls: "gh-month-row" }); monthRow.setCssStyles({ display: "grid", gridTemplateColumns: `repeat(${weeks.length},${cell}px)`, gap: gap + "px", marginBottom: "3px" }); let lastMonth = -1; weeks.forEach((week, wi) => { const first = week.find((d) => d.date); if (!first) return; const m = new Date(first.date).getUTCMonth(); if (m !== lastMonth) { lastMonth = m; const lbl = monthRow.createEl("span", { cls: "gh-month-lbl", text: MONTHS_SHORT[m] }); lbl.setCssStyles({ gridColumn: String(wi + 1), fontSize: Math.max(8, cell - 2) + "px" }); } }); const grid = graphWrap.createDiv({ cls: "gh-grid" }); grid.setCssStyles({ display: "flex", gap: gap + "px" }); weeks.forEach((week) => { const col = grid.createDiv({ cls: "gh-col" }); col.setCssStyles({ display: "flex", flexDirection: "column", gap: gap + "px" }); week.forEach((day) => this.renderCell(col, day, cell)); }); } renderMonthGrid(container, weeks) { const { cell, gap } = this.getCellSize(); const graphWrap = container.createDiv({ cls: "gh-graph-wrap" }); const colTemplate = `repeat(7,${cell}px)`; const dowRow = graphWrap.createDiv({ cls: "gh-dow-row" }); dowRow.setCssStyles({ display: "grid", gridTemplateColumns: colTemplate, gap: gap + "px", marginBottom: "4px" }); ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].forEach((d) => { const lbl = dowRow.createEl("span", { cls: "gh-dow-lbl", text: d }); lbl.setCssStyles({ fontSize: "10px", color: "var(--text-faint)", textAlign: "center", overflow: "hidden" }); }); const grid = graphWrap.createDiv({ cls: "gh-month-grid" }); grid.setCssStyles({ display: "flex", flexDirection: "column", gap: gap + "px" }); weeks.forEach((week) => { const row = grid.createDiv({ cls: "gh-month-row-cells" }); row.setCssStyles({ display: "grid", gridTemplateColumns: colTemplate, gap: gap + "px" }); week.forEach((day) => this.renderCell(row, day, cell)); }); } renderCell(parent, day, size) { const cell = parent.createDiv({ cls: "gh-cell" }); cell.setCssStyles({ width: size + "px", height: size + "px" }); if (!day.date) { cell.addClass("gh-cell--empty"); return; } const today = (0, import_obsidian.moment)().format("YYYY-MM-DD"); cell.dataset.level = String(countToLevel(day.count)); if (day.date === today) cell.addClass("gh-today"); cell.addEventListener("mouseenter", (e) => this.showTooltip(e, day)); cell.addEventListener("mouseleave", () => { if (this.tooltipEl) this.tooltipEl.setCssStyles({ display: "none" }); }); cell.addEventListener("click", () => { void this.openOrCreateDailyNote(day.date); }); } showTooltip(e, day) { if (!this.tooltipEl) return; const s = this.plugin.settings; const showSources = s.dataSource === "both" || s.demoMode; const repoEntries = Object.entries(day.repos).sort((a, b) => b[1] - a[1]); this.tooltipEl.empty(); this.tooltipEl.removeClass("gh-tooltip--console", "gh-tooltip--modern"); if (s.tooltipStyle === "simple") { this.renderSimpleTooltip(day, showSources); } else { this.renderStandardTooltip(day, showSources, repoEntries); } this.tooltipEl.setCssStyles({ display: "block", visibility: "hidden" }); const tooltipWidth = this.tooltipEl.offsetWidth || 180; const tooltipHeight = this.tooltipEl.offsetHeight || 80; const spaceOnRight = window.innerWidth - e.pageX; const left = spaceOnRight < tooltipWidth + 20 ? e.pageX - tooltipWidth - 12 : e.pageX + 12; const spaceBelow = window.innerHeight - e.pageY; const top = spaceBelow < tooltipHeight + 20 ? e.pageY - tooltipHeight - 8 : e.pageY - 34; this.tooltipEl.setCssStyles({ left: left + "px", top: top + "px", visibility: "visible" }); } renderSimpleTooltip(day, showSources) { const t = this.tooltipEl; t.addClass("gh-tooltip--modern"); t.createEl("div", { cls: "gh-tip-date", text: (0, import_obsidian.moment)(day.date).format("MMM D, YYYY") }); if (day.count === 0) { t.createEl("div", { cls: "gh-tip-no-contrib", text: "No contributions" }); return; } const countRow = t.createDiv({ cls: "gh-tip-count-row" }); countRow.createEl("span", { cls: "gh-tip-count", text: String(day.count) }); countRow.createEl("span", { cls: "gh-tip-count-lbl", text: ` contribution${day.count !== 1 ? "s" : ""}` }); if (showSources && (day.githubCount > 0 || day.localCount > 0)) { const sources = t.createDiv({ cls: "gh-tip-sources" }); if (day.githubCount > 0) { const row = sources.createDiv({ cls: "gh-tip-source-row" }); const ghIco = row.createEl("span", { cls: "gh-tip-source-icon" }); ghIco.appendChild(this.makeGithubSvg()); row.createEl("span", { cls: "gh-tip-source-lbl", text: "GitHub" }); row.createEl("span", { cls: "gh-tip-source-val", text: String(day.githubCount) }); } if (day.localCount > 0) { const row = sources.createDiv({ cls: "gh-tip-source-row" }); const localIco = row.createEl("span", { cls: "gh-tip-source-icon" }); localIco.appendChild(this.makeLocalSvg()); row.createEl("span", { cls: "gh-tip-source-lbl", text: "Local" }); row.createEl("span", { cls: "gh-tip-source-val", text: String(day.localCount) }); } } } makeGithubSvg() { const svg = activeDocument.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("width", "13"); svg.setAttribute("height", "13"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("fill", "none"); svg.setAttribute("stroke", "currentColor"); svg.setAttribute("stroke-width", "2"); svg.setAttribute("stroke-linecap", "round"); svg.setAttribute("stroke-linejoin", "round"); const p1 = activeDocument.createElementNS("http://www.w3.org/2000/svg", "path"); p1.setAttribute("d", "M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"); svg.appendChild(p1); const p2 = activeDocument.createElementNS("http://www.w3.org/2000/svg", "path"); p2.setAttribute("d", "M9 18c-4.51 2-5-2-7-2"); svg.appendChild(p2); return svg; } makeLocalSvg() { const svg = activeDocument.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("width", "13"); svg.setAttribute("height", "13"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("fill", "none"); svg.setAttribute("stroke", "currentColor"); svg.setAttribute("stroke-width", "2"); svg.setAttribute("stroke-linecap", "round"); svg.setAttribute("stroke-linejoin", "round"); const rect = activeDocument.createElementNS("http://www.w3.org/2000/svg", "rect"); rect.setAttribute("width", "18"); rect.setAttribute("height", "12"); rect.setAttribute("x", "3"); rect.setAttribute("y", "4"); rect.setAttribute("rx", "2"); rect.setAttribute("ry", "2"); svg.appendChild(rect); const line = activeDocument.createElementNS("http://www.w3.org/2000/svg", "line"); line.setAttribute("x1", "2"); line.setAttribute("x2", "22"); line.setAttribute("y1", "20"); line.setAttribute("y2", "20"); svg.appendChild(line); return svg; } renderStandardTooltip(day, showSources, repoEntries) { const t = this.tooltipEl; t.addClass("gh-tooltip--modern"); t.createEl("div", { cls: "gh-tip-date", text: (0, import_obsidian.moment)(day.date).format("MMM D, YYYY") }); if (day.count === 0) { t.createEl("div", { cls: "gh-tip-no-contrib", text: "No contributions" }); return; } const countRow = t.createDiv({ cls: "gh-tip-count-row" }); countRow.createEl("span", { cls: "gh-tip-count", text: String(day.count) }); countRow.createEl("span", { cls: "gh-tip-count-lbl", text: ` contribution${day.count !== 1 ? "s" : ""}` }); if (showSources && (day.githubCount > 0 || day.localCount > 0)) { const sources = t.createDiv({ cls: "gh-tip-sources" }); if (day.githubCount > 0) { const row = sources.createDiv({ cls: "gh-tip-source-row" }); const ico = row.createEl("span", { cls: "gh-tip-source-icon" }); ico.appendChild(this.makeGithubSvg()); row.createEl("span", { cls: "gh-tip-source-lbl", text: "GitHub" }); row.createEl("span", { cls: "gh-tip-source-val", text: String(day.githubCount) }); } if (day.localCount > 0) { const row = sources.createDiv({ cls: "gh-tip-source-row" }); const ico = row.createEl("span", { cls: "gh-tip-source-icon" }); ico.appendChild(this.makeLocalSvg()); row.createEl("span", { cls: "gh-tip-source-lbl", text: "Local" }); row.createEl("span", { cls: "gh-tip-source-val", text: String(day.localCount) }); } } if (repoEntries.length > 0) { t.createDiv({ cls: "gh-tip-divider" }); const repoColors = ["#3fb950", "#3b82f6", "#a855f7", "#f59e0b", "#ef4444"]; repoEntries.forEach(([repo, count], i) => { const row = t.createDiv({ cls: "gh-tip-repo-row" }); const dot = row.createEl("span", { cls: "gh-tip-dot" }); dot.setCssStyles({ background: repoColors[i % repoColors.length] }); row.createEl("span", { cls: "gh-tip-repo-name", text: repo }); row.createEl("span", { cls: "gh-tip-repo-count", text: `(${count})` }); }); } } renderLegend(container) { const { cell } = this.getCellSize(); const legend = container.createDiv({ cls: "gh-legend" }); legend.createEl("span", { cls: "gh-legend-lbl", text: "Less" }); for (let i = 0; i <= 4; i++) { const sq = legend.createDiv({ cls: "gh-cell gh-legend-cell" }); sq.dataset.level = String(i); sq.setCssStyles({ width: cell + "px", height: cell + "px" }); } legend.createEl("span", { cls: "gh-legend-lbl", text: "More" }); } renderEmpty(container, missing) { const wrap = container.createDiv({ cls: "gh-empty" }); wrap.createEl("div", { cls: "gh-empty-icon", text: "\u{1F419}" }); const msg = missing === "github" ? "Connect your GitHub account in Settings to see your contribution graph." : "Set a local repo root folder in Settings to scan for git commits."; wrap.createEl("p", { text: msg }); const btn = wrap.createEl("button", { cls: "gh-btn", text: "Open Settings" }); btn.onclick = () => { const appWithSettings = this.app; appWithSettings.setting.open(); appWithSettings.setting.openTabById("github-contributions"); }; } renderSkeleton(container) { const wrap = container.createDiv({ cls: "gh-skeleton-wrap" }); wrap.createDiv({ cls: "gh-skeleton gh-skeleton-header" }); wrap.createDiv({ cls: "gh-skeleton gh-skeleton-stats" }); const grid = wrap.createDiv({ cls: "gh-skeleton-grid" }); const cols = this.viewMode === "year" ? 53 : 7; const rows = this.viewMode === "year" ? 7 : 6; const { cell, gap } = this.getCellSize(); grid.setCssStyles({ display: "grid", gridTemplateColumns: `repeat(${cols},${cell}px)`, gridTemplateRows: `repeat(${rows},${cell}px)`, gap: gap + "px" }); for (let i = 0; i < cols * rows; i++) grid.createDiv({ cls: "gh-skeleton gh-skeleton-cell" }); } renderError(container, msg) { container.createDiv({ cls: "gh-error", text: `\u26A0 ${msg}` }); } async openOrCreateDailyNote(date) { const s = this.plugin.settings; const fmt = s.dailyNoteDateFormat || "YYYY-MM-DD"; const folder = s.dailyNoteFolder ? s.dailyNoteFolder.replace(/\/$/, "") + "/" : ""; const name = (0, import_obsidian.moment)(date).format(fmt); const filePath = `${folder}${name}.md`; let file = this.app.vault.getAbstractFileByPath(filePath); if (!file) { try { file = await this.app.vault.create(filePath, `# ${name} `); new import_obsidian.Notice(`Created: ${name}`); } catch (err) { new import_obsidian.Notice(`Error creating note: ${err.message}`); return; } } if (file instanceof import_obsidian.TFile) await this.app.workspace.getLeaf(false).openFile(file); } }; function countToLevel(n) { if (n === 0) return 0; if (n <= 2) return 1; if (n <= 5) return 2; if (n <= 9) return 3; return 4; } var GitHubContributionsSettingTab = class extends import_obsidian.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } display() { const { containerEl } = this; containerEl.empty(); new import_obsidian.Setting(containerEl).setName("Data Sources").setHeading(); new import_obsidian.Setting(containerEl).setName("Source").setDesc("Which contributions to display").addDropdown( (d) => d.addOption("github", "GitHub only").addOption("local", "Local git only").addOption("both", "Both").setValue(this.plugin.settings.dataSource).onChange(async (v) => { this.plugin.settings.dataSource = v; await this.plugin.saveSettings(); this.display(); }) ); const src = this.plugin.settings.dataSource; if (src === "github" || src === "both") { new import_obsidian.Setting(containerEl).setName("Authentication").setDesc("Connect GitHub to fetch your contribution data").addDropdown( (d) => d.addOption("oauth", "Connect with GitHub (recommended)").addOption("pat", "Personal Access Token (advanced)").setValue(this.plugin.settings.authMethod).onChange(async (v) => { this.plugin.settings.authMethod = v; await this.plugin.saveSettings(); this.display(); }) ); if (this.plugin.settings.authMethod === "oauth") { const isConnected = !!this.plugin.settings.githubToken && !!this.plugin.settings.githubUsername; if (isConnected) { new import_obsidian.Setting(containerEl).setName("Connected as " + this.plugin.settings.githubUsername).setDesc("Your GitHub account is connected via OAuth").addButton( (btn) => btn.setButtonText("Disconnect").setWarning().onClick(async () => { this.plugin.settings.githubToken = ""; this.plugin.settings.githubUsername = ""; await this.plugin.saveSettings(); this.display(); }) ); } else { let cancelled = false; const oauthSetting = new import_obsidian.Setting(containerEl).setName("Connect GitHub account").setDesc("Click to start the authorization flow"); oauthSetting.addButton( (btn) => btn.setButtonText("Connect GitHub").setCta().onClick(async () => { btn.setButtonText("Connecting..."); btn.buttonEl.setAttr("disabled", "true"); cancelled = false; try { const device = await requestDeviceCode(); const expiryMins = Math.floor(device.expires_in / 60); oauthSetting.setDesc( createFragment((f) => { f.appendText("Enter this code at "); f.createEl("strong", { text: "github.com/login/device" }); f.createEl("br"); f.createEl("span", { cls: "gh-oauth-code", text: device.user_code }); f.createEl("br"); f.createEl("em", { text: `Waiting for approval\u2026 (code expires in ${expiryMins} minutes)` }); }) ); window.open(device.verification_uri); const token = await pollForToken(device.device_code, device.interval, device.expires_in, () => cancelled); const username = await fetchGitHubUsername(token); this.plugin.settings.githubToken = token; this.plugin.settings.githubUsername = username; await this.plugin.saveSettings(); new import_obsidian.Notice("Connected to GitHub as " + username); this.display(); } catch (e) { const msg = e.message; if (msg === "Cancelled") { oauthSetting.setDesc("Connection cancelled. Click Connect GitHub to try again."); } else { oauthSetting.setDesc("\u26A0 " + msg); new import_obsidian.Notice("GitHub connection failed: " + msg); } btn.setButtonText("Connect GitHub"); btn.buttonEl.removeAttribute("disabled"); } }) ); oauthSetting.addButton( (btn) => btn.setButtonText("Cancel").onClick(() => { cancelled = true; oauthSetting.setDesc("Cancelled."); btn.buttonEl.setAttr("disabled", "true"); }) ); } } else { new import_obsidian.Setting(containerEl).setName("GitHub username").addText((t) => t.setPlaceholder("username").setValue(this.plugin.settings.githubUsername).onChange(async (v) => { this.plugin.settings.githubUsername = v.trim(); await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Personal Access Token").setDesc("PAT with read:user scope - github.com/settings/tokens").addText((t) => { t.inputEl.type = "password"; t.setPlaceholder("ghp_\u2026").setValue(this.plugin.settings.githubToken).onChange(async (v) => { this.plugin.settings.githubToken = v.trim(); await this.plugin.saveSettings(); }); }); } } if (src === "local" || src === "both") { if (!isDesktop) { containerEl.createEl("p", { cls: "gh-settings-notice", text: "\u26A0 Local git scanning is not available on mobile." }); } else { new import_obsidian.Setting(containerEl).setName("Local repo root").setDesc("Folder to scan for git repositories").addText((t) => { t.setPlaceholder("C:\\Users\\You\\Projects").setValue(this.plugin.settings.localRepoRoot); t.onChange((v) => { this.plugin.settings.localRepoRoot = v.trim(); }); }).addButton((btn) => { btn.setButtonText("Scan"); btn.buttonEl.setAttr("title", "Save path and scan for repositories"); btn.onClick(async () => { btn.setButtonText("Scanning\u2026"); btn.buttonEl.setAttr("disabled", "true"); await this.plugin.saveSettings(); btn.setButtonText("Scan"); btn.buttonEl.removeAttribute("disabled"); new import_obsidian.Notice("Repo scan complete - refresh the panel to see results"); }); }); new import_obsidian.Setting(containerEl).setName("Scan depth").setDesc("How many folder levels deep to search for git repos").addDropdown( (d) => d.addOption("2", "2 levels").addOption("3", "3 levels").addOption("4", "4 levels").addOption("5", "5 levels").addOption("0", "Unlimited (slow on large drives)").setValue(String(this.plugin.settings.scanDepth)).onChange(async (v) => { this.plugin.settings.scanDepth = parseInt(v); await this.plugin.saveSettings(); }) ); } } new import_obsidian.Setting(containerEl).setName("Display").setHeading(); new import_obsidian.Setting(containerEl).setName("Stats display").setDesc("How the stats bar is shown").addDropdown( (d) => d.addOption("default", "Default (pills with labels)").addOption("compact", "Compact (icons + numbers)").addOption("grid", "Grid (2-column)").setValue(this.plugin.settings.statsStyle).onChange(async (v) => { this.plugin.settings.statsStyle = v; await this.plugin.saveSettings(); }) ); new import_obsidian.Setting(containerEl).setName("Colour palette").setDesc("Colour scheme for contribution cells").addDropdown( (d) => d.addOption("classic", "Classic (GitHub greens)").addOption("high-contrast", "High contrast (vivid greens)").addOption("cobalt", "Cobalt (blue to cyan)").addOption("neon", "Neon (purple to yellow)").addOption("ember", "Ember (amber to gold)").setValue(this.plugin.settings.palette).onChange(async (v) => { this.plugin.settings.palette = v; await this.plugin.saveSettings(); }) ); new import_obsidian.Setting(containerEl).setName("Size").setDesc('Cell size. "Fit to sidebar" auto-sizes to fill the panel width.').addDropdown((d) => d.addOption("ultra-compact", "Ultra compact").addOption("compact", "Compact").addOption("medium", "Medium").addOption("large", "Large").addOption("fit", "Fit to sidebar").setValue(this.plugin.settings.sizePreset).onChange(async (v) => { this.plugin.settings.sizePreset = v; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Default view").addDropdown((d) => d.addOption("year", "Year").addOption("month", "Month").setValue(this.plugin.settings.defaultView).onChange(async (v) => { this.plugin.settings.defaultView = v; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Default year").addText((t) => t.setPlaceholder(String(new Date().getFullYear())).setValue(String(this.plugin.settings.selectedYear)).onChange(async (v) => { const y = parseInt(v); if (!isNaN(y) && y >= 2008 && y <= new Date().getFullYear()) { this.plugin.settings.selectedYear = y; await this.plugin.saveSettings(); } })); new import_obsidian.Setting(containerEl).setName("Tooltip style").addDropdown( (d) => d.addOption("standard", "Standard (with repos)").addOption("simple", "Simple (no repos)").setValue(this.plugin.settings.tooltipStyle).onChange(async (v) => { this.plugin.settings.tooltipStyle = v; await this.plugin.saveSettings(); }) ); new import_obsidian.Setting(containerEl).setName("Demo mode").setDesc("Show fake contribution data - useful for screenshots or testing").addToggle((t) => t.setValue(this.plugin.settings.demoMode).onChange(async (v) => { this.plugin.settings.demoMode = v; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Show legend").setDesc("Show the Less / More colour legend below the graph").addToggle((t) => t.setValue(this.plugin.settings.showLegend).onChange(async (v) => { this.plugin.settings.showLegend = v; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Daily Notes").setHeading(); new import_obsidian.Setting(containerEl).setName("Daily notes folder").setDesc("Leave blank for vault root").addText((t) => { t.setPlaceholder("Daily Notes/").setValue(this.plugin.settings.dailyNoteFolder); t.onChange(debounce(async (v) => { this.plugin.settings.dailyNoteFolder = v; await this.plugin.saveSettings(); }, 800)); }); new import_obsidian.Setting(containerEl).setName("Date format").setDesc("Moment.js format for filenames. Default: YYYY-MM-DD").addText((t) => { t.setPlaceholder("YYYY-MM-DD").setValue(this.plugin.settings.dailyNoteDateFormat); t.onChange(debounce(async (v) => { this.plugin.settings.dailyNoteDateFormat = v; await this.plugin.saveSettings(); }, 800)); }); } }; var GitHubContributionsPlugin = class extends import_obsidian.Plugin { constructor() { super(...arguments); this.settings = { ...DEFAULT_SETTINGS }; } async onload() { await this.loadSettings(); this.addSettingTab(new GitHubContributionsSettingTab(this.app, this)); this.registerView(VIEW_TYPE, (leaf) => new ContributionsView(leaf, this)); this.addRibbonIcon("github", "GitHub Contributions", () => { void this.activateView(); }); this.addCommand({ id: "open-panel", name: "Open panel", callback: () => { void this.activateView(); } }); this.injectPaletteStyles(); } onunload() { var _a; (_a = activeDocument.getElementById("gh-palette-styles")) == null ? void 0 : _a.remove(); activeDocument.querySelectorAll(".gh-tooltip").forEach((el) => el.remove()); } async activateView() { const existing = this.app.workspace.getLeavesOfType(VIEW_TYPE); if (existing.length > 0) { void this.app.workspace.revealLeaf(existing[0]); return; } const leaf = this.app.workspace.getRightLeaf(false); if (leaf) { await leaf.setViewState({ type: VIEW_TYPE, active: true }); void this.app.workspace.revealLeaf(leaf); } } async loadSettings() { const savedData = await this.loadData(); this.settings = Object.assign({}, DEFAULT_SETTINGS, savedData != null ? savedData : {}); if (this.settings.selectedYear > new Date().getFullYear()) this.settings.selectedYear = new Date().getFullYear(); } async saveSettings() { await this.saveData(this.settings); this.injectPaletteStyles(); this.app.workspace.getLeavesOfType(VIEW_TYPE).forEach((leaf) => { if (leaf.view instanceof ContributionsView) void leaf.view.refresh(); }); } injectPaletteStyles() { var _a; const doc = activeDocument; const existing = doc.getElementById("gh-palette-styles"); if (existing) existing.remove(); const p = (_a = PALETTES[this.settings.palette]) != null ? _a : PALETTES["classic"]; const style = doc.createElement("style"); style.id = "gh-palette-styles"; style.textContent = ` body{--gh-c0:${p.dark[0]};--gh-c1:${p.dark[1]};--gh-c2:${p.dark[2]};--gh-c3:${p.dark[3]};--gh-c4:${p.dark[4]}} body.theme-light{--gh-c0:${p.light[0]};--gh-c1:${p.light[1]};--gh-c2:${p.light[2]};--gh-c3:${p.light[3]};--gh-c4:${p.light[4]}} `; doc.head.appendChild(style); } };