gearheaded_obsidian-github-.../main.js

1222 lines
51 KiB
JavaScript

/*
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 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.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();
}
});
this.resizeObserver.observe(this.containerEl);
this.panelWidth = this.containerEl.clientWidth;
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 };
}
async render() {
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;
}
this.renderSkeleton(container);
try {
let repos = [];
if (needsLocal && s.localRepoRoot) {
repos = await discoverRepos(s.localRepoRoot, s.scanDepth);
}
let days;
let totalGH = 0, totalLocal = 0;
if (s.demoMode) {
days = buildDemoData(this.displayYear);
totalGH = [...days.values()].reduce((a, d) => a + d.githubCount, 0);
} else {
const result = await buildDayMap(s, this.displayYear, repos);
days = result.days;
totalGH = result.totalGH;
totalLocal = result.totalLocal;
}
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);
}
};