diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index e578fbf..49b59fa 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -98,6 +98,17 @@ jobs: [ -f "$lf" ] && cp "$lf" "${STAGE}/" done + # Copy assets/ folder if it exists (favicon, images, etc.) + if [ -d "assets" ]; then + mkdir -p "${STAGE}/assets" + find ./assets -type f \( -name '*.svg' -o -name '*.png' -o -name '*.ico' -o -name '*.jpg' -o -name '*.webp' \) \ + | while read -r f; do + dest="${STAGE}/${f#./}" + mkdir -p "$(dirname "$dest")" + cp "$f" "$dest" + done + fi + find "${STAGE}" -name '.gitkeep' -delete echo "Staged files:" @@ -131,10 +142,9 @@ jobs: {{ page.title | default: site.title }} - + - @@ -248,16 +277,14 @@ jobs: echo "::group::Processing content files" - # ── README.md → homepage ────────────────────────────────────── if [ -f "README.md" ] && ! head -1 README.md | grep -q '^\-\-\-'; then TEMP=$(mktemp) printf -- '---\nlayout: default\ntitle: Home\npermalink: /\n---\n\n' > "$TEMP" cat README.md >> "$TEMP" mv "$TEMP" README.md - echo "Processed README.md → homepage" + echo "Processed README.md" fi - # ── Fix relative links for Jekyll ───────────────────────────── for f in *.md; do [ -f "$f" ] || continue [ -f "CONTRIBUTING.md" ] && sed -i 's|\](CONTRIBUTING\.md)|\](contributing/)|g' "$f" @@ -265,12 +292,10 @@ jobs: ([ -f "LICENSE" ] || [ -f "LICENSE.txt" ] || [ -f "LICENSE.md" ]) && sed -i 's|\](LICENSE)|\](license/)|g' "$f" done - # ── Docs: add front matter + create index ───────────────────── if [ -d "docs" ]; then for f in docs/*.md; do [ -f "$f" ] || continue [ "$(basename "$f")" = "index.md" ] && continue - if ! head -1 "$f" | grep -q '^\-\-\-'; then BASENAME=$(basename "$f" .md) SYNOPSIS=$(grep -m1 -A1 '## Synopsis' "$f" 2>/dev/null | tail -1 | sed 's/^[[:space:]]*//') @@ -306,7 +331,6 @@ jobs: fi fi - # ── Releases page ───────────────────────────────────────────── mkdir -p _includes cat > _includes/releases.js << 'JSEOF' (async function() { @@ -318,24 +342,25 @@ jobs: var data = await res.json(); if (!data.length) { c.innerHTML = '

No releases found.

'; return; } + function esc(s) { return s.replace(//g,'>'); } + function inline(s) { var codes = []; s = s.replace(/`([^`]+)`/g, function(m, code) { - codes.push(code.replace(//g,'>')); - return '\x00CODE' + (codes.length - 1) + '\x00'; + codes.push(esc(code)); + return '\x00C' + (codes.length - 1) + '\x00'; }); s = s .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') - .replace(/(https?:\/\/[^\s<)]+)/g, function(m, url, offset, str) { - if (str.charAt(offset - 1) === '"' || str.charAt(offset - 1) === '>' || str.substring(offset - 6, offset) === 'href="') return m; - return '' + url + ''; + .replace(/(^|[^"'>])(https?:\/\/[^\s<)]+)/g, function(m, pre, url) { + return pre + '' + url + ''; }) .replace(/(^|[\s(])@([a-zA-Z0-9_-]+)/g, function(m, pre, user) { return pre + '@' + user + ''; }) .replace(/\*\*([^*]+)\*\*/g, '$1') - .replace(/(?$1'); - s = s.replace(/\x00CODE(\d+)\x00/g, function(m, idx) { + .replace(/(^|[^*])\*([^*\n]+)\*([^*]|$)/g, '$1$2$3'); + s = s.replace(/\x00C(\d+)\x00/g, function(m, idx) { return '' + codes[parseInt(idx)] + ''; }); return s; @@ -344,52 +369,46 @@ jobs: function md(s) { if (!s) return ''; var lines = s.split('\n'); - var html = ''; + var out = ''; var inList = false; for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - var h3 = line.match(/^### (.+)$/); - var h2 = line.match(/^## (.+)$/); - var li = line.match(/^\* (.+)$/); - var cb = line.match(/^```(.*)$/); - if (cb) { - if (inList) { html += ''; inList = false; } - var lang = cb[1].trim(); - var escaped = ''; + var L = lines[i]; + var mH3 = L.match(/^### (.+)$/); + var mH2 = L.match(/^## (.+)$/); + var mLi = L.match(/^\* (.+)$/); + var mCb = L.match(/^```(.*)$/); + if (mCb) { + if (inList) { out += ''; inList = false; } + var lang = mCb[1].trim(); + var code = ''; i++; - while (i < lines.length && !lines[i].match(/^```/)) { escaped += lines[i] + '\n'; i++; } - escaped = escaped.replace(//g,'>'); - var langClass = lang ? ' class="language-' + lang + '"' : ''; - html += '' + escaped + ''; - } else if (h2) { - if (inList) { html += ''; inList = false; } - html += '

' + inline(h2[1]) + '

'; - } else if (h3) { - if (inList) { html += ''; inList = false; } - html += '

' + inline(h3[1]) + '

'; - } else if (li) { - if (!inList) { html += ''; inList = false; } + out += '

' + inline(mH2[1]) + '

'; + } else if (mH3) { + if (inList) { out += ''; inList = false; } + out += '

' + inline(mH3[1]) + '

'; + } else if (mLi) { + if (!inList) { out += ''; inList = false; } - if (line.trim() === '---') { html += '
'; } - else if (line.trim()) { - html += '

' + inline(line) + '

'; - } + if (inList) { out += ''; inList = false; } + var trimmed = L.trim(); + if (trimmed === '---') { out += '
'; } + else if (trimmed !== '') { out += '

' + inline(L) + '

'; } } } - if (inList) html += ''; - return html; + if (inList) out += ''; + return out; } - // Fetch hash file contents for assets that are hash files - async function fetchHashContent(url) { + async function getHash(url) { try { - var r = await fetch(url); - if (r.ok) { - var text = await r.text(); - return text.trim(); - } + var r = await fetch(url, {headers:{'Accept':'application/octet-stream'}}); + if (r.ok) return (await r.text()).trim(); } catch(e) {} return null; } @@ -397,79 +416,65 @@ jobs: var html = ''; for (var i = 0; i < data.length; i++) { var r = data[i]; - var d = new Date(r.published_at).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }); - var latest = i === 0 ? 'latest' : ''; + var d = new Date(r.published_at).toLocaleDateString('en-GB', {day:'numeric',month:'short',year:'numeric'}); + var latest = i === 0 ? ' latest' : ''; var openAttr = i === 0 ? ' open' : ''; - // Commit SHA in header var sha = r.target_commitish || ''; - var shortSha = sha.substring(0, 7); - var shaHtml = ''; - if (sha.length >= 7) { - shaHtml = '' + shortSha + ''; - } + var shortSha = sha.length >= 7 ? sha.substring(0, 7) : ''; + var shaHtml = shortSha ? '' + shortSha + '' : ''; - // Assets — show ALL files, no filtering - var assets = ''; var allAssets = r.assets || []; var srcZip = 'https://github.com/' + repo + '/archive/refs/tags/' + r.tag_name + '.zip'; var srcTar = 'https://github.com/' + repo + '/archive/refs/tags/' + r.tag_name + '.tar.gz'; - var totalAssets = allAssets.length + 2; - assets += '
Assets (' + totalAssets + ')'; - - // Build asset HTML with hash content fetched inline + var assetsHtml = '
Assets (' + (allAssets.length + 2) + ')'; for (var j = 0; j < allAssets.length; j++) { var a = allAssets[j]; - var isHash = a.name.match(/\.(sha256|sha512|md5|sha1)$/i); - assets += '
'; - assets += '' + a.name + ''; - assets += ' ' + (a.size / 1024).toFixed(1) + ' KB'; - if (isHash) { - assets += 'loading hash...'; - } - assets += '
'; + var isHash = /\.(sha256|sha512|md5|sha1)$/i.test(a.name); + assetsHtml += '
' + + '' + a.name + '' + + ' ' + (a.size / 1024).toFixed(1) + ' KB' + + (isHash ? '' : '') + + '
'; } + assetsHtml += ''; + assetsHtml += '
Source code (tar.gz)
'; + assetsHtml += '
'; - assets += ''; - assets += '
Source code (tar.gz)
'; - assets += '
'; + var footer = ''; - // View release footer - var footer = ''; - - html += '
' - + '' + r.tag_name + '' + html += '
' + + '' + + '' + esc(r.tag_name) + '' + latest - + '' + d + '' + + ' ' + d + '' + shaHtml - + '
' + + '
' + + '
' + md(r.body) - + assets + + assetsHtml + footer + '
'; } c.innerHTML = html; - // Fetch and populate hash contents - var hashSpans = c.querySelectorAll('[data-hash-url]'); - for (var k = 0; k < hashSpans.length; k++) { - (function(span) { - fetchHashContent(span.getAttribute('data-hash-url')).then(function(hash) { - if (hash) { - span.textContent = hash; - } else { - span.textContent = ''; - } - }); - })(hashSpans[k]); - } + document.querySelectorAll('[data-hash-url]').forEach(function(span) { + getHash(span.getAttribute('data-hash-url')).then(function(h) { + span.textContent = h || ''; + }); + }); - // Trigger Prism highlighting on dynamically inserted code blocks - if (typeof Prism !== 'undefined') { - setTimeout(function() { Prism.highlightAll(); }, 100); + function doHighlight() { + if (typeof Prism !== 'undefined' && Prism.highlightAll) { + Prism.highlightAll(); + } else { + setTimeout(doHighlight, 300); + } } + setTimeout(doHighlight, 300); + } catch(e) { c.innerHTML = '

Unable to load releases. Visit GitHub directly.

'; } @@ -490,7 +495,6 @@ jobs: RELEOF echo "Created releases.md" - # ── License page (full content from LICENSE/LICENSE.txt) ────── LICENSE_SRC="" for lf in LICENSE LICENSE.txt; do [ -f "$lf" ] && LICENSE_SRC="$lf" && break @@ -513,7 +517,6 @@ jobs: echo "Processed LICENSE.md" fi - # ── CONTRIBUTING.md ─────────────────────────────────────────── if [ -f "CONTRIBUTING.md" ] && ! head -1 CONTRIBUTING.md | grep -q '^\-\-\-'; then TEMP=$(mktemp) printf -- '---\nlayout: default\ntitle: Contributing\npermalink: /contributing/\n---\n\n' > "$TEMP" @@ -522,7 +525,6 @@ jobs: echo "Processed CONTRIBUTING.md" fi - # ── CODE_OF_CONDUCT.md ──────────────────────────────────────── if [ -f "CODE_OF_CONDUCT.md" ] && ! head -1 CODE_OF_CONDUCT.md | grep -q '^\-\-\-'; then TEMP=$(mktemp) printf -- '---\nlayout: default\ntitle: Code of Conduct\npermalink: /code-of-conduct/\n---\n\n' > "$TEMP" diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 0000000..6bdd79b --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +