feat(hephaestus.x): per-repo detail pages under /silentmode/*
Adds live pages for each repo without leaving hephaestus.x:
hephaestus.x/silentmode/ → index of all silentmode/* repos
hephaestus.x/silentmode/theseus/ → README + recent commits + clone URLs
hephaestus.x/silentmode/sirius/ → same
hephaestus.x/silentmode/ariadne/ → same, gated ("private, sign in to browse")
hephaestus.x/silentmode/hephaestus/ → same, gated
Single _template.html hardcoded per repo via sed; each detail page pulls
metadata + README + last 8 commits from code.silentmode.st via the
CORS-enabled Forgejo API on load, renders README with a minimal
markdown-to-HTML pass, and gracefully falls back to a "sign in to browse"
note when the API returns 401/404 (private repo).
The URL stays on hephaestus.x while browsing — user only leaves when they
click "Browse full source →" or a specific commit link.
This commit is contained in:
parent
e4e5314704
commit
941851ea0e
6 changed files with 1262 additions and 0 deletions
227
hephaestus.x/silentmode/_template.html
Normal file
227
hephaestus.x/silentmode/_template.html
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>silentmode/__REPO__ — Hephaestus</title>
|
||||||
|
<meta name="description" content="silentmode/__REPO__ on Hephaestus — Silent Mode's decentralized code host.">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
|
||||||
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
|
||||||
|
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
|
||||||
|
display:flex;flex-direction:column}
|
||||||
|
a{color:var(--acid);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
|
||||||
|
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
|
||||||
|
.top a{color:var(--mut)}
|
||||||
|
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
|
||||||
|
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
|
||||||
|
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
|
||||||
|
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
|
||||||
|
.meta span{margin-right:14px}
|
||||||
|
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
|
||||||
|
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
|
||||||
|
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
|
||||||
|
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
|
||||||
|
.btn:hover{background:var(--panel);text-decoration:none}
|
||||||
|
.btn.acid:hover{background:var(--acid)}
|
||||||
|
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
|
||||||
|
section:last-of-type{border-bottom:none}
|
||||||
|
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
|
||||||
|
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
|
||||||
|
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
|
||||||
|
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
|
||||||
|
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
|
||||||
|
overflow-x:auto;font-size:14px;line-height:1.7}
|
||||||
|
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
|
||||||
|
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
|
||||||
|
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
|
||||||
|
#readme h2{font-size:1.2rem}
|
||||||
|
#readme h3{font-size:1.05rem;color:var(--acid)}
|
||||||
|
#readme p{margin:.6rem 0;color:var(--ink)}
|
||||||
|
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
|
||||||
|
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
|
||||||
|
#readme pre code{background:transparent;border:none;padding:0}
|
||||||
|
#readme a{color:var(--acid)}
|
||||||
|
#readme ul,#readme ol{padding-left:1.4rem}
|
||||||
|
#readme li{margin:.25rem 0}
|
||||||
|
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
|
||||||
|
.commits{display:grid;gap:8px}
|
||||||
|
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
|
||||||
|
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
|
||||||
|
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||||
|
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
|
||||||
|
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
|
||||||
|
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
|
||||||
|
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
|
||||||
|
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
|
||||||
|
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
|
||||||
|
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
|
||||||
|
<div class="top">
|
||||||
|
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>__REPO__</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header class="repo">
|
||||||
|
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">__REPO__</span></h1>
|
||||||
|
<p class="meta" id="meta"><span>loading …</span></p>
|
||||||
|
<p class="desc" id="desc"></p>
|
||||||
|
<div class="cta">
|
||||||
|
<a class="btn acid" href="https://code.silentmode.st/silentmode/__REPO__">Browse full source →</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/__REPO__/issues">Issues</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/__REPO__/releases">Releases</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>README</h2>
|
||||||
|
<div id="readme" class="empty">Loading README from the forge…</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Recent commits</h2>
|
||||||
|
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Clone</h2>
|
||||||
|
<div class="clone-label">HTTPS (with a personal access token)</div>
|
||||||
|
<pre class="clone">git clone https://<you>:<token>@code.silentmode.st/silentmode/__REPO__.git</pre>
|
||||||
|
<div class="clone-label">SSH (with a key registered under your account)</div>
|
||||||
|
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/__REPO__.git</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const REPO = "__REPO__";
|
||||||
|
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||||
|
}
|
||||||
|
function fmtDate(iso) {
|
||||||
|
if (!iso) return "";
|
||||||
|
const d = new Date(iso);
|
||||||
|
if (isNaN(d)) return "";
|
||||||
|
const days = (Date.now() - d.getTime()) / 86400000;
|
||||||
|
if (days < 1) return "today";
|
||||||
|
if (days < 30) return `${Math.round(days)}d ago`;
|
||||||
|
return d.toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal markdown → HTML renderer for the README preview.
|
||||||
|
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
|
||||||
|
// fenced code blocks, and blockquotes. For anything richer, users click through
|
||||||
|
// to the full forge which has Forgejo's proper markdown renderer.
|
||||||
|
function mdToHtml(md) {
|
||||||
|
// Escape first, then apply markdown patterns
|
||||||
|
let s = esc(md);
|
||||||
|
// Fenced code blocks
|
||||||
|
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
|
||||||
|
// Headers
|
||||||
|
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
|
||||||
|
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
||||||
|
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
|
||||||
|
// Bold + italic
|
||||||
|
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
||||||
|
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
||||||
|
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
|
||||||
|
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
|
||||||
|
// Links [text](url)
|
||||||
|
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
||||||
|
// Unordered lists
|
||||||
|
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
|
||||||
|
// Blockquotes
|
||||||
|
s = s.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
|
||||||
|
// Paragraphs: any line-block not already wrapped in a block tag
|
||||||
|
s = s.split(/\n{2,}/).map(chunk => {
|
||||||
|
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
|
||||||
|
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
|
||||||
|
}).join("\n");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRepo() {
|
||||||
|
const meta = document.getElementById("meta");
|
||||||
|
const desc = document.getElementById("desc");
|
||||||
|
const readme = document.getElementById("readme");
|
||||||
|
const commits = document.getElementById("commits");
|
||||||
|
try {
|
||||||
|
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
|
||||||
|
const parts = [];
|
||||||
|
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
|
||||||
|
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
|
||||||
|
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
|
||||||
|
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
|
||||||
|
meta.innerHTML = parts.join("");
|
||||||
|
desc.textContent = info.description || "";
|
||||||
|
|
||||||
|
// README
|
||||||
|
try {
|
||||||
|
const branch = info.default_branch || "master";
|
||||||
|
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
|
||||||
|
if (rmRes.ok) {
|
||||||
|
const md = await rmRes.text();
|
||||||
|
readme.classList.remove("empty");
|
||||||
|
readme.innerHTML = mdToHtml(md);
|
||||||
|
} else {
|
||||||
|
readme.textContent = "(no README.md in this repo)";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
readme.textContent = `(couldn't load README: ${e.message})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent commits
|
||||||
|
try {
|
||||||
|
const cRes = await fetch(`${API}/commits?limit=8`);
|
||||||
|
if (cRes.ok) {
|
||||||
|
const list = await cRes.json();
|
||||||
|
commits.innerHTML = list.slice(0, 8).map(c => {
|
||||||
|
const msg = (c.commit?.message || "").split("\n")[0];
|
||||||
|
const author = c.commit?.author?.name || c.author?.login || "unknown";
|
||||||
|
const when = fmtDate(c.commit?.author?.date);
|
||||||
|
const sha = (c.sha || "").slice(0, 7);
|
||||||
|
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
|
||||||
|
<div class="msg">${esc(msg)}</div>
|
||||||
|
<div class="sha">${esc(sha)}</div>
|
||||||
|
<div class="author">by ${esc(author)}</div>
|
||||||
|
<div class="when">${esc(when)}</div>
|
||||||
|
</a>`;
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
|
||||||
|
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
|
||||||
|
readme.innerHTML = `
|
||||||
|
<div class="private-note">
|
||||||
|
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
|
||||||
|
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
|
||||||
|
you'll be able to read it there.
|
||||||
|
</div>`;
|
||||||
|
commits.innerHTML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRepo();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
227
hephaestus.x/silentmode/ariadne/index.html
Normal file
227
hephaestus.x/silentmode/ariadne/index.html
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>silentmode/ariadne — Hephaestus</title>
|
||||||
|
<meta name="description" content="silentmode/ariadne on Hephaestus — Silent Mode's decentralized code host.">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
|
||||||
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
|
||||||
|
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
|
||||||
|
display:flex;flex-direction:column}
|
||||||
|
a{color:var(--acid);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
|
||||||
|
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
|
||||||
|
.top a{color:var(--mut)}
|
||||||
|
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
|
||||||
|
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
|
||||||
|
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
|
||||||
|
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
|
||||||
|
.meta span{margin-right:14px}
|
||||||
|
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
|
||||||
|
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
|
||||||
|
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
|
||||||
|
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
|
||||||
|
.btn:hover{background:var(--panel);text-decoration:none}
|
||||||
|
.btn.acid:hover{background:var(--acid)}
|
||||||
|
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
|
||||||
|
section:last-of-type{border-bottom:none}
|
||||||
|
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
|
||||||
|
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
|
||||||
|
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
|
||||||
|
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
|
||||||
|
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
|
||||||
|
overflow-x:auto;font-size:14px;line-height:1.7}
|
||||||
|
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
|
||||||
|
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
|
||||||
|
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
|
||||||
|
#readme h2{font-size:1.2rem}
|
||||||
|
#readme h3{font-size:1.05rem;color:var(--acid)}
|
||||||
|
#readme p{margin:.6rem 0;color:var(--ink)}
|
||||||
|
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
|
||||||
|
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
|
||||||
|
#readme pre code{background:transparent;border:none;padding:0}
|
||||||
|
#readme a{color:var(--acid)}
|
||||||
|
#readme ul,#readme ol{padding-left:1.4rem}
|
||||||
|
#readme li{margin:.25rem 0}
|
||||||
|
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
|
||||||
|
.commits{display:grid;gap:8px}
|
||||||
|
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
|
||||||
|
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
|
||||||
|
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||||
|
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
|
||||||
|
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
|
||||||
|
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
|
||||||
|
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
|
||||||
|
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
|
||||||
|
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
|
||||||
|
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
|
||||||
|
<div class="top">
|
||||||
|
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>ariadne</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header class="repo">
|
||||||
|
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">ariadne</span></h1>
|
||||||
|
<p class="meta" id="meta"><span>loading …</span></p>
|
||||||
|
<p class="desc" id="desc"></p>
|
||||||
|
<div class="cta">
|
||||||
|
<a class="btn acid" href="https://code.silentmode.st/silentmode/ariadne">Browse full source →</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/ariadne/issues">Issues</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/ariadne/releases">Releases</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>README</h2>
|
||||||
|
<div id="readme" class="empty">Loading README from the forge…</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Recent commits</h2>
|
||||||
|
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Clone</h2>
|
||||||
|
<div class="clone-label">HTTPS (with a personal access token)</div>
|
||||||
|
<pre class="clone">git clone https://<you>:<token>@code.silentmode.st/silentmode/ariadne.git</pre>
|
||||||
|
<div class="clone-label">SSH (with a key registered under your account)</div>
|
||||||
|
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/ariadne.git</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const REPO = "ariadne";
|
||||||
|
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||||
|
}
|
||||||
|
function fmtDate(iso) {
|
||||||
|
if (!iso) return "";
|
||||||
|
const d = new Date(iso);
|
||||||
|
if (isNaN(d)) return "";
|
||||||
|
const days = (Date.now() - d.getTime()) / 86400000;
|
||||||
|
if (days < 1) return "today";
|
||||||
|
if (days < 30) return `${Math.round(days)}d ago`;
|
||||||
|
return d.toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal markdown → HTML renderer for the README preview.
|
||||||
|
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
|
||||||
|
// fenced code blocks, and blockquotes. For anything richer, users click through
|
||||||
|
// to the full forge which has Forgejo's proper markdown renderer.
|
||||||
|
function mdToHtml(md) {
|
||||||
|
// Escape first, then apply markdown patterns
|
||||||
|
let s = esc(md);
|
||||||
|
// Fenced code blocks
|
||||||
|
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
|
||||||
|
// Headers
|
||||||
|
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
|
||||||
|
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
||||||
|
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
|
||||||
|
// Bold + italic
|
||||||
|
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
||||||
|
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
||||||
|
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
|
||||||
|
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
|
||||||
|
// Links [text](url)
|
||||||
|
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
||||||
|
// Unordered lists
|
||||||
|
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
|
||||||
|
// Blockquotes
|
||||||
|
s = s.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
|
||||||
|
// Paragraphs: any line-block not already wrapped in a block tag
|
||||||
|
s = s.split(/\n{2,}/).map(chunk => {
|
||||||
|
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
|
||||||
|
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
|
||||||
|
}).join("\n");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRepo() {
|
||||||
|
const meta = document.getElementById("meta");
|
||||||
|
const desc = document.getElementById("desc");
|
||||||
|
const readme = document.getElementById("readme");
|
||||||
|
const commits = document.getElementById("commits");
|
||||||
|
try {
|
||||||
|
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
|
||||||
|
const parts = [];
|
||||||
|
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
|
||||||
|
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
|
||||||
|
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
|
||||||
|
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
|
||||||
|
meta.innerHTML = parts.join("");
|
||||||
|
desc.textContent = info.description || "";
|
||||||
|
|
||||||
|
// README
|
||||||
|
try {
|
||||||
|
const branch = info.default_branch || "master";
|
||||||
|
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
|
||||||
|
if (rmRes.ok) {
|
||||||
|
const md = await rmRes.text();
|
||||||
|
readme.classList.remove("empty");
|
||||||
|
readme.innerHTML = mdToHtml(md);
|
||||||
|
} else {
|
||||||
|
readme.textContent = "(no README.md in this repo)";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
readme.textContent = `(couldn't load README: ${e.message})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent commits
|
||||||
|
try {
|
||||||
|
const cRes = await fetch(`${API}/commits?limit=8`);
|
||||||
|
if (cRes.ok) {
|
||||||
|
const list = await cRes.json();
|
||||||
|
commits.innerHTML = list.slice(0, 8).map(c => {
|
||||||
|
const msg = (c.commit?.message || "").split("\n")[0];
|
||||||
|
const author = c.commit?.author?.name || c.author?.login || "unknown";
|
||||||
|
const when = fmtDate(c.commit?.author?.date);
|
||||||
|
const sha = (c.sha || "").slice(0, 7);
|
||||||
|
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
|
||||||
|
<div class="msg">${esc(msg)}</div>
|
||||||
|
<div class="sha">${esc(sha)}</div>
|
||||||
|
<div class="author">by ${esc(author)}</div>
|
||||||
|
<div class="when">${esc(when)}</div>
|
||||||
|
</a>`;
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
|
||||||
|
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
|
||||||
|
readme.innerHTML = `
|
||||||
|
<div class="private-note">
|
||||||
|
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
|
||||||
|
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
|
||||||
|
you'll be able to read it there.
|
||||||
|
</div>`;
|
||||||
|
commits.innerHTML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRepo();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
227
hephaestus.x/silentmode/hephaestus/index.html
Normal file
227
hephaestus.x/silentmode/hephaestus/index.html
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>silentmode/hephaestus — Hephaestus</title>
|
||||||
|
<meta name="description" content="silentmode/hephaestus on Hephaestus — Silent Mode's decentralized code host.">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
|
||||||
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
|
||||||
|
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
|
||||||
|
display:flex;flex-direction:column}
|
||||||
|
a{color:var(--acid);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
|
||||||
|
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
|
||||||
|
.top a{color:var(--mut)}
|
||||||
|
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
|
||||||
|
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
|
||||||
|
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
|
||||||
|
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
|
||||||
|
.meta span{margin-right:14px}
|
||||||
|
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
|
||||||
|
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
|
||||||
|
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
|
||||||
|
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
|
||||||
|
.btn:hover{background:var(--panel);text-decoration:none}
|
||||||
|
.btn.acid:hover{background:var(--acid)}
|
||||||
|
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
|
||||||
|
section:last-of-type{border-bottom:none}
|
||||||
|
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
|
||||||
|
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
|
||||||
|
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
|
||||||
|
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
|
||||||
|
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
|
||||||
|
overflow-x:auto;font-size:14px;line-height:1.7}
|
||||||
|
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
|
||||||
|
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
|
||||||
|
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
|
||||||
|
#readme h2{font-size:1.2rem}
|
||||||
|
#readme h3{font-size:1.05rem;color:var(--acid)}
|
||||||
|
#readme p{margin:.6rem 0;color:var(--ink)}
|
||||||
|
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
|
||||||
|
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
|
||||||
|
#readme pre code{background:transparent;border:none;padding:0}
|
||||||
|
#readme a{color:var(--acid)}
|
||||||
|
#readme ul,#readme ol{padding-left:1.4rem}
|
||||||
|
#readme li{margin:.25rem 0}
|
||||||
|
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
|
||||||
|
.commits{display:grid;gap:8px}
|
||||||
|
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
|
||||||
|
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
|
||||||
|
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||||
|
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
|
||||||
|
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
|
||||||
|
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
|
||||||
|
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
|
||||||
|
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
|
||||||
|
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
|
||||||
|
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
|
||||||
|
<div class="top">
|
||||||
|
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>hephaestus</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header class="repo">
|
||||||
|
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">hephaestus</span></h1>
|
||||||
|
<p class="meta" id="meta"><span>loading …</span></p>
|
||||||
|
<p class="desc" id="desc"></p>
|
||||||
|
<div class="cta">
|
||||||
|
<a class="btn acid" href="https://code.silentmode.st/silentmode/hephaestus">Browse full source →</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/hephaestus/issues">Issues</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/hephaestus/releases">Releases</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>README</h2>
|
||||||
|
<div id="readme" class="empty">Loading README from the forge…</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Recent commits</h2>
|
||||||
|
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Clone</h2>
|
||||||
|
<div class="clone-label">HTTPS (with a personal access token)</div>
|
||||||
|
<pre class="clone">git clone https://<you>:<token>@code.silentmode.st/silentmode/hephaestus.git</pre>
|
||||||
|
<div class="clone-label">SSH (with a key registered under your account)</div>
|
||||||
|
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/hephaestus.git</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const REPO = "hephaestus";
|
||||||
|
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||||
|
}
|
||||||
|
function fmtDate(iso) {
|
||||||
|
if (!iso) return "";
|
||||||
|
const d = new Date(iso);
|
||||||
|
if (isNaN(d)) return "";
|
||||||
|
const days = (Date.now() - d.getTime()) / 86400000;
|
||||||
|
if (days < 1) return "today";
|
||||||
|
if (days < 30) return `${Math.round(days)}d ago`;
|
||||||
|
return d.toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal markdown → HTML renderer for the README preview.
|
||||||
|
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
|
||||||
|
// fenced code blocks, and blockquotes. For anything richer, users click through
|
||||||
|
// to the full forge which has Forgejo's proper markdown renderer.
|
||||||
|
function mdToHtml(md) {
|
||||||
|
// Escape first, then apply markdown patterns
|
||||||
|
let s = esc(md);
|
||||||
|
// Fenced code blocks
|
||||||
|
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
|
||||||
|
// Headers
|
||||||
|
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
|
||||||
|
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
||||||
|
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
|
||||||
|
// Bold + italic
|
||||||
|
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
||||||
|
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
||||||
|
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
|
||||||
|
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
|
||||||
|
// Links [text](url)
|
||||||
|
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
||||||
|
// Unordered lists
|
||||||
|
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
|
||||||
|
// Blockquotes
|
||||||
|
s = s.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
|
||||||
|
// Paragraphs: any line-block not already wrapped in a block tag
|
||||||
|
s = s.split(/\n{2,}/).map(chunk => {
|
||||||
|
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
|
||||||
|
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
|
||||||
|
}).join("\n");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRepo() {
|
||||||
|
const meta = document.getElementById("meta");
|
||||||
|
const desc = document.getElementById("desc");
|
||||||
|
const readme = document.getElementById("readme");
|
||||||
|
const commits = document.getElementById("commits");
|
||||||
|
try {
|
||||||
|
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
|
||||||
|
const parts = [];
|
||||||
|
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
|
||||||
|
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
|
||||||
|
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
|
||||||
|
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
|
||||||
|
meta.innerHTML = parts.join("");
|
||||||
|
desc.textContent = info.description || "";
|
||||||
|
|
||||||
|
// README
|
||||||
|
try {
|
||||||
|
const branch = info.default_branch || "master";
|
||||||
|
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
|
||||||
|
if (rmRes.ok) {
|
||||||
|
const md = await rmRes.text();
|
||||||
|
readme.classList.remove("empty");
|
||||||
|
readme.innerHTML = mdToHtml(md);
|
||||||
|
} else {
|
||||||
|
readme.textContent = "(no README.md in this repo)";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
readme.textContent = `(couldn't load README: ${e.message})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent commits
|
||||||
|
try {
|
||||||
|
const cRes = await fetch(`${API}/commits?limit=8`);
|
||||||
|
if (cRes.ok) {
|
||||||
|
const list = await cRes.json();
|
||||||
|
commits.innerHTML = list.slice(0, 8).map(c => {
|
||||||
|
const msg = (c.commit?.message || "").split("\n")[0];
|
||||||
|
const author = c.commit?.author?.name || c.author?.login || "unknown";
|
||||||
|
const when = fmtDate(c.commit?.author?.date);
|
||||||
|
const sha = (c.sha || "").slice(0, 7);
|
||||||
|
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
|
||||||
|
<div class="msg">${esc(msg)}</div>
|
||||||
|
<div class="sha">${esc(sha)}</div>
|
||||||
|
<div class="author">by ${esc(author)}</div>
|
||||||
|
<div class="when">${esc(when)}</div>
|
||||||
|
</a>`;
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
|
||||||
|
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
|
||||||
|
readme.innerHTML = `
|
||||||
|
<div class="private-note">
|
||||||
|
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
|
||||||
|
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
|
||||||
|
you'll be able to read it there.
|
||||||
|
</div>`;
|
||||||
|
commits.innerHTML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRepo();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
127
hephaestus.x/silentmode/index.html
Normal file
127
hephaestus.x/silentmode/index.html
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>silentmode/* — Hephaestus</title>
|
||||||
|
<meta name="description" content="All silentmode/* repos on Hephaestus.">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
|
||||||
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
|
||||||
|
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
|
||||||
|
display:flex;flex-direction:column}
|
||||||
|
a{color:var(--acid);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
.wrap{max-width:820px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
|
||||||
|
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
|
||||||
|
.top a{color:var(--mut)}
|
||||||
|
header.hero{text-align:center;padding:3rem 1.2rem 1rem}
|
||||||
|
.mark{font-size:52px;line-height:1}
|
||||||
|
h1{font-size:clamp(1.8rem,4.5vw,2.4rem);margin:.4rem 0 .3rem;letter-spacing:-0.01em;font-family:ui-monospace,monospace;font-weight:500}
|
||||||
|
h1 .g{color:var(--acid)}
|
||||||
|
.tag{color:var(--mut);font-size:1rem;max-width:560px;margin:0 auto}
|
||||||
|
section{padding:2rem 0 1rem}
|
||||||
|
.repo{display:grid;grid-template-columns:1fr auto;gap:16px;align-items:baseline;
|
||||||
|
background:var(--panel);border:1px solid var(--line);border-radius:12px;
|
||||||
|
padding:16px 20px;margin-bottom:10px;transition:border-color .1s,background .1s;text-decoration:none}
|
||||||
|
.repo:hover{border-color:var(--acid);background:var(--panel2);text-decoration:none}
|
||||||
|
.repo .name{font-family:ui-monospace,monospace;font-size:14.5px;color:var(--ink)}
|
||||||
|
.repo .name .owner{color:var(--dim)}
|
||||||
|
.repo .name .sep{color:var(--dim);margin:0 2px}
|
||||||
|
.repo .name .repo-label{color:var(--acid)}
|
||||||
|
.repo .desc{color:var(--mut);font-size:13.5px;line-height:1.5;margin:.3rem 0 0;grid-column:1/-1}
|
||||||
|
.repo .meta{color:var(--dim);font-size:12px;font-family:ui-monospace,monospace;text-align:right;white-space:nowrap}
|
||||||
|
.repo.priv .name .repo-label{color:var(--mut)}
|
||||||
|
.empty{color:var(--dim);font-size:14px;text-align:center;padding:2rem}
|
||||||
|
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
|
||||||
|
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
|
||||||
|
<div class="top">
|
||||||
|
<a href="../">hephaestus.x</a> / <strong>silentmode</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header class="hero">
|
||||||
|
<div class="mark">🛠</div>
|
||||||
|
<h1><span class="g">silentmode</span>/*</h1>
|
||||||
|
<p class="tag">Public repositories under the <code>silentmode</code> org. Click through for a per-repo overview or straight into the full forge.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div id="repos"><div class="empty">Loading from the forge…</div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="../">hephaestus.x</a> · Silent Mode's decentralized code host
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
// Static index: enumerate the known silentmode/* repos. Public ones link to the
|
||||||
|
// per-repo detail page we generate at hephaestus.x/silentmode/<name>/ ; private
|
||||||
|
// ones link straight to code.silentmode.st since the detail page shows a
|
||||||
|
// "sign in to browse" message anyway.
|
||||||
|
const KNOWN = [
|
||||||
|
{name: "theseus", private: false},
|
||||||
|
{name: "sirius", private: false},
|
||||||
|
{name: "ariadne", private: true},
|
||||||
|
{name: "hephaestus", private: true},
|
||||||
|
];
|
||||||
|
const container = document.getElementById("repos");
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||||
|
}
|
||||||
|
function fmtDate(iso) {
|
||||||
|
if (!iso) return "";
|
||||||
|
const d = new Date(iso);
|
||||||
|
if (isNaN(d)) return "";
|
||||||
|
const days = (Date.now() - d.getTime()) / 86400000;
|
||||||
|
if (days < 1) return "today";
|
||||||
|
if (days < 30) return `${Math.round(days)}d ago`;
|
||||||
|
return d.toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRepo(entry) {
|
||||||
|
const priv = entry.private;
|
||||||
|
// Public repos: fetch fresh info via CORS. Private: fill in a static line.
|
||||||
|
if (!priv) {
|
||||||
|
try {
|
||||||
|
const info = await fetch(`https://code.silentmode.st/api/v1/repos/silentmode/${entry.name}`).then(r => r.json());
|
||||||
|
return {...entry, description: info.description, updated_at: info.updated_at, size: info.size};
|
||||||
|
} catch { return entry; }
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(KNOWN.map(loadRepo)).then(list => {
|
||||||
|
container.innerHTML = list.map(r => {
|
||||||
|
// Public → per-repo detail page on hephaestus.x; private → direct to code.silentmode.st
|
||||||
|
const href = r.private
|
||||||
|
? `https://code.silentmode.st/silentmode/${r.name}`
|
||||||
|
: `./${r.name}/`;
|
||||||
|
const badge = r.private ? '<span style="color:var(--dim)">private</span>' : "";
|
||||||
|
const updated = r.updated_at ? `updated ${fmtDate(r.updated_at)}` : "";
|
||||||
|
const desc = r.description ? `<div class="desc">${esc(r.description)}</div>` : "";
|
||||||
|
return `
|
||||||
|
<a class="repo${r.private ? " priv" : ""}" href="${href}">
|
||||||
|
<div class="name">
|
||||||
|
<span class="owner">silentmode</span><span class="sep">/</span><span class="repo-label">${esc(r.name)}</span>
|
||||||
|
${desc}
|
||||||
|
</div>
|
||||||
|
<div class="meta">${badge}${badge && updated ? " · " : ""}${updated}</div>
|
||||||
|
</a>`;
|
||||||
|
}).join("");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
227
hephaestus.x/silentmode/sirius/index.html
Normal file
227
hephaestus.x/silentmode/sirius/index.html
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>silentmode/sirius — Hephaestus</title>
|
||||||
|
<meta name="description" content="silentmode/sirius on Hephaestus — Silent Mode's decentralized code host.">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
|
||||||
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
|
||||||
|
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
|
||||||
|
display:flex;flex-direction:column}
|
||||||
|
a{color:var(--acid);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
|
||||||
|
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
|
||||||
|
.top a{color:var(--mut)}
|
||||||
|
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
|
||||||
|
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
|
||||||
|
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
|
||||||
|
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
|
||||||
|
.meta span{margin-right:14px}
|
||||||
|
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
|
||||||
|
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
|
||||||
|
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
|
||||||
|
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
|
||||||
|
.btn:hover{background:var(--panel);text-decoration:none}
|
||||||
|
.btn.acid:hover{background:var(--acid)}
|
||||||
|
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
|
||||||
|
section:last-of-type{border-bottom:none}
|
||||||
|
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
|
||||||
|
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
|
||||||
|
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
|
||||||
|
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
|
||||||
|
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
|
||||||
|
overflow-x:auto;font-size:14px;line-height:1.7}
|
||||||
|
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
|
||||||
|
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
|
||||||
|
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
|
||||||
|
#readme h2{font-size:1.2rem}
|
||||||
|
#readme h3{font-size:1.05rem;color:var(--acid)}
|
||||||
|
#readme p{margin:.6rem 0;color:var(--ink)}
|
||||||
|
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
|
||||||
|
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
|
||||||
|
#readme pre code{background:transparent;border:none;padding:0}
|
||||||
|
#readme a{color:var(--acid)}
|
||||||
|
#readme ul,#readme ol{padding-left:1.4rem}
|
||||||
|
#readme li{margin:.25rem 0}
|
||||||
|
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
|
||||||
|
.commits{display:grid;gap:8px}
|
||||||
|
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
|
||||||
|
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
|
||||||
|
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||||
|
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
|
||||||
|
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
|
||||||
|
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
|
||||||
|
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
|
||||||
|
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
|
||||||
|
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
|
||||||
|
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
|
||||||
|
<div class="top">
|
||||||
|
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>sirius</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header class="repo">
|
||||||
|
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">sirius</span></h1>
|
||||||
|
<p class="meta" id="meta"><span>loading …</span></p>
|
||||||
|
<p class="desc" id="desc"></p>
|
||||||
|
<div class="cta">
|
||||||
|
<a class="btn acid" href="https://code.silentmode.st/silentmode/sirius">Browse full source →</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/sirius/issues">Issues</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/sirius/releases">Releases</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>README</h2>
|
||||||
|
<div id="readme" class="empty">Loading README from the forge…</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Recent commits</h2>
|
||||||
|
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Clone</h2>
|
||||||
|
<div class="clone-label">HTTPS (with a personal access token)</div>
|
||||||
|
<pre class="clone">git clone https://<you>:<token>@code.silentmode.st/silentmode/sirius.git</pre>
|
||||||
|
<div class="clone-label">SSH (with a key registered under your account)</div>
|
||||||
|
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/sirius.git</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const REPO = "sirius";
|
||||||
|
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||||
|
}
|
||||||
|
function fmtDate(iso) {
|
||||||
|
if (!iso) return "";
|
||||||
|
const d = new Date(iso);
|
||||||
|
if (isNaN(d)) return "";
|
||||||
|
const days = (Date.now() - d.getTime()) / 86400000;
|
||||||
|
if (days < 1) return "today";
|
||||||
|
if (days < 30) return `${Math.round(days)}d ago`;
|
||||||
|
return d.toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal markdown → HTML renderer for the README preview.
|
||||||
|
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
|
||||||
|
// fenced code blocks, and blockquotes. For anything richer, users click through
|
||||||
|
// to the full forge which has Forgejo's proper markdown renderer.
|
||||||
|
function mdToHtml(md) {
|
||||||
|
// Escape first, then apply markdown patterns
|
||||||
|
let s = esc(md);
|
||||||
|
// Fenced code blocks
|
||||||
|
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
|
||||||
|
// Headers
|
||||||
|
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
|
||||||
|
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
||||||
|
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
|
||||||
|
// Bold + italic
|
||||||
|
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
||||||
|
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
||||||
|
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
|
||||||
|
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
|
||||||
|
// Links [text](url)
|
||||||
|
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
||||||
|
// Unordered lists
|
||||||
|
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
|
||||||
|
// Blockquotes
|
||||||
|
s = s.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
|
||||||
|
// Paragraphs: any line-block not already wrapped in a block tag
|
||||||
|
s = s.split(/\n{2,}/).map(chunk => {
|
||||||
|
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
|
||||||
|
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
|
||||||
|
}).join("\n");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRepo() {
|
||||||
|
const meta = document.getElementById("meta");
|
||||||
|
const desc = document.getElementById("desc");
|
||||||
|
const readme = document.getElementById("readme");
|
||||||
|
const commits = document.getElementById("commits");
|
||||||
|
try {
|
||||||
|
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
|
||||||
|
const parts = [];
|
||||||
|
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
|
||||||
|
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
|
||||||
|
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
|
||||||
|
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
|
||||||
|
meta.innerHTML = parts.join("");
|
||||||
|
desc.textContent = info.description || "";
|
||||||
|
|
||||||
|
// README
|
||||||
|
try {
|
||||||
|
const branch = info.default_branch || "master";
|
||||||
|
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
|
||||||
|
if (rmRes.ok) {
|
||||||
|
const md = await rmRes.text();
|
||||||
|
readme.classList.remove("empty");
|
||||||
|
readme.innerHTML = mdToHtml(md);
|
||||||
|
} else {
|
||||||
|
readme.textContent = "(no README.md in this repo)";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
readme.textContent = `(couldn't load README: ${e.message})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent commits
|
||||||
|
try {
|
||||||
|
const cRes = await fetch(`${API}/commits?limit=8`);
|
||||||
|
if (cRes.ok) {
|
||||||
|
const list = await cRes.json();
|
||||||
|
commits.innerHTML = list.slice(0, 8).map(c => {
|
||||||
|
const msg = (c.commit?.message || "").split("\n")[0];
|
||||||
|
const author = c.commit?.author?.name || c.author?.login || "unknown";
|
||||||
|
const when = fmtDate(c.commit?.author?.date);
|
||||||
|
const sha = (c.sha || "").slice(0, 7);
|
||||||
|
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
|
||||||
|
<div class="msg">${esc(msg)}</div>
|
||||||
|
<div class="sha">${esc(sha)}</div>
|
||||||
|
<div class="author">by ${esc(author)}</div>
|
||||||
|
<div class="when">${esc(when)}</div>
|
||||||
|
</a>`;
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
|
||||||
|
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
|
||||||
|
readme.innerHTML = `
|
||||||
|
<div class="private-note">
|
||||||
|
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
|
||||||
|
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
|
||||||
|
you'll be able to read it there.
|
||||||
|
</div>`;
|
||||||
|
commits.innerHTML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRepo();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
227
hephaestus.x/silentmode/theseus/index.html
Normal file
227
hephaestus.x/silentmode/theseus/index.html
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>silentmode/theseus — Hephaestus</title>
|
||||||
|
<meta name="description" content="silentmode/theseus on Hephaestus — Silent Mode's decentralized code host.">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
|
||||||
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
|
||||||
|
color:var(--ink);font:15px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;
|
||||||
|
display:flex;flex-direction:column}
|
||||||
|
a{color:var(--acid);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem;width:100%;flex:1}
|
||||||
|
.top{padding:1.6rem 0;color:var(--dim);font-size:13px}
|
||||||
|
.top a{color:var(--mut)}
|
||||||
|
header.repo{padding:1.6rem 0 1.2rem;border-bottom:1px solid var(--line)}
|
||||||
|
h1{margin:0;font-size:1.6rem;font-family:ui-monospace,monospace;font-weight:500}
|
||||||
|
h1 .owner{color:var(--mut)} h1 .sep{color:var(--dim)} h1 .name{color:var(--acid)}
|
||||||
|
.meta{margin-top:.6rem;color:var(--mut);font-size:13.5px;line-height:1.6}
|
||||||
|
.meta span{margin-right:14px}
|
||||||
|
.desc{margin-top:.8rem;color:var(--ink);font-size:15px}
|
||||||
|
.cta{margin-top:1.2rem;display:flex;gap:8px;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-block;padding:9px 18px;border-radius:9px;font-size:13.5px;font-weight:500;
|
||||||
|
border:1px solid var(--line);color:var(--ink);background:transparent;transition:filter .1s,background .1s;text-decoration:none}
|
||||||
|
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600;border-color:var(--acid)}
|
||||||
|
.btn:hover{background:var(--panel);text-decoration:none}
|
||||||
|
.btn.acid:hover{background:var(--acid)}
|
||||||
|
section{padding:1.8rem 0;border-bottom:1px solid var(--line)}
|
||||||
|
section:last-of-type{border-bottom:none}
|
||||||
|
h2{margin:0 0 .8rem;font-size:1rem;color:var(--mut);font-weight:500;text-transform:uppercase;letter-spacing:.05em}
|
||||||
|
pre.clone{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 14px;overflow-x:auto;
|
||||||
|
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:0 0 8px}
|
||||||
|
.clone-label{color:var(--dim);font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px}
|
||||||
|
#readme{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 24px;
|
||||||
|
overflow-x:auto;font-size:14px;line-height:1.7}
|
||||||
|
#readme.empty{color:var(--dim);font-style:italic;text-align:center;padding:2rem}
|
||||||
|
#readme h1,#readme h2,#readme h3{color:var(--ink);font-weight:600;margin-top:1.4rem;margin-bottom:.5rem;font-family:inherit;text-transform:none;letter-spacing:0}
|
||||||
|
#readme h1{font-size:1.5rem;border-bottom:1px solid var(--line);padding-bottom:.4rem}
|
||||||
|
#readme h2{font-size:1.2rem}
|
||||||
|
#readme h3{font-size:1.05rem;color:var(--acid)}
|
||||||
|
#readme p{margin:.6rem 0;color:var(--ink)}
|
||||||
|
#readme code{background:#0e131b;border:1px solid var(--line);border-radius:4px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px}
|
||||||
|
#readme pre{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow-x:auto}
|
||||||
|
#readme pre code{background:transparent;border:none;padding:0}
|
||||||
|
#readme a{color:var(--acid)}
|
||||||
|
#readme ul,#readme ol{padding-left:1.4rem}
|
||||||
|
#readme li{margin:.25rem 0}
|
||||||
|
#readme blockquote{border-left:3px solid var(--acid);padding-left:12px;margin:.8rem 0;color:var(--mut)}
|
||||||
|
.commits{display:grid;gap:8px}
|
||||||
|
.commit{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 16px;
|
||||||
|
font-size:13.5px;display:grid;grid-template-columns:1fr auto;gap:8px 14px;align-items:baseline}
|
||||||
|
.commit .msg{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||||
|
.commit .sha{color:var(--dim);font-family:ui-monospace,monospace;font-size:12px}
|
||||||
|
.commit .when{color:var(--dim);font-size:11.5px;text-align:right;white-space:nowrap}
|
||||||
|
.commit .author{color:var(--mut);font-size:11.5px;grid-column:1/-1}
|
||||||
|
footer{border-top:1px solid var(--line);padding:2rem 1.2rem 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2rem}
|
||||||
|
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
|
||||||
|
.private-note{background:var(--panel);border:1px dashed var(--line);border-left:3px solid var(--acid);
|
||||||
|
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
|
||||||
|
<div class="top">
|
||||||
|
<a href="../../">hephaestus.x</a> / <a href="../">silentmode</a> / <strong>theseus</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header class="repo">
|
||||||
|
<h1><span class="owner">silentmode</span><span class="sep">/</span><span class="name">theseus</span></h1>
|
||||||
|
<p class="meta" id="meta"><span>loading …</span></p>
|
||||||
|
<p class="desc" id="desc"></p>
|
||||||
|
<div class="cta">
|
||||||
|
<a class="btn acid" href="https://code.silentmode.st/silentmode/theseus">Browse full source →</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/theseus/issues">Issues</a>
|
||||||
|
<a class="btn" href="https://code.silentmode.st/silentmode/theseus/releases">Releases</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>README</h2>
|
||||||
|
<div id="readme" class="empty">Loading README from the forge…</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Recent commits</h2>
|
||||||
|
<div class="commits" id="commits"><div class="commit"><div class="msg">Loading…</div></div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Clone</h2>
|
||||||
|
<div class="clone-label">HTTPS (with a personal access token)</div>
|
||||||
|
<pre class="clone">git clone https://<you>:<token>@code.silentmode.st/silentmode/theseus.git</pre>
|
||||||
|
<div class="clone-label">SSH (with a key registered under your account)</div>
|
||||||
|
<pre class="clone">git clone ssh://git@code.silentmode.st:2222/silentmode/theseus.git</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="../../">hephaestus.x</a> · Silent Mode's decentralized code host · <a href="https://code.silentmode.st/">code.silentmode.st</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const REPO = "theseus";
|
||||||
|
const API = `https://code.silentmode.st/api/v1/repos/silentmode/${REPO}`;
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
return String(s ?? "").replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||||
|
}
|
||||||
|
function fmtDate(iso) {
|
||||||
|
if (!iso) return "";
|
||||||
|
const d = new Date(iso);
|
||||||
|
if (isNaN(d)) return "";
|
||||||
|
const days = (Date.now() - d.getTime()) / 86400000;
|
||||||
|
if (days < 1) return "today";
|
||||||
|
if (days < 30) return `${Math.round(days)}d ago`;
|
||||||
|
return d.toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal markdown → HTML renderer for the README preview.
|
||||||
|
// Not comprehensive — covers headers, paragraphs, lists, links, inline code,
|
||||||
|
// fenced code blocks, and blockquotes. For anything richer, users click through
|
||||||
|
// to the full forge which has Forgejo's proper markdown renderer.
|
||||||
|
function mdToHtml(md) {
|
||||||
|
// Escape first, then apply markdown patterns
|
||||||
|
let s = esc(md);
|
||||||
|
// Fenced code blocks
|
||||||
|
s = s.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => `<pre><code>${code.trimEnd()}</code></pre>`);
|
||||||
|
// Headers
|
||||||
|
s = s.replace(/^### (.+)$/gm, "<h3>$1</h3>");
|
||||||
|
s = s.replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
||||||
|
s = s.replace(/^# (.+)$/gm, "<h1>$1</h1>");
|
||||||
|
// Bold + italic
|
||||||
|
s = s.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
||||||
|
s = s.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
||||||
|
// Inline code (leave alone anything inside <pre> — the regex won't match across newlines)
|
||||||
|
s = s.replace(/`([^`\n]+)`/g, "<code>$1</code>");
|
||||||
|
// Links [text](url)
|
||||||
|
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
||||||
|
// Unordered lists
|
||||||
|
s = s.replace(/(?:^|\n)((?:- .+\n?)+)/g, m => "\n<ul>" + m.trim().split("\n").map(l => `<li>${l.replace(/^-\s+/, "")}</li>`).join("") + "</ul>\n");
|
||||||
|
// Blockquotes
|
||||||
|
s = s.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
|
||||||
|
// Paragraphs: any line-block not already wrapped in a block tag
|
||||||
|
s = s.split(/\n{2,}/).map(chunk => {
|
||||||
|
if (/^<(h[1-6]|ul|ol|pre|blockquote)/.test(chunk.trim())) return chunk;
|
||||||
|
return chunk.trim() ? `<p>${chunk.trim().replace(/\n/g, "<br>")}</p>` : "";
|
||||||
|
}).join("\n");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRepo() {
|
||||||
|
const meta = document.getElementById("meta");
|
||||||
|
const desc = document.getElementById("desc");
|
||||||
|
const readme = document.getElementById("readme");
|
||||||
|
const commits = document.getElementById("commits");
|
||||||
|
try {
|
||||||
|
const info = await fetch(API).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)));
|
||||||
|
const parts = [];
|
||||||
|
if (info.private) parts.push('<span style="color:var(--dim)">private</span>');
|
||||||
|
if (info.default_branch) parts.push(`<span>default branch <code>${esc(info.default_branch)}</code></span>`);
|
||||||
|
if (info.size != null) parts.push(`<span>${info.size < 1024 ? info.size + " KB" : (info.size / 1024).toFixed(1) + " MB"}</span>`);
|
||||||
|
if (info.updated_at) parts.push(`<span>updated ${fmtDate(info.updated_at)}</span>`);
|
||||||
|
meta.innerHTML = parts.join("");
|
||||||
|
desc.textContent = info.description || "";
|
||||||
|
|
||||||
|
// README
|
||||||
|
try {
|
||||||
|
const branch = info.default_branch || "master";
|
||||||
|
const rmRes = await fetch(`${API}/raw/README.md?ref=${encodeURIComponent(branch)}`);
|
||||||
|
if (rmRes.ok) {
|
||||||
|
const md = await rmRes.text();
|
||||||
|
readme.classList.remove("empty");
|
||||||
|
readme.innerHTML = mdToHtml(md);
|
||||||
|
} else {
|
||||||
|
readme.textContent = "(no README.md in this repo)";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
readme.textContent = `(couldn't load README: ${e.message})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent commits
|
||||||
|
try {
|
||||||
|
const cRes = await fetch(`${API}/commits?limit=8`);
|
||||||
|
if (cRes.ok) {
|
||||||
|
const list = await cRes.json();
|
||||||
|
commits.innerHTML = list.slice(0, 8).map(c => {
|
||||||
|
const msg = (c.commit?.message || "").split("\n")[0];
|
||||||
|
const author = c.commit?.author?.name || c.author?.login || "unknown";
|
||||||
|
const when = fmtDate(c.commit?.author?.date);
|
||||||
|
const sha = (c.sha || "").slice(0, 7);
|
||||||
|
return `<a class="commit" href="https://code.silentmode.st/silentmode/${REPO}/commit/${sha}">
|
||||||
|
<div class="msg">${esc(msg)}</div>
|
||||||
|
<div class="sha">${esc(sha)}</div>
|
||||||
|
<div class="author">by ${esc(author)}</div>
|
||||||
|
<div class="when">${esc(when)}</div>
|
||||||
|
</a>`;
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
commits.innerHTML = `<div class="commit"><div class="msg" style="color:var(--dim)">Couldn't load commits: ${esc(e.message)}</div></div>`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Repo is private (401) or gone (404). Show a helpful message + sign-in CTA.
|
||||||
|
meta.innerHTML = '<span style="color:var(--dim)">private repo — sign in to browse</span>';
|
||||||
|
readme.innerHTML = `
|
||||||
|
<div class="private-note">
|
||||||
|
This repository is private. To browse it, <a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">sign in with your wallet</a>
|
||||||
|
on <code>code.silentmode.st</code> — if you've been added as a collaborator or org member,
|
||||||
|
you'll be able to read it there.
|
||||||
|
</div>`;
|
||||||
|
commits.innerHTML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRepo();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Reference in a new issue