Repository navigation
Expand file tree
/
Copy pathcontent.js
More file actions
206 lines (175 loc) · 7.99 KB
/
Copy pathcontent.js
File metadata and controls
206 lines (175 loc) · 7.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
(function () {
'use strict';
const SELECTOR_DATA = 'script[data-target*="embeddedData"], script[type="application/json"]';
const INJECT_CLASS = 'gh-repo-creation-date';
// In-memory cache for repository creation dates keyed by "owner/repo"
const dateCache = new Map();
const pendingFetches = new Map();
function getRepoKey() {
const parts = window.location.pathname.split('/').filter(Boolean);
if (parts.length >= 2) {
const nonRepoRoots = [
'settings', 'marketplace', 'explore', 'topics', 'trending',
'collections', 'events', 'sponsors', 'organizations',
'account', 'notifications', 'login', 'signup', 'orgs'
];
if (!nonRepoRoots.includes(parts[0].toLowerCase())) {
return `${parts[0]}/${parts[1]}`.toLowerCase();
}
}
return null;
}
function findRepoCreatedAt(obj) {
if (typeof obj !== 'object' || obj === null) return null;
if (obj.repo && obj.repo.createdAt) return obj.repo.createdAt;
if (obj.repository && obj.repository.createdAt) return obj.repository.createdAt;
if (obj.createdAt && typeof obj.createdAt === 'string' && obj.createdAt.includes('T')) {
if (obj.name || obj.id || obj.owner) return obj.createdAt;
}
for (const key in obj) {
try {
const res = findRepoCreatedAt(obj[key]);
if (res) return res;
} catch (e) { }
}
return null;
}
async function getCreatedAtDate() {
const repoKey = getRepoKey();
if (repoKey && dateCache.has(repoKey)) {
return dateCache.get(repoKey);
}
// 1. Try finding in embedded JSON script tags
const scriptTags = Array.from(document.querySelectorAll(SELECTOR_DATA));
for (const tag of scriptTags) {
try {
const data = JSON.parse(tag.textContent);
const createdAt = findRepoCreatedAt(data);
if (createdAt) {
if (repoKey) dateCache.set(repoKey, createdAt);
return createdAt;
}
} catch (e) { }
}
// 2. Fallback: Fetch directly from GitHub API
if (repoKey) {
if (pendingFetches.has(repoKey)) {
return pendingFetches.get(repoKey);
}
const fetchPromise = (async () => {
try {
const response = await fetch(`https://api.github.com/repos/${repoKey}`);
if (response.ok) {
const data = await response.json();
if (data && data.created_at) {
dateCache.set(repoKey, data.created_at);
return data.created_at;
}
}
} catch (err) {
console.warn('[GitHub Repo Creation Date] API fallback failed:', err);
} finally {
pendingFetches.delete(repoKey);
}
return null;
})();
pendingFetches.set(repoKey, fetchPromise);
return fetchPromise;
}
return null;
}
function getInjectionPoints() {
// GitHub can render multiple "About" headings (e.g. desktop sidebar vs responsive mobile section)
const aboutHeadings = Array.from(document.querySelectorAll('h2, [data-component="Heading"]'))
.filter(h => {
const text = h.textContent.trim();
return /^(about|repository details)/i.test(text);
});
if (aboutHeadings.length === 0) return [];
const injectionPoints = [];
const selectors = [
'p.f4',
'p[class*="description"]',
'div.tmp-my-3',
'div[class*="websiteRow"]',
'.list-topics-container',
'div[class*="TopicTagGroup"]',
'div[class*="tagGroup"]'
];
for (const aboutHeading of aboutHeadings) {
const aboutSection = aboutHeading.closest('[class*="sidebarSection"], [class*="BorderGrid-cell"], [class*="borderGrid"] > div') || aboutHeading.parentElement;
if (!aboutSection) continue;
// Skip if this section already has our creation date element
if (aboutSection.querySelector(`.${INJECT_CLASS}`)) continue;
const containers = Array.from(aboutSection.querySelectorAll(selectors.join(', ')));
if (containers.length > 0) {
const lastContainer = containers[containers.length - 1];
injectionPoints.push({ section: aboutSection, element: lastContainer, position: 'afterend' });
} else {
injectionPoints.push({ section: aboutSection, element: aboutHeading, position: 'afterend' });
}
}
return injectionPoints;
}
function createCreationBlock(formattedDate) {
const creationBlock = document.createElement('div');
creationBlock.className = `${INJECT_CLASS} mt-2 color-fg-muted`;
creationBlock.innerHTML = `<h3 class="sr-only">Creation Date</h3><svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" class="octicon octicon-calendar mr-2" style="vertical-align: text-bottom; margin-right: 8px !important;"><path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"></path></svg><span>${formattedDate}</span>`;
return creationBlock;
}
async function injectCreationDate(forceClear) {
if (forceClear) {
document.querySelectorAll(`.${INJECT_CLASS}`).forEach(el => el.remove());
}
const injectionPoints = getInjectionPoints();
if (injectionPoints.length === 0) return;
try {
const createdAt = await getCreatedAtDate();
if (!createdAt) return;
const date = new Date(createdAt);
const formattedDate = date.toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
});
// Re-fetch points in case DOM shifted while awaiting
const currentPoints = getInjectionPoints();
for (const injection of currentPoints) {
if (!injection.section.querySelector(`.${INJECT_CLASS}`)) {
const block = createCreationBlock(formattedDate);
injection.element.insertAdjacentElement(injection.position, block);
}
}
} catch (e) {
console.warn('[GitHub Repo Creation Date] Injection error:', e);
}
}
console.log('[GitHub Repo Creation Date Extension] Active');
injectCreationDate(false);
// Support GitHub Turbo / soft-navigation events
let currentRepo = getRepoKey();
function handleNavigation() {
const newRepo = getRepoKey();
const repoChanged = newRepo !== currentRepo;
if (repoChanged) {
currentRepo = newRepo;
injectCreationDate(true);
} else {
injectCreationDate(false);
}
}
document.addEventListener('turbo:load', () => handleNavigation());
document.addEventListener('turbo:render', () => handleNavigation());
document.addEventListener('soft-nav:end', () => handleNavigation());
let lastUrl = location.href;
const observer = new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
handleNavigation();
} else if (getInjectionPoints().length > 0) {
injectCreationDate(false);
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();