mirror of
https://github.com/GetOpenScript/DisplayRepoSizeGitHub.openscript.git
synced 2026-09-18 10:35:42 +00:00
Rename to DisplayRepoInfo.os.js and show repo age with creation time tooltip
This commit is contained in:
216
DisplayRepoInfo.os.js
Normal file
216
DisplayRepoInfo.os.js
Normal file
@@ -0,0 +1,216 @@
|
||||
// ==UserScript==
|
||||
// @name Display Repo Info and File Sizes for GitHub
|
||||
// @description Displays repository disk usage, age, and individual file sizes on GitHub.
|
||||
// @match https://github.com/*/*
|
||||
// ==/UserScript==
|
||||
|
||||
const ROW_ID = 'openscript-repo-info-about';
|
||||
const SIZE_CLASS = 'openscript-file-size';
|
||||
const TABLE_SELECTOR = 'table[aria-labelledby="folders-and-files"]';
|
||||
|
||||
const formatBytes = b => {
|
||||
const u = ['B', 'KB', 'MB', 'GB'];
|
||||
let i = 0;
|
||||
for (; b >= 1024 && i < 3; i++) b /= 1024;
|
||||
return `${i && b < 10 ? b.toFixed(1) : Math.round(b)} ${u[i]}`;
|
||||
};
|
||||
|
||||
const formatAge = d => {
|
||||
const days = Math.max(0, Math.floor((Date.now() - new Date(d)) / 864e5));
|
||||
const y = Math.floor(days / 365.25);
|
||||
const m = Math.floor((days % 365.25) / 30.44);
|
||||
if (y) return `${y} year${y > 1 ? 's' : ''}${m ? `, ${m} month${m > 1 ? 's' : ''}` : ''}`;
|
||||
if (m) return `${m} month${m > 1 ? 's' : ''}`;
|
||||
return `${days || '< 1'} day${days === 1 ? '' : 's'}`;
|
||||
};
|
||||
|
||||
const getRepoInfo = () => {
|
||||
const [, owner, repo] = location.pathname.split('/');
|
||||
return owner && repo && !/^(settings|orgs|organizations|notifications|search|features|pricing|explore|marketplace|topics|trending)$/i.test(owner)
|
||||
? { owner, repo } : null;
|
||||
};
|
||||
|
||||
const getPat = () => {
|
||||
const envObj = typeof OpenScript !== 'undefined' ? OpenScript.env : typeof env !== 'undefined' ? env : {};
|
||||
const [, val] = Object.entries(envObj || {}).find(([k, v]) => /^(GH_PAT|GITHUB_PAT|GITHUB_TOKEN|PAT)$/i.test(k) && v) || [];
|
||||
return val ? String(val).trim() : '';
|
||||
};
|
||||
|
||||
const getHeaders = () => {
|
||||
const pat = getPat();
|
||||
return {
|
||||
Accept: 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
...(pat && { Authorization: `Bearer ${pat}` }),
|
||||
};
|
||||
};
|
||||
|
||||
const fetchRepoInfo = async ({ owner, repo }) => {
|
||||
try {
|
||||
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
||||
headers: getHeaders(),
|
||||
});
|
||||
if (res.status === 404)
|
||||
return { err: 'private (missing GH_PAT)', tip: 'Set GH_PAT in OpenScript for private repository access' };
|
||||
if (res.status === 401)
|
||||
return { err: 'invalid GH_PAT', tip: 'GitHub rejected the configured token' };
|
||||
if (!res.ok)
|
||||
return { err: `API error (${res.status})`, tip: `GitHub API returned ${res.status}` };
|
||||
|
||||
const { size: kb = 0, created_at: created } = await res.json();
|
||||
return {
|
||||
size: {
|
||||
text: kb ? formatBytes(kb * 1024) : '0 KB (calculating...)',
|
||||
tip: kb ? `Total repository disk usage (including full git history): ${kb.toLocaleString()} KB`
|
||||
: 'GitHub is still calculating disk usage for this repository',
|
||||
},
|
||||
age: created ? {
|
||||
text: formatAge(created),
|
||||
tip: `Created: ${new Date(created).toLocaleString(undefined, { dateStyle: 'full', timeStyle: 'long' })}`,
|
||||
} : null,
|
||||
};
|
||||
} catch {
|
||||
return { err: 'failed to load', tip: 'Network or fetch error' };
|
||||
}
|
||||
};
|
||||
|
||||
const getCodeView = () => {
|
||||
try {
|
||||
const app = document.querySelector('react-app[app-name="code-view"]');
|
||||
const data = JSON.parse(app?.querySelector('script[type="application/json"]')?.textContent || '{}');
|
||||
const route = data.payload?.codeViewRepoRoute || data.payload?.codeViewTreeRoute;
|
||||
const info = getRepoInfo();
|
||||
if (!info || !route?.tree?.items || !route.refInfo) return null;
|
||||
const path = String(route.path || '').replace(/^\/+|\/+$/g, '');
|
||||
const ref = route.refInfo.currentOid || route.refInfo.name;
|
||||
return { ...info, path, ref, items: route.tree.items, key: `${info.owner}/${info.repo}:${ref}:${path}` };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const findAboutHeading = () => [...document.querySelectorAll('h2')].find(h =>
|
||||
/^\s*About\s*$/i.test(h.textContent) &&
|
||||
h.closest('[data-component="SplitPageLayout.Pane"], .Layout-sidebar, .BorderGrid')
|
||||
);
|
||||
|
||||
const updateAboutInfo = async () => {
|
||||
const info = getRepoInfo();
|
||||
if (!info) return;
|
||||
|
||||
const key = `${info.owner}/${info.repo}`;
|
||||
const existing = document.getElementById(ROW_ID);
|
||||
if (existing?.dataset.repo === key) return;
|
||||
existing?.remove();
|
||||
document.getElementById('openscript-repo-size-about')?.remove();
|
||||
document.getElementById('openscript-repo-size')?.remove();
|
||||
|
||||
const heading = findAboutHeading();
|
||||
if (!heading) return;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.id = ROW_ID;
|
||||
container.dataset.repo = key;
|
||||
container.innerHTML = `
|
||||
<div class="openscript-size-row mt-2 text-small color-fg-muted d-flex flex-items-center">
|
||||
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" width="16" class="octicon octicon-database mr-2 color-fg-muted" fill="currentColor">
|
||||
<path d="M1 3.5c0-.83.67-1.5 1.5-1.5h11c.83 0 1.5.67 1.5 1.5v9c0 .83-.67 1.5-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9Zm1.5-.5a.5.5 0 0 0-.5.5V5h12V3.5a.5.5 0 0 0-.5-.5h-11ZM14 6H2v2h12V6Zm0 3H2v3.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V9Z"></path>
|
||||
</svg>
|
||||
<span><strong class="size-val color-fg-default font-semibold">calculating...</strong> repo size</span>
|
||||
</div>
|
||||
<div class="openscript-age-row mt-2 text-small color-fg-muted d-flex flex-items-center">
|
||||
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" width="16" class="octicon octicon-history mr-2 color-fg-muted" fill="currentColor">
|
||||
<path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path>
|
||||
</svg>
|
||||
<span><strong class="age-val color-fg-default font-semibold">calculating...</strong> repo age</span>
|
||||
</div>`;
|
||||
|
||||
const sibling = heading.nextElementSibling;
|
||||
(sibling || heading).insertAdjacentElement('afterend', container);
|
||||
|
||||
const res = await fetchRepoInfo(info);
|
||||
if (!container.isConnected) return;
|
||||
const sRow = container.querySelector('.openscript-size-row');
|
||||
const aRow = container.querySelector('.openscript-age-row');
|
||||
if (res.err) {
|
||||
sRow.querySelector('.size-val').textContent = res.err;
|
||||
sRow.title = res.tip;
|
||||
aRow.remove();
|
||||
} else {
|
||||
sRow.querySelector('.size-val').textContent = res.size.text;
|
||||
sRow.title = res.size.tip;
|
||||
if (res.age) {
|
||||
aRow.querySelector('.age-val').textContent = res.age.text;
|
||||
aRow.title = res.age.tip;
|
||||
} else aRow.remove();
|
||||
}
|
||||
};
|
||||
|
||||
const addFileSize = (row, name, bytes) => {
|
||||
for (const cell of row.querySelectorAll('td.react-directory-row-name-cell-small-screen, td.react-directory-row-name-cell-large-screen')) {
|
||||
const link = cell.querySelector('a.Link--primary[href*="/blob/"]');
|
||||
const col = link?.closest('.react-directory-filename-column');
|
||||
if (!col || link.title !== name || col.querySelector(`:scope > .${SIZE_CLASS}`)) continue;
|
||||
|
||||
const badge = document.createElement('span');
|
||||
badge.className = `${SIZE_CLASS} color-fg-muted`;
|
||||
badge.textContent = formatBytes(bytes);
|
||||
badge.title = `File size: ${bytes.toLocaleString()} bytes`;
|
||||
Object.assign(badge.style, { flex: 'none', marginLeft: '8px', fontSize: '12px', fontWeight: '400', whiteSpace: 'nowrap' });
|
||||
col.append(badge);
|
||||
}
|
||||
};
|
||||
|
||||
const updateFileSizes = async () => {
|
||||
const view = getCodeView();
|
||||
const table = document.querySelector(TABLE_SELECTOR);
|
||||
if (!view || !table) return;
|
||||
|
||||
const links = table.querySelectorAll('td[class*="react-directory-row-name-cell"] a.Link--primary[href*="/blob/"]');
|
||||
if (table.dataset.openscriptFileSizes === view.key && table.querySelectorAll(`.${SIZE_CLASS}`).length === links.length) return;
|
||||
|
||||
if (table.dataset.openscriptFileSizes !== view.key)
|
||||
table.querySelectorAll(`.${SIZE_CLASS}`).forEach(el => el.remove());
|
||||
table.dataset.openscriptFileSizes = view.key;
|
||||
|
||||
const path = view.path ? `/${view.path.split('/').map(encodeURIComponent).join('/')}` : '';
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://api.github.com/repos/${encodeURIComponent(view.owner)}/${encodeURIComponent(view.repo)}/contents${path}?ref=${encodeURIComponent(view.ref)}`,
|
||||
{ headers: getHeaders() },
|
||||
);
|
||||
if (!res.ok) return;
|
||||
const entries = await res.json();
|
||||
if (!Array.isArray(entries) || getCodeView()?.key !== view.key || !table.isConnected) return;
|
||||
|
||||
const sizes = Object.fromEntries(entries.map(i => [i.path, i.size]));
|
||||
const paths = Object.fromEntries(view.items.filter(i => i.contentType === 'file').map(i => [i.name, i.path]));
|
||||
|
||||
for (const row of table.querySelectorAll('tbody tr')) {
|
||||
const link = row.querySelector('td[class*="react-directory-row-name-cell"] a.Link--primary[href*="/blob/"]');
|
||||
const bytes = sizes[paths[link?.title]];
|
||||
if (Number.isFinite(bytes)) addFileSize(row, link.title, bytes);
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
let scheduled;
|
||||
const run = () => {
|
||||
clearTimeout(scheduled);
|
||||
scheduled = setTimeout(() => {
|
||||
updateAboutInfo();
|
||||
updateFileSizes();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
['turbo:load', 'turbo:render', 'pjax:end', 'popstate'].forEach(ev =>
|
||||
window.addEventListener(ev, run)
|
||||
);
|
||||
|
||||
const start = () => {
|
||||
new MutationObserver(run).observe(document.body, { childList: true, subtree: true });
|
||||
run();
|
||||
};
|
||||
|
||||
if (document.body) start();
|
||||
else window.addEventListener('DOMContentLoaded', start, { once: true });
|
||||
@@ -1,214 +0,0 @@
|
||||
// ==UserScript==
|
||||
// @name Display Repo and File Sizes for GitHub
|
||||
// @description Displays repository disk usage and individual file sizes on GitHub.
|
||||
// @match https://github.com/*/*
|
||||
// ==/UserScript==
|
||||
|
||||
const ROW_ID = 'openscript-repo-size-about';
|
||||
const SIZE_CLASS = 'openscript-file-size';
|
||||
const TABLE_SELECTOR = 'table[aria-labelledby="folders-and-files"]';
|
||||
|
||||
const formatBytes = bytes => {
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
let size = bytes;
|
||||
let unit = 0;
|
||||
while (size >= 1024 && unit < units.length - 1) {
|
||||
size /= 1024;
|
||||
unit++;
|
||||
}
|
||||
return `${unit && size < 10 ? size.toFixed(1) : Math.round(size)} ${units[unit]}`;
|
||||
};
|
||||
|
||||
const getRepoInfo = () => {
|
||||
const [, owner, repo] = location.pathname.split('/');
|
||||
const reserved = new Set([
|
||||
'settings', 'orgs', 'organizations', 'notifications', 'search',
|
||||
'features', 'pricing', 'explore', 'marketplace', 'topics', 'trending',
|
||||
]);
|
||||
return owner && repo && !reserved.has(owner) ? { owner, repo } : null;
|
||||
};
|
||||
|
||||
const getPat = () => {
|
||||
const values = typeof OpenScript !== 'undefined' ? OpenScript.env :
|
||||
typeof env !== 'undefined' ? env : {};
|
||||
const found = Object.entries(values).find(([key, value]) =>
|
||||
/^(GH_PAT|GITHUB_PAT|GITHUB_TOKEN|PAT)$/i.test(key) && value
|
||||
);
|
||||
return found ? String(found[1]).trim() : '';
|
||||
};
|
||||
|
||||
const getHeaders = () => {
|
||||
const headers = {
|
||||
Accept: 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
};
|
||||
const pat = getPat();
|
||||
if (pat) headers.Authorization = `Bearer ${pat}`;
|
||||
return headers;
|
||||
};
|
||||
|
||||
const fetchRepoSize = async ({ owner, repo }) => {
|
||||
try {
|
||||
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
||||
headers: getHeaders(),
|
||||
});
|
||||
if (response.status === 404)
|
||||
return { text: 'private (missing GH_PAT)', title: 'Set GH_PAT in OpenScript for private repository access' };
|
||||
if (response.status === 401)
|
||||
return { text: 'invalid GH_PAT', title: 'GitHub rejected the configured token' };
|
||||
if (!response.ok)
|
||||
return { text: `API error (${response.status})`, title: `GitHub API returned ${response.status}` };
|
||||
|
||||
const kb = (await response.json()).size || 0;
|
||||
return kb ? {
|
||||
text: formatBytes(kb * 1024),
|
||||
title: `Total repository disk usage (including full git history): ${kb.toLocaleString()} KB`,
|
||||
} : {
|
||||
text: '0 KB (calculating...)',
|
||||
title: 'GitHub is still calculating disk usage for this repository',
|
||||
};
|
||||
} catch {
|
||||
return { text: 'failed to load', title: 'Network or fetch error' };
|
||||
}
|
||||
};
|
||||
|
||||
const getCodeView = () => {
|
||||
try {
|
||||
const app = document.querySelector('react-app[app-name="code-view"]');
|
||||
const data = JSON.parse(app?.querySelector('script[type="application/json"]')?.textContent || '{}');
|
||||
const payload = data.payload || {};
|
||||
const route = payload.codeViewRepoRoute || payload.codeViewTreeRoute;
|
||||
const info = getRepoInfo();
|
||||
if (!info || !route?.tree?.items || !route.refInfo) return null;
|
||||
|
||||
const path = String(route.path || '').replace(/^\/+|\/+$/g, '');
|
||||
const ref = route.refInfo.currentOid || route.refInfo.name;
|
||||
return {
|
||||
...info, path, ref, items: route.tree.items,
|
||||
key: `${info.owner}/${info.repo}:${ref}:${path}`,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const findAboutHeading = () => [...document.querySelectorAll('h2')].find(heading =>
|
||||
/^\s*About\s*$/i.test(heading.textContent) &&
|
||||
heading.closest('[data-component="SplitPageLayout.Pane"], .Layout-sidebar, .BorderGrid')
|
||||
);
|
||||
|
||||
const updateAboutSize = async () => {
|
||||
const info = getRepoInfo();
|
||||
if (!info) return;
|
||||
|
||||
const key = `${info.owner}/${info.repo}`;
|
||||
const existing = document.getElementById(ROW_ID);
|
||||
if (existing?.dataset.repo === key) return;
|
||||
existing?.remove();
|
||||
document.getElementById('openscript-repo-size')?.remove();
|
||||
|
||||
const heading = findAboutHeading();
|
||||
if (!heading) return;
|
||||
|
||||
const row = document.createElement('div');
|
||||
row.id = ROW_ID;
|
||||
row.dataset.repo = key;
|
||||
row.className = 'mt-2 text-small color-fg-muted d-flex flex-items-center';
|
||||
row.innerHTML = `
|
||||
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" width="16" class="octicon octicon-database mr-2 color-fg-muted" fill="currentColor">
|
||||
<path d="M1 3.5c0-.83.67-1.5 1.5-1.5h11c.83 0 1.5.67 1.5 1.5v9c0 .83-.67 1.5-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9Zm1.5-.5a.5.5 0 0 0-.5.5V5h12V3.5a.5.5 0 0 0-.5-.5h-11ZM14 6H2v2h12V6Zm0 3H2v3.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V9Z"></path>
|
||||
</svg>
|
||||
<span><strong class="size-val color-fg-default font-semibold">calculating...</strong> repo size</span>`;
|
||||
|
||||
const sibling = heading.nextElementSibling;
|
||||
(sibling || heading).insertAdjacentElement('afterend', row);
|
||||
|
||||
const size = await fetchRepoSize(info);
|
||||
if (!row.isConnected) return;
|
||||
row.querySelector('.size-val').textContent = size.text;
|
||||
row.title = size.title;
|
||||
};
|
||||
|
||||
const addFileSize = (row, name, bytes) => {
|
||||
for (const cell of row.querySelectorAll(
|
||||
'td.react-directory-row-name-cell-small-screen, td.react-directory-row-name-cell-large-screen'
|
||||
)) {
|
||||
const link = cell.querySelector('a.Link--primary[href*="/blob/"]');
|
||||
const column = link?.closest('.react-directory-filename-column');
|
||||
if (!column || link.title !== name || column.querySelector(`:scope > .${SIZE_CLASS}`)) continue;
|
||||
|
||||
const text = formatBytes(bytes);
|
||||
const badge = document.createElement('span');
|
||||
badge.className = `${SIZE_CLASS} color-fg-muted`;
|
||||
badge.textContent = text;
|
||||
badge.title = `File size: ${bytes.toLocaleString()} bytes`;
|
||||
Object.assign(badge.style, {
|
||||
flex: 'none', marginLeft: '8px', fontSize: '12px', fontWeight: '400', whiteSpace: 'nowrap',
|
||||
});
|
||||
column.append(badge);
|
||||
}
|
||||
};
|
||||
|
||||
const updateFileSizes = async () => {
|
||||
const view = getCodeView();
|
||||
const table = document.querySelector(TABLE_SELECTOR);
|
||||
if (!view || !table) return;
|
||||
|
||||
const fileLinks = table.querySelectorAll(
|
||||
'td[class*="react-directory-row-name-cell"] a.Link--primary[href*="/blob/"]'
|
||||
);
|
||||
if (table.dataset.openscriptFileSizes === view.key &&
|
||||
table.querySelectorAll(`.${SIZE_CLASS}`).length === fileLinks.length) return;
|
||||
|
||||
if (table.dataset.openscriptFileSizes !== view.key)
|
||||
table.querySelectorAll(`.${SIZE_CLASS}`).forEach(size => size.remove());
|
||||
table.dataset.openscriptFileSizes = view.key;
|
||||
|
||||
const path = view.path ? `/${view.path.split('/').map(encodeURIComponent).join('/')}` : '';
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.github.com/repos/${encodeURIComponent(view.owner)}/${encodeURIComponent(view.repo)}/contents${path}?ref=${encodeURIComponent(view.ref)}`,
|
||||
{ headers: getHeaders() },
|
||||
);
|
||||
if (!response.ok) return;
|
||||
const entries = await response.json();
|
||||
if (!Array.isArray(entries) || getCodeView()?.key !== view.key || !table.isConnected) return;
|
||||
|
||||
const sizes = Object.fromEntries(entries.map(item => [item.path, item.size]));
|
||||
const paths = Object.fromEntries(view.items
|
||||
.filter(item => item.contentType === 'file')
|
||||
.map(item => [item.name, item.path]));
|
||||
|
||||
for (const row of table.querySelectorAll('tbody tr')) {
|
||||
const link = row.querySelector(
|
||||
'td[class*="react-directory-row-name-cell"] a.Link--primary[href*="/blob/"]'
|
||||
);
|
||||
const name = link?.title;
|
||||
const bytes = sizes[paths[name]];
|
||||
if (Number.isFinite(bytes)) addFileSize(row, name, bytes);
|
||||
}
|
||||
} catch {
|
||||
// Leave GitHub's UI untouched when the API is unavailable.
|
||||
}
|
||||
};
|
||||
|
||||
let scheduled;
|
||||
const run = () => {
|
||||
clearTimeout(scheduled);
|
||||
scheduled = setTimeout(() => {
|
||||
updateAboutSize();
|
||||
updateFileSizes();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
['turbo:load', 'turbo:render', 'pjax:end', 'popstate'].forEach(event =>
|
||||
window.addEventListener(event, run)
|
||||
);
|
||||
|
||||
const start = () => {
|
||||
new MutationObserver(run).observe(document.body, { childList: true, subtree: true });
|
||||
run();
|
||||
};
|
||||
|
||||
if (document.body) start();
|
||||
else window.addEventListener('DOMContentLoaded', start, { once: true });
|
||||
@@ -1,9 +1,10 @@
|
||||
# Display Repo and File Sizes for GitHub
|
||||
# Display Repo Info and File Sizes for GitHub
|
||||
|
||||
A lightweight OpenScript that displays total repository disk usage in the **About** section and each file's size beside its name in GitHub's file browser.
|
||||
A lightweight OpenScript that displays total repository disk usage and age in the **About** section and each file's size beside its name in GitHub's file browser.
|
||||
|
||||
## Features
|
||||
- **Accurate Git History Size**: Uses GitHub's native `repo.size` disk usage measurement, accurately reflecting the full Git history, delta compression, and packfiles rather than loose blobs.
|
||||
- **Repository Age & Creation Timestamp**: Shows repository age in the About sidebar and displays the exact creation date and time with timezone on hover.
|
||||
- **New Repo Handling**: If a repository was just pushed and GitHub is still computing initial disk usage (`0 KB`), it provides a friendly indicator (`0 KB (calculating...)`) until GitHub finishes indexing.
|
||||
- **Integrated in About Section**: Injects cleanly under repository details in the right sidebar.
|
||||
- **Individual File Sizes**: Shows human-readable byte sizes beside files in root and nested directory listings.
|
||||
@@ -15,7 +16,7 @@ A lightweight OpenScript that displays total repository disk usage in the **Abou
|
||||
## Installation in OpenScript
|
||||
1. Open the **OpenScript** extension popup.
|
||||
2. Click **+ New** in the header (or click your existing script to edit).
|
||||
3. Paste the contents of [`DisplayRepoSizeGitHub.user.js`](./DisplayRepoSizeGitHub.user.js).
|
||||
3. Paste the contents of [`DisplayRepoInfo.os.js`](./DisplayRepoInfo.os.js).
|
||||
4. Click **save script**.
|
||||
|
||||
## GitHub PAT Configuration (for Private Repos)
|
||||
|
||||
Reference in New Issue
Block a user