contrib-graphs 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +52 -2
- package/dist/data-DptvXfjH.d.cts +344 -0
- package/dist/data-DptvXfjH.d.ts +344 -0
- package/dist/data-oHgoFFM5.js +533 -0
- package/dist/data-w1Mb5e5D.cjs +658 -0
- package/dist/data.cjs +21 -0
- package/dist/data.d.cts +7 -0
- package/dist/data.d.ts +7 -0
- package/dist/data.js +2 -0
- package/dist/index.cjs +1124 -0
- package/dist/index.d.cts +232 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +1098 -0
- package/dist/styles.css +291 -0
- package/package.json +70 -4
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
|
|
2
|
+
// src/themes.ts
|
|
3
|
+
const lightBase = {
|
|
4
|
+
text: "#1f2328",
|
|
5
|
+
muted: "#656d76",
|
|
6
|
+
surface: "#ffffff",
|
|
7
|
+
border: "#d0d7de"
|
|
8
|
+
};
|
|
9
|
+
const darkBase = {
|
|
10
|
+
text: "#e6edf3",
|
|
11
|
+
muted: "#8d96a0",
|
|
12
|
+
surface: "#0d1117",
|
|
13
|
+
border: "#30363d"
|
|
14
|
+
};
|
|
15
|
+
/** Built-in themes: `github`, `gitlab`, `frost`, `ember`, `violet` and `mono`. */
|
|
16
|
+
const themes = {
|
|
17
|
+
github: {
|
|
18
|
+
name: "GitHub",
|
|
19
|
+
light: {
|
|
20
|
+
...lightBase,
|
|
21
|
+
levels: [
|
|
22
|
+
"#ebedf0",
|
|
23
|
+
"#9be9a8",
|
|
24
|
+
"#40c463",
|
|
25
|
+
"#30a14e",
|
|
26
|
+
"#216e39"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
dark: {
|
|
30
|
+
...darkBase,
|
|
31
|
+
levels: [
|
|
32
|
+
"#161b22",
|
|
33
|
+
"#0e4429",
|
|
34
|
+
"#006d32",
|
|
35
|
+
"#26a641",
|
|
36
|
+
"#39d353"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
gitlab: {
|
|
41
|
+
name: "GitLab",
|
|
42
|
+
light: {
|
|
43
|
+
...lightBase,
|
|
44
|
+
levels: [
|
|
45
|
+
"#ececef",
|
|
46
|
+
"#acd5f2",
|
|
47
|
+
"#7fa8c9",
|
|
48
|
+
"#527ba0",
|
|
49
|
+
"#254e77"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
dark: {
|
|
53
|
+
...darkBase,
|
|
54
|
+
surface: "#18171d",
|
|
55
|
+
border: "#3a383f",
|
|
56
|
+
levels: [
|
|
57
|
+
"#28272d",
|
|
58
|
+
"#24406a",
|
|
59
|
+
"#3a6aa3",
|
|
60
|
+
"#63a0d9",
|
|
61
|
+
"#a9d4ff"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
frost: {
|
|
66
|
+
name: "Frost",
|
|
67
|
+
light: {
|
|
68
|
+
...lightBase,
|
|
69
|
+
levels: [
|
|
70
|
+
"#e5e9f0",
|
|
71
|
+
"#c3d7e6",
|
|
72
|
+
"#88c0d0",
|
|
73
|
+
"#5e81ac",
|
|
74
|
+
"#3b5378"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
dark: {
|
|
78
|
+
...darkBase,
|
|
79
|
+
surface: "#2e3440",
|
|
80
|
+
border: "#434c5e",
|
|
81
|
+
text: "#eceff4",
|
|
82
|
+
muted: "#a3adbf",
|
|
83
|
+
levels: [
|
|
84
|
+
"#3b4252",
|
|
85
|
+
"#3d5468",
|
|
86
|
+
"#5e81ac",
|
|
87
|
+
"#81a1c1",
|
|
88
|
+
"#88c0d0"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
ember: {
|
|
93
|
+
name: "Ember",
|
|
94
|
+
light: {
|
|
95
|
+
...lightBase,
|
|
96
|
+
levels: [
|
|
97
|
+
"#f1ece8",
|
|
98
|
+
"#fdd8a8",
|
|
99
|
+
"#fb9b4b",
|
|
100
|
+
"#e8590c",
|
|
101
|
+
"#a8320a"
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
dark: {
|
|
105
|
+
...darkBase,
|
|
106
|
+
surface: "#16110e",
|
|
107
|
+
border: "#3a2d25",
|
|
108
|
+
levels: [
|
|
109
|
+
"#241c17",
|
|
110
|
+
"#5a2a12",
|
|
111
|
+
"#9c3d0c",
|
|
112
|
+
"#e8590c",
|
|
113
|
+
"#ffa94d"
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
violet: {
|
|
118
|
+
name: "Violet",
|
|
119
|
+
light: {
|
|
120
|
+
...lightBase,
|
|
121
|
+
levels: [
|
|
122
|
+
"#eeebf3",
|
|
123
|
+
"#d6c8f7",
|
|
124
|
+
"#a78bfa",
|
|
125
|
+
"#7c3aed",
|
|
126
|
+
"#4c1d95"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
dark: {
|
|
130
|
+
...darkBase,
|
|
131
|
+
surface: "#13111b",
|
|
132
|
+
border: "#2f2a45",
|
|
133
|
+
levels: [
|
|
134
|
+
"#1f1b2b",
|
|
135
|
+
"#3b2a6b",
|
|
136
|
+
"#6d4fd1",
|
|
137
|
+
"#a78bfa",
|
|
138
|
+
"#ddd0ff"
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
mono: {
|
|
143
|
+
name: "Mono",
|
|
144
|
+
light: {
|
|
145
|
+
...lightBase,
|
|
146
|
+
levels: [
|
|
147
|
+
"#ececec",
|
|
148
|
+
"#c4c4c4",
|
|
149
|
+
"#8f8f8f",
|
|
150
|
+
"#5a5a5a",
|
|
151
|
+
"#1f1f1f"
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
dark: {
|
|
155
|
+
...darkBase,
|
|
156
|
+
surface: "#0f0f0f",
|
|
157
|
+
border: "#2e2e2e",
|
|
158
|
+
levels: [
|
|
159
|
+
"#1c1c1c",
|
|
160
|
+
"#3a3a3a",
|
|
161
|
+
"#6b6b6b",
|
|
162
|
+
"#a3a3a3",
|
|
163
|
+
"#f0f0f0"
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
/** Names of the built-in themes. */
|
|
169
|
+
const themeNames = Object.keys(themes);
|
|
170
|
+
/** Returns the theme object for a preset name or a custom theme; unknown names fall back to `github`. */
|
|
171
|
+
function resolveTheme(theme) {
|
|
172
|
+
if (!theme) return themes.github;
|
|
173
|
+
if (typeof theme === "string") return themes[theme] ?? themes.github;
|
|
174
|
+
return theme;
|
|
175
|
+
}
|
|
176
|
+
/** Turns a theme into CSS variables (`--cg-l0-light`, `--cg-l0-dark`…); the stylesheet picks light or dark. */
|
|
177
|
+
function themeVars(theme) {
|
|
178
|
+
const vars = {};
|
|
179
|
+
for (const mode of ["light", "dark"]) {
|
|
180
|
+
const p = theme[mode];
|
|
181
|
+
p.levels.forEach((c, i) => vars[`--cg-l${i}-${mode}`] = c);
|
|
182
|
+
vars[`--cg-text-${mode}`] = p.text;
|
|
183
|
+
vars[`--cg-muted-${mode}`] = p.muted;
|
|
184
|
+
vars[`--cg-surface-${mode}`] = p.surface;
|
|
185
|
+
vars[`--cg-border-${mode}`] = p.border;
|
|
186
|
+
}
|
|
187
|
+
return vars;
|
|
188
|
+
}
|
|
189
|
+
// src/utils.ts
|
|
190
|
+
const DAY_MS = 864e5;
|
|
191
|
+
function parseISO(s) {
|
|
192
|
+
const [y, m, d] = s.split("-").map(Number);
|
|
193
|
+
return new Date(Date.UTC(y, (m ?? 1) - 1, d ?? 1));
|
|
194
|
+
}
|
|
195
|
+
function toISO(d) {
|
|
196
|
+
return d.toISOString().slice(0, 10);
|
|
197
|
+
}
|
|
198
|
+
function addDays(d, n) {
|
|
199
|
+
return new Date(d.getTime() + n * DAY_MS);
|
|
200
|
+
}
|
|
201
|
+
/** Today (the user's local date) as midnight UTC. */
|
|
202
|
+
function todayUTC() {
|
|
203
|
+
const n = /* @__PURE__ */ new Date();
|
|
204
|
+
return new Date(Date.UTC(n.getFullYear(), n.getMonth(), n.getDate()));
|
|
205
|
+
}
|
|
206
|
+
function toUTCDate(d) {
|
|
207
|
+
if (typeof d === "string") return parseISO(d);
|
|
208
|
+
return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
209
|
+
}
|
|
210
|
+
/** Returns a function that maps a count to a level from 0 to 4 for the given counts and scale. */
|
|
211
|
+
function makeLevelFn(counts, scale = "quantile") {
|
|
212
|
+
if (typeof scale !== "string") {
|
|
213
|
+
const [a, b, c, d] = scale;
|
|
214
|
+
return (n) => n >= d ? 4 : n >= c ? 3 : n >= b ? 2 : n >= a && n > 0 ? 1 : 0;
|
|
215
|
+
}
|
|
216
|
+
const nz = counts.filter((c) => c > 0).sort((x, y) => x - y);
|
|
217
|
+
if (nz.length === 0) return () => 0;
|
|
218
|
+
const max = nz[nz.length - 1];
|
|
219
|
+
if (scale === "linear") return (n) => n <= 0 ? 0 : Math.min(4, Math.max(1, Math.ceil(n / max * 4)));
|
|
220
|
+
const q = (p) => nz[Math.floor(p * (nz.length - 1))];
|
|
221
|
+
const q1 = q(.25);
|
|
222
|
+
const q2 = q(.5);
|
|
223
|
+
const q3 = q(.75);
|
|
224
|
+
return (n) => n <= 0 ? 0 : n <= q1 ? 1 : n <= q2 ? 2 : n <= q3 ? 3 : 4;
|
|
225
|
+
}
|
|
226
|
+
/** Lays out days as a weeks × 7 grid with levels, totals and month labels. Dates are handled in UTC. */
|
|
227
|
+
function buildCalendar(data, opts = {}) {
|
|
228
|
+
const weekCount = Math.max(1, Math.floor(opts.weeks ?? 53));
|
|
229
|
+
const weekStart = opts.weekStart ?? 0;
|
|
230
|
+
const end = opts.endDate ? toUTCDate(opts.endDate) : todayUTC();
|
|
231
|
+
const first = addDays(end, -((end.getUTCDay() - weekStart + 7) % 7) - (weekCount - 1) * 7);
|
|
232
|
+
const counts = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const d of data) {
|
|
234
|
+
if (!d || typeof d.date !== "string") continue;
|
|
235
|
+
const c = Number(d.count) || 0;
|
|
236
|
+
counts.set(d.date, (counts.get(d.date) ?? 0) + Math.max(0, c));
|
|
237
|
+
}
|
|
238
|
+
const days = [];
|
|
239
|
+
const weeks = [];
|
|
240
|
+
const endTime = end.getTime();
|
|
241
|
+
for (let w = 0; w < weekCount; w++) {
|
|
242
|
+
const week = [];
|
|
243
|
+
for (let r = 0; r < 7; r++) {
|
|
244
|
+
const date = addDays(first, w * 7 + r);
|
|
245
|
+
if (date.getTime() > endTime) {
|
|
246
|
+
week.push(null);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const iso = toISO(date);
|
|
250
|
+
const day = {
|
|
251
|
+
date: iso,
|
|
252
|
+
count: counts.get(iso) ?? 0,
|
|
253
|
+
level: 0,
|
|
254
|
+
index: days.length,
|
|
255
|
+
weekIndex: w,
|
|
256
|
+
dayIndex: r,
|
|
257
|
+
weekday: date.getUTCDay()
|
|
258
|
+
};
|
|
259
|
+
days.push(day);
|
|
260
|
+
week.push(day);
|
|
261
|
+
}
|
|
262
|
+
weeks.push(week);
|
|
263
|
+
}
|
|
264
|
+
const levelOf = makeLevelFn(days.map((d) => d.count), opts.scale);
|
|
265
|
+
let total = 0;
|
|
266
|
+
let max = 0;
|
|
267
|
+
for (const d of days) {
|
|
268
|
+
d.level = levelOf(d.count);
|
|
269
|
+
total += d.count;
|
|
270
|
+
if (d.count > max) max = d.count;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
weeks,
|
|
274
|
+
days,
|
|
275
|
+
months: monthLabels(weeks, opts.locale),
|
|
276
|
+
total,
|
|
277
|
+
max,
|
|
278
|
+
start: days[0]?.date ?? toISO(first),
|
|
279
|
+
end: days[days.length - 1]?.date ?? toISO(end),
|
|
280
|
+
levelOf
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/** Keeps the last `n` weeks. Levels come from the full calendar, so cropping doesn't change colors. */
|
|
284
|
+
function cropCalendar(cal, n, locale) {
|
|
285
|
+
if (n >= cal.weeks.length) return cal;
|
|
286
|
+
const days = [];
|
|
287
|
+
const weeks = cal.weeks.slice(cal.weeks.length - n).map((w, wi) => w.map((d) => {
|
|
288
|
+
if (!d) return null;
|
|
289
|
+
const nd = {
|
|
290
|
+
...d,
|
|
291
|
+
weekIndex: wi,
|
|
292
|
+
index: days.length
|
|
293
|
+
};
|
|
294
|
+
days.push(nd);
|
|
295
|
+
return nd;
|
|
296
|
+
}));
|
|
297
|
+
return {
|
|
298
|
+
...cal,
|
|
299
|
+
weeks,
|
|
300
|
+
days,
|
|
301
|
+
months: monthLabels(weeks, locale),
|
|
302
|
+
total: days.reduce((s, d) => s + d.count, 0),
|
|
303
|
+
max: days.reduce((m, d) => Math.max(m, d.count), 0),
|
|
304
|
+
start: days[0]?.date ?? cal.start
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function monthLabels(weeks, locale) {
|
|
308
|
+
const fmt = new Intl.DateTimeFormat(locale ?? "en", {
|
|
309
|
+
month: "short",
|
|
310
|
+
timeZone: "UTC"
|
|
311
|
+
});
|
|
312
|
+
const out = [];
|
|
313
|
+
weeks.forEach((week, w) => {
|
|
314
|
+
const days = week.filter((d) => d !== null);
|
|
315
|
+
if (!days.length) return;
|
|
316
|
+
const target = w === 0 ? days[0] : days.find((d) => d.date.endsWith("-01"));
|
|
317
|
+
if (!target) return;
|
|
318
|
+
const dt = parseISO(target.date);
|
|
319
|
+
out.push({
|
|
320
|
+
label: fmt.format(dt),
|
|
321
|
+
weekIndex: w,
|
|
322
|
+
month: dt.getUTCMonth(),
|
|
323
|
+
year: dt.getUTCFullYear()
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
if (out.length > 1 && out[1].weekIndex - out[0].weekIndex < 3) out.shift();
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
329
|
+
/** Total, active days, best day, longest streak and current streak. A current streak isn't broken by a quiet today. */
|
|
330
|
+
function computeStats(days) {
|
|
331
|
+
let total = 0;
|
|
332
|
+
let activeDays = 0;
|
|
333
|
+
let best = null;
|
|
334
|
+
let longest = 0;
|
|
335
|
+
let run = 0;
|
|
336
|
+
for (const d of days) {
|
|
337
|
+
total += d.count;
|
|
338
|
+
if (d.count > 0) {
|
|
339
|
+
activeDays++;
|
|
340
|
+
run++;
|
|
341
|
+
if (run > longest) longest = run;
|
|
342
|
+
if (!best || d.count > best.count) best = d;
|
|
343
|
+
} else run = 0;
|
|
344
|
+
}
|
|
345
|
+
let current = 0;
|
|
346
|
+
let i = days.length - 1;
|
|
347
|
+
if (i >= 0 && days[i].count === 0) i--;
|
|
348
|
+
for (; i >= 0 && days[i].count > 0; i--) current++;
|
|
349
|
+
return {
|
|
350
|
+
total,
|
|
351
|
+
activeDays,
|
|
352
|
+
best,
|
|
353
|
+
longestStreak: longest,
|
|
354
|
+
currentStreak: current
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/** Formats an ISO date with `Intl`, in UTC so the day never shifts. */
|
|
358
|
+
function formatDate(iso, locale, opts = {
|
|
359
|
+
weekday: "short",
|
|
360
|
+
month: "short",
|
|
361
|
+
day: "numeric",
|
|
362
|
+
year: "numeric"
|
|
363
|
+
}) {
|
|
364
|
+
return new Intl.DateTimeFormat(locale ?? "en", {
|
|
365
|
+
...opts,
|
|
366
|
+
timeZone: "UTC"
|
|
367
|
+
}).format(parseISO(iso));
|
|
368
|
+
}
|
|
369
|
+
/** Formats a number with `Intl`. */
|
|
370
|
+
function formatNumber(n, locale) {
|
|
371
|
+
return new Intl.NumberFormat(locale ?? "en").format(n);
|
|
372
|
+
}
|
|
373
|
+
/** Short names of the 7 weekdays starting at `weekStart`. */
|
|
374
|
+
function weekdayLabels(locale, weekStart = 0) {
|
|
375
|
+
const fmt = new Intl.DateTimeFormat(locale ?? "en", {
|
|
376
|
+
weekday: "short",
|
|
377
|
+
timeZone: "UTC"
|
|
378
|
+
});
|
|
379
|
+
return Array.from({ length: 7 }, (_, i) => fmt.format(new Date(Date.UTC(2023, 0, 1 + (i + weekStart) % 7))));
|
|
380
|
+
}
|
|
381
|
+
/** Picks the singular or plural unit for a count. */
|
|
382
|
+
function pluralize(n, unit = ["contribution", "contributions"]) {
|
|
383
|
+
return n === 1 ? unit[0] : unit[1];
|
|
384
|
+
}
|
|
385
|
+
// src/sample.ts
|
|
386
|
+
/** Available sample profiles with a short English label. */
|
|
387
|
+
const sampleProfiles = {
|
|
388
|
+
steady: "Steady",
|
|
389
|
+
burst: "Bursts",
|
|
390
|
+
weekend: "Weekends",
|
|
391
|
+
sparse: "Sparse"
|
|
392
|
+
};
|
|
393
|
+
function mulberry32(seed) {
|
|
394
|
+
let a = seed >>> 0;
|
|
395
|
+
return () => {
|
|
396
|
+
a = a + 1831565813 >>> 0;
|
|
397
|
+
let t = a;
|
|
398
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
399
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
400
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/** Deterministic sample data: the same seed always gives the same days. Useful for demos and tests. */
|
|
404
|
+
function generateSampleData({ seed = 7, profile = "steady", days = 371, endDate } = {}) {
|
|
405
|
+
const rng = mulberry32(seed * 9973 + profile.length * 131);
|
|
406
|
+
const start = addDays(endDate ?? todayUTC(), -(days - 1));
|
|
407
|
+
const geo = (mean) => Math.floor(-Math.log(1 - rng()) * mean) + 1;
|
|
408
|
+
const gaps = Array.from({ length: profile === "steady" ? 2 : 1 }, () => {
|
|
409
|
+
const s = Math.floor(rng() * (days - 20));
|
|
410
|
+
return [s, s + 6 + Math.floor(rng() * 8)];
|
|
411
|
+
});
|
|
412
|
+
const sprints = [];
|
|
413
|
+
for (let d = 0; d < days;) {
|
|
414
|
+
d += 10 + Math.floor(rng() * 35);
|
|
415
|
+
const len = 6 + Math.floor(rng() * 16);
|
|
416
|
+
sprints.push([d, d + len]);
|
|
417
|
+
d += len;
|
|
418
|
+
}
|
|
419
|
+
const out = [];
|
|
420
|
+
for (let i = 0; i < days; i++) {
|
|
421
|
+
const date = addDays(start, i);
|
|
422
|
+
const wd = date.getUTCDay();
|
|
423
|
+
const weekend = wd === 0 || wd === 6;
|
|
424
|
+
const inGap = gaps.some(([a, b]) => i >= a && i <= b);
|
|
425
|
+
const inSprint = sprints.some(([a, b]) => i >= a && i <= b);
|
|
426
|
+
let p;
|
|
427
|
+
let mean;
|
|
428
|
+
switch (profile) {
|
|
429
|
+
case "burst":
|
|
430
|
+
p = inSprint ? .92 : .12;
|
|
431
|
+
mean = inSprint ? 9 : 2;
|
|
432
|
+
break;
|
|
433
|
+
case "weekend":
|
|
434
|
+
p = weekend ? .85 : .18;
|
|
435
|
+
mean = weekend ? 8 : 2;
|
|
436
|
+
break;
|
|
437
|
+
case "sparse":
|
|
438
|
+
p = .14;
|
|
439
|
+
mean = 3;
|
|
440
|
+
break;
|
|
441
|
+
default:
|
|
442
|
+
p = inGap ? 0 : weekend ? .3 : .78;
|
|
443
|
+
mean = weekend ? 2 : 4.5;
|
|
444
|
+
}
|
|
445
|
+
p = Math.min(1, p * (.8 + i / days * .4));
|
|
446
|
+
out.push({
|
|
447
|
+
date: toISO(date),
|
|
448
|
+
count: rng() < p ? geo(mean) : 0
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
return out;
|
|
452
|
+
}
|
|
453
|
+
// src/adapters/index.ts
|
|
454
|
+
/**
|
|
455
|
+
* GitHub GraphQL query for the contribution calendar. It needs a token even
|
|
456
|
+
* for public data, so run it on the server and never send the token to the browser.
|
|
457
|
+
*/
|
|
458
|
+
const GITHUB_CONTRIBUTIONS_QUERY = `
|
|
459
|
+
query ($login: String!, $from: DateTime, $to: DateTime) {
|
|
460
|
+
user(login: $login) {
|
|
461
|
+
contributionsCollection(from: $from, to: $to) {
|
|
462
|
+
contributionCalendar {
|
|
463
|
+
totalContributions
|
|
464
|
+
weeks { contributionDays { date contributionCount } }
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
`;
|
|
470
|
+
/** Converts a GitHub GraphQL response into `{ date, count }` days. */
|
|
471
|
+
function fromGitHub(res) {
|
|
472
|
+
return (res.data?.user?.contributionsCollection?.contributionCalendar?.weeks ?? []).flatMap((w) => w.contributionDays.map((d) => ({
|
|
473
|
+
date: d.date,
|
|
474
|
+
count: d.contributionCount
|
|
475
|
+
})));
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Fetches the last year of contributions from GitHub's GraphQL API. Server only (Node 18+, Deno, Bun, edge runtimes).
|
|
479
|
+
* A classic token with `read:user` includes private contributions.
|
|
480
|
+
*/
|
|
481
|
+
async function fetchGitHubContributions(login, token) {
|
|
482
|
+
const res = await fetch("https://api.github.com/graphql", {
|
|
483
|
+
method: "POST",
|
|
484
|
+
headers: {
|
|
485
|
+
Authorization: `bearer ${token}`,
|
|
486
|
+
"Content-Type": "application/json"
|
|
487
|
+
},
|
|
488
|
+
body: JSON.stringify({
|
|
489
|
+
query: GITHUB_CONTRIBUTIONS_QUERY,
|
|
490
|
+
variables: { login }
|
|
491
|
+
})
|
|
492
|
+
});
|
|
493
|
+
if (!res.ok) throw new Error(`GitHub API ${res.status}`);
|
|
494
|
+
return fromGitHub(await res.json());
|
|
495
|
+
}
|
|
496
|
+
/** Converts GitLab's `{ "2026-03-01": 4 }` calendar into `{ date, count }` days. */
|
|
497
|
+
function fromGitLab(json) {
|
|
498
|
+
return Object.entries(json).map(([date, count]) => ({
|
|
499
|
+
date,
|
|
500
|
+
count
|
|
501
|
+
}));
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Fetches a GitLab user's calendar. The endpoint is public but sends no CORS headers,
|
|
505
|
+
* so call it from the server or at build time. Pass `baseUrl` for self-hosted instances.
|
|
506
|
+
*/
|
|
507
|
+
async function fetchGitLabContributions(username, baseUrl = "https://gitlab.com") {
|
|
508
|
+
const res = await fetch(`${baseUrl}/users/${encodeURIComponent(username)}/calendar.json`);
|
|
509
|
+
if (!res.ok) throw new Error(`GitLab ${res.status}`);
|
|
510
|
+
return fromGitLab(await res.json());
|
|
511
|
+
}
|
|
512
|
+
/** Counts occurrences per day: blog posts, workouts, local commits… Strings are sliced to the date; `Date` objects use the local date. */
|
|
513
|
+
function fromDates(dates) {
|
|
514
|
+
const map = /* @__PURE__ */ new Map();
|
|
515
|
+
for (const d of dates) {
|
|
516
|
+
const iso = typeof d === "string" ? d.slice(0, 10) : toISO(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())));
|
|
517
|
+
map.set(iso, (map.get(iso) ?? 0) + 1);
|
|
518
|
+
}
|
|
519
|
+
return [...map].map(([date, count]) => ({
|
|
520
|
+
date,
|
|
521
|
+
count
|
|
522
|
+
}));
|
|
523
|
+
}
|
|
524
|
+
/** Adds several sources day by day (for example GitHub plus GitLab), sorted by date. */
|
|
525
|
+
function mergeSources(...sources) {
|
|
526
|
+
const map = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const src of sources) for (const d of src) map.set(d.date, (map.get(d.date) ?? 0) + d.count);
|
|
528
|
+
return [...map].sort(([a], [b]) => a < b ? -1 : 1).map(([date, count]) => ({
|
|
529
|
+
date,
|
|
530
|
+
count
|
|
531
|
+
}));
|
|
532
|
+
}
|
|
533
|
+
Object.defineProperty(exports, "GITHUB_CONTRIBUTIONS_QUERY", {
|
|
534
|
+
enumerable: true,
|
|
535
|
+
get: function() {
|
|
536
|
+
return GITHUB_CONTRIBUTIONS_QUERY;
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
Object.defineProperty(exports, "buildCalendar", {
|
|
540
|
+
enumerable: true,
|
|
541
|
+
get: function() {
|
|
542
|
+
return buildCalendar;
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
Object.defineProperty(exports, "computeStats", {
|
|
546
|
+
enumerable: true,
|
|
547
|
+
get: function() {
|
|
548
|
+
return computeStats;
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
Object.defineProperty(exports, "cropCalendar", {
|
|
552
|
+
enumerable: true,
|
|
553
|
+
get: function() {
|
|
554
|
+
return cropCalendar;
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
Object.defineProperty(exports, "fetchGitHubContributions", {
|
|
558
|
+
enumerable: true,
|
|
559
|
+
get: function() {
|
|
560
|
+
return fetchGitHubContributions;
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
Object.defineProperty(exports, "fetchGitLabContributions", {
|
|
564
|
+
enumerable: true,
|
|
565
|
+
get: function() {
|
|
566
|
+
return fetchGitLabContributions;
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
Object.defineProperty(exports, "formatDate", {
|
|
570
|
+
enumerable: true,
|
|
571
|
+
get: function() {
|
|
572
|
+
return formatDate;
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
Object.defineProperty(exports, "formatNumber", {
|
|
576
|
+
enumerable: true,
|
|
577
|
+
get: function() {
|
|
578
|
+
return formatNumber;
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
Object.defineProperty(exports, "fromDates", {
|
|
582
|
+
enumerable: true,
|
|
583
|
+
get: function() {
|
|
584
|
+
return fromDates;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
Object.defineProperty(exports, "fromGitHub", {
|
|
588
|
+
enumerable: true,
|
|
589
|
+
get: function() {
|
|
590
|
+
return fromGitHub;
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
Object.defineProperty(exports, "fromGitLab", {
|
|
594
|
+
enumerable: true,
|
|
595
|
+
get: function() {
|
|
596
|
+
return fromGitLab;
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
Object.defineProperty(exports, "generateSampleData", {
|
|
600
|
+
enumerable: true,
|
|
601
|
+
get: function() {
|
|
602
|
+
return generateSampleData;
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
Object.defineProperty(exports, "makeLevelFn", {
|
|
606
|
+
enumerable: true,
|
|
607
|
+
get: function() {
|
|
608
|
+
return makeLevelFn;
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
Object.defineProperty(exports, "mergeSources", {
|
|
612
|
+
enumerable: true,
|
|
613
|
+
get: function() {
|
|
614
|
+
return mergeSources;
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
Object.defineProperty(exports, "pluralize", {
|
|
618
|
+
enumerable: true,
|
|
619
|
+
get: function() {
|
|
620
|
+
return pluralize;
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
Object.defineProperty(exports, "resolveTheme", {
|
|
624
|
+
enumerable: true,
|
|
625
|
+
get: function() {
|
|
626
|
+
return resolveTheme;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
Object.defineProperty(exports, "sampleProfiles", {
|
|
630
|
+
enumerable: true,
|
|
631
|
+
get: function() {
|
|
632
|
+
return sampleProfiles;
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
Object.defineProperty(exports, "themeNames", {
|
|
636
|
+
enumerable: true,
|
|
637
|
+
get: function() {
|
|
638
|
+
return themeNames;
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
Object.defineProperty(exports, "themeVars", {
|
|
642
|
+
enumerable: true,
|
|
643
|
+
get: function() {
|
|
644
|
+
return themeVars;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
Object.defineProperty(exports, "themes", {
|
|
648
|
+
enumerable: true,
|
|
649
|
+
get: function() {
|
|
650
|
+
return themes;
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
Object.defineProperty(exports, "weekdayLabels", {
|
|
654
|
+
enumerable: true,
|
|
655
|
+
get: function() {
|
|
656
|
+
return weekdayLabels;
|
|
657
|
+
}
|
|
658
|
+
});
|
package/dist/data.cjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_data = require("./data-w1Mb5e5D.cjs");
|
|
4
|
+
exports.GITHUB_CONTRIBUTIONS_QUERY = require_data.GITHUB_CONTRIBUTIONS_QUERY;
|
|
5
|
+
exports.buildCalendar = require_data.buildCalendar;
|
|
6
|
+
exports.computeStats = require_data.computeStats;
|
|
7
|
+
exports.cropCalendar = require_data.cropCalendar;
|
|
8
|
+
exports.fetchGitHubContributions = require_data.fetchGitHubContributions;
|
|
9
|
+
exports.fetchGitLabContributions = require_data.fetchGitLabContributions;
|
|
10
|
+
exports.formatDate = require_data.formatDate;
|
|
11
|
+
exports.fromDates = require_data.fromDates;
|
|
12
|
+
exports.fromGitHub = require_data.fromGitHub;
|
|
13
|
+
exports.fromGitLab = require_data.fromGitLab;
|
|
14
|
+
exports.generateSampleData = require_data.generateSampleData;
|
|
15
|
+
exports.makeLevelFn = require_data.makeLevelFn;
|
|
16
|
+
exports.mergeSources = require_data.mergeSources;
|
|
17
|
+
exports.resolveTheme = require_data.resolveTheme;
|
|
18
|
+
exports.sampleProfiles = require_data.sampleProfiles;
|
|
19
|
+
exports.themeNames = require_data.themeNames;
|
|
20
|
+
exports.themeVars = require_data.themeVars;
|
|
21
|
+
exports.themes = require_data.themes;
|
package/dist/data.d.cts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* `contrib-graphs/data`: todo lo que no es un componente. Sin "use client", así
|
|
4
|
+
* que se puede usar en Server Components, rutas de API, scripts de build, etc.
|
|
5
|
+
*/
|
|
6
|
+
import { A as ThemePalette, C as ContributionDay, D as GraphTheme, E as MonthLabel, M as themeNames, N as themeVars, O as LevelColors, P as themes, S as ColorMode, T as LevelScale, _ as formatDate, a as fromDates, b as Calendar, c as mergeSources, d as sampleProfiles, f as CalendarOptions, g as cropCalendar, h as computeStats, i as fetchGitLabContributions, j as resolveTheme, k as ThemeName, l as SampleProfile, m as buildCalendar, n as GitHubCalendarResponse, o as fromGitHub, p as GraphStats, r as fetchGitHubContributions, s as fromGitLab, t as GITHUB_CONTRIBUTIONS_QUERY, u as generateSampleData, v as makeLevelFn, w as Level, x as CalendarDay, y as BaseGraphProps } from "./data-DptvXfjH.cjs";
|
|
7
|
+
export { type BaseGraphProps, type Calendar, type CalendarDay, type CalendarOptions, type ColorMode, type ContributionDay, GITHUB_CONTRIBUTIONS_QUERY, GitHubCalendarResponse, type GraphStats, type GraphTheme, type Level, type LevelColors, type LevelScale, type MonthLabel, type SampleProfile, type ThemeName, type ThemePalette, buildCalendar, computeStats, cropCalendar, fetchGitHubContributions, fetchGitLabContributions, formatDate, fromDates, fromGitHub, fromGitLab, generateSampleData, makeLevelFn, mergeSources, resolveTheme, sampleProfiles, themeNames, themeVars, themes };
|
package/dist/data.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* `contrib-graphs/data`: todo lo que no es un componente. Sin "use client", así
|
|
4
|
+
* que se puede usar en Server Components, rutas de API, scripts de build, etc.
|
|
5
|
+
*/
|
|
6
|
+
import { A as ThemePalette, C as ContributionDay, D as GraphTheme, E as MonthLabel, M as themeNames, N as themeVars, O as LevelColors, P as themes, S as ColorMode, T as LevelScale, _ as formatDate, a as fromDates, b as Calendar, c as mergeSources, d as sampleProfiles, f as CalendarOptions, g as cropCalendar, h as computeStats, i as fetchGitLabContributions, j as resolveTheme, k as ThemeName, l as SampleProfile, m as buildCalendar, n as GitHubCalendarResponse, o as fromGitHub, p as GraphStats, r as fetchGitHubContributions, s as fromGitLab, t as GITHUB_CONTRIBUTIONS_QUERY, u as generateSampleData, v as makeLevelFn, w as Level, x as CalendarDay, y as BaseGraphProps } from "./data-DptvXfjH.js";
|
|
7
|
+
export { type BaseGraphProps, type Calendar, type CalendarDay, type CalendarOptions, type ColorMode, type ContributionDay, GITHUB_CONTRIBUTIONS_QUERY, GitHubCalendarResponse, type GraphStats, type GraphTheme, type Level, type LevelColors, type LevelScale, type MonthLabel, type SampleProfile, type ThemeName, type ThemePalette, buildCalendar, computeStats, cropCalendar, fetchGitHubContributions, fetchGitLabContributions, formatDate, fromDates, fromGitHub, fromGitLab, generateSampleData, makeLevelFn, mergeSources, resolveTheme, sampleProfiles, themeNames, themeVars, themes };
|
package/dist/data.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
|
|
2
|
+
export { t as GITHUB_CONTRIBUTIONS_QUERY, u as buildCalendar, d as computeStats, f as cropCalendar, n as fetchGitHubContributions, r as fetchGitLabContributions, p as formatDate, i as fromDates, a as fromGitHub, o as fromGitLab, c as generateSampleData, h as makeLevelFn, s as mergeSources, v as resolveTheme, l as sampleProfiles, y as themeNames, b as themeVars, x as themes } from "./data-oHgoFFM5.js";
|