diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index ba583d5..a36429f 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -8,128 +8,21 @@ on: jobs: build-release: runs-on: ubuntu-latest - steps: - name: Checkout - uses: actions/checkout@v4 - - - name: Install dependencies + uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + - run: pip install -r mkdocs/requirements.txt + - run: mkdocs build -f mkdocs/mkdocs.yml -d $RUNNER_TEMP/${{ github.ref_name }} + # - run: mkdocs gh-deploy --force + - name: Zip Folder run: | - sudo apt-get update - - # texlive-latex-recommended \ - # texlive-fonts-recommended \ - # texlive-latex-extra \ - - sudo apt-get install -y pandoc \ - jq \ - curl - - name: Build pandoc after-body include from .js files - run: | - set -euo pipefail - - mkdir -p build - out="build/after-body.html" - : > "$out" # truncate - - # Ensure deterministic order - shopt -s nullglob - files=(js/*.js) - if [ ${#files[@]} -eq 0 ]; then - echo "No JS files found in js/; generating empty after-body.html" - exit 0 - fi - - for f in "${files[@]}"; do - { - echo "" - } >> "$out" - done - - name: Build artifacts - run: | - set -euo pipefail - mkdir -p dist - - for doc in *.md; do - # Skip README.md - [[ "$doc" == "README.md" ]] && continue - basename="${doc%.md}" - # Mobile-friendly HTML with TOC + serif styling - pandoc "${doc}" \ - --toc \ - --standalone \ - --number-sections \ - --toc-depth=2 \ - --lua-filter pandoc-filters/shift-numbering.lua \ - --metadata-file metadata.yml \ - --embed-resources \ - --metadata title="CMBA ${basename}" \ - --include-after-body=build/after-body.html \ - --css styles/style.css \ - -o "dist/${basename}.html" - - # PDF - # pandoc "${doc}" \ - # --metadata title="CMBA ${doc}" \ - # -o "dist/${doc}.pdf" - - # Include source markdown for transparency - cp ${doc} dist/ - done - - - - name: Create or update Gitea Release and upload assets - env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - GITEA_BASE_URL: ${{ secrets.GITEA_BASE_URL }} - # Gitea provides these variables in Actions: - REPO: ${{ gitea.repository }} # "owner/repo" - TAG: ${{ gitea.ref_name }} # e.g. "v2026.0" - run: | - set -euo pipefail - - OWNER="$(echo "$REPO" | cut -d/ -f1)" - NAME="$(echo "$REPO" | cut -d/ -f2)" - API="${GITHUB_API_URL}" - AUTH="Authorization: token ${GITEA_TOKEN}" - - echo "Repo: $OWNER/$NAME" - echo "Tag: $TAG" - echo "API: $API" - - # Check if release exists for this tag - existing_release_json="$(curl -sS -H "$AUTH" \ - "$API/repos/$OWNER/$NAME/releases/tags/$TAG" || true)" - - if echo "$existing_release_json" | jq -e '.id' >/dev/null 2>&1; then - RELEASE_ID="$(echo "$existing_release_json" | jq -r '.id')" - echo "Release exists (id=$RELEASE_ID)." - else - echo "Creating release for tag $TAG..." - create_json="$(curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \ - "$API/repos/$OWNER/$NAME/releases" \ - -d "$(jq -n --arg tag "$TAG" --arg name "$TAG" \ - '{tag_name:$tag, name:$name, draft:false, prerelease:false, body:"CMBA rulebooks release."}')")" - RELEASE_ID="$(echo "$create_json" | jq -r '.id')" - echo "Created release (id=$RELEASE_ID)." - fi - - # Upload each file as an asset. If an asset already exists with same name, delete then re-upload. - assets_json="$(curl -sS -H "$AUTH" "$API/repos/$OWNER/$NAME/releases/$RELEASE_ID/assets" || echo '[]')" - - for f in dist/*; do - filename="$(basename "$f")" - existing_asset_id="$(echo "$assets_json" | jq -r --arg n "$filename" '.[] | select(.name==$n) | .id' | head -n1 || true)" - - if [ -n "${existing_asset_id:-}" ] && [ "$existing_asset_id" != "null" ]; then - echo "Deleting existing asset: $filename (id=$existing_asset_id)" - curl -sS -X DELETE -H "$AUTH" \ - "$API/repos/$OWNER/$NAME/releases/$RELEASE_ID/assets/$existing_asset_id" >/dev/null - fi - echo "Uploading $filename" - curl -sS --fail -X POST -H "$AUTH" -H "Accept: application/json" -F "attachment=@$f" "$API/repos/$OWNER/$NAME/releases/$RELEASE_ID/assets?name=$filename" >/dev/null - done - - echo "Done. Release page: ${GITEA_BASE_URL}/${OWNER}/${NAME}/releases/tag/${TAG}" \ No newline at end of file + zip -r dist/{{ github.ref_name }}.zip $RUNNER_TEMP/${{ github.ref_name }} + - name: Release + if: ${{ !env.ACT }} + uses: softprops/action-gh-release@v2 + with: + files: | + dist/*.zip \ No newline at end of file diff --git a/.gitignore b/.gitignore index 946330a..be6d31f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,13 @@ /dataSources/ /dataSources.local.xml +# Vitrtual Environments +.venv/ +venv/ + .idea/ *.bbprojectd +.vscode # Backups created by aspell *.md.bak* diff --git a/assets/favicon.png b/assets/favicon.png new file mode 100644 index 0000000..c2455d2 Binary files /dev/null and b/assets/favicon.png differ diff --git a/cmba-bylaws.md b/cmba-bylaws.md index 0722474..7af327d 100644 --- a/cmba-bylaws.md +++ b/cmba-bylaws.md @@ -1,5 +1,4 @@ -# By-Laws {.unnumbered} - +# Bylaws ## Definition; Amendments These By-Laws shall stand as the official rules of the CMBA. All Association members shall abide by and are subject to, without exception, all rules and regulations as outlined in the Constitution and By-Laws as well as the authority of the Association President, in accordance with [Subjectivity](#subjectivity) diff --git a/js/quote.js b/js/quote.js deleted file mode 100644 index 8993d44..0000000 --- a/js/quote.js +++ /dev/null @@ -1,185 +0,0 @@ -(() => { - // Inject Quote buttons into Pandoc-style headings and copy a section-range quote to clipboard. - // - // Assumptions (matches your sample): - // - Headings are h1/h2 with stable ids (anchors). - // - A "section" is: the heading + all following sibling elements until the next heading - // of the same or higher level (H2 stops at next H2 or H1; H1 stops at next H1). - // - // Primary target: GroupMe. We optimize text/plain to be readable and "quote-like". - // We still provide text/html for rich paste targets (Docs/Email). - // - // No dependencies. - - const HEADING_SELECTOR = "h1[id], h2[id]"; // change to "h2[id]" if you only want section-level - - document.addEventListener("DOMContentLoaded", () => { - injectQuoteButtons(); - }); - - function injectQuoteButtons() { - document.querySelectorAll(HEADING_SELECTOR).forEach((heading) => { - // Avoid double-injection - if (heading.querySelector(":scope > .quote-btn")) return; - - const btn = document.createElement("button"); - btn.type = "button"; - btn.className = "quote-btn"; - btn.textContent = ""; - btn.innerHTML = ` - - `; - btn.addEventListener("click", (e) => { - e.preventDefault(); - e.stopPropagation(); - onQuoteClick(btn, heading); - }); - - // Add a little separation from heading text - heading.appendChild(document.createTextNode(" ")); - heading.appendChild(btn); - }); - } - - async function onQuoteClick(btn, heading) { - const originalText = btn.textContent; - - try { - btn.textContent = "Copying…"; - btn.disabled = true; - - await copyQuotedSection(heading); - - btn.textContent = "Copied"; - setTimeout(() => { - btn.textContent = originalText; - btn.disabled = false; - }, 800); - } catch (err) { - console.error(err); - btn.textContent = "Failed"; - setTimeout(() => { - btn.textContent = originalText; - btn.disabled = false; - }, 1200); - } - } - - async function copyQuotedSection(heading) { - if (!heading.id) throw new Error("Heading missing id."); - - const level = heading.tagName; // H1 or H2 - - // Clone heading + subsequent siblings for the quote range - const nodes = []; - nodes.push(heading.cloneNode(true)); - - let el = heading.nextElementSibling; - while (el) { - if (isBoundary(level, el)) break; - nodes.push(el.cloneNode(true)); - el = el.nextElementSibling; - } - - // Remove injected button from cloned heading (so it doesn't show up in pasted content) - nodes[0].querySelector(".quote-btn")?.remove(); - - const container = document.createElement("div"); - nodes.forEach((n) => container.appendChild(n)); - - const href = buildAnchorUrl(heading.id); - - // HTML (for rich paste targets) - const html = buildHtmlQuote(container.innerHTML, href); - - // Plain text (optimized for GroupMe) - const plain = buildGroupMePlainQuote(container, href); - - await writeClipboardMultiFormat({ html, plain, url: href }); - } - - function isBoundary(level, el) { - // Stop at next heading of same or higher rank. - // H2 stops at next H1 or H2. - // H1 stops at next H1. - if (level === "H1") return el.tagName === "H1"; - if (level === "H2") return el.tagName === "H1" || el.tagName === "H2"; - return false; - } - - function buildAnchorUrl(id) { - const url = new URL(window.location.href); - url.hash = id; - return url.toString(); - } - - function buildHtmlQuote(innerHtml, href) { - const safeHref = escapeHtmlAttr(href); - const safeText = escapeHtml(href); - - return ` -
- ${innerHtml} - -- `.trim(); - } - - function buildGroupMePlainQuote(container, href) { - // Create a readable quote-like block: - // - Prefix lines with "> " to visually indicate quoting (works even if not rendered specially) - // - Include Source link - const text = normalizePlainText(container); - - const quoted = text - .split("\n") - .map((line) => (line.trim().length ? `> ${line}` : `>`)) - .join("\n"); - - return `${quoted}\n> \n> Source: ${href}`; - } - - function normalizePlainText(container) { - // Use innerText to approximate what the user sees (captures list numbering reasonably in many browsers). - // Then normalize spacing. - return (container.innerText || "") - .replace(/\r\n/g, "\n") - .replace(/\n{3,}/g, "\n\n") - .trim(); - } - - async function writeClipboardMultiFormat({ html, plain, url }) { - // Best path: multi-format clipboard write (Chromium/Edge, etc.) - if (navigator.clipboard?.write) { - const item = new ClipboardItem({ - "text/html": new Blob([html], { type: "text/html" }), - "text/plain": new Blob([plain], { type: "text/plain" }), - // Best-effort URL type; some paste targets prefer this - "text/uri-list": new Blob([url], { type: "text/uri-list" }), - }); - await navigator.clipboard.write([item]); - return; - } - - // Fallback: plain text only - if (navigator.clipboard?.writeText) { - await navigator.clipboard.writeText(plain); - return; - } - - throw new Error("Clipboard API unavailable."); - } - - function escapeHtml(s) { - return String(s) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); - } - - function escapeHtmlAttr(s) { - return escapeHtml(s); - } -})(); \ No newline at end of file diff --git a/metadata.yml b/metadata.yml deleted file mode 100644 index efa2263..0000000 --- a/metadata.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- - title: Chicago Metropolitan Baseball Association - subtitle: Constitution and By-Laws - date: 2024-06-06 ---- \ No newline at end of file diff --git a/mkdocs/index.md b/mkdocs/index.md new file mode 100644 index 0000000..586f560 --- /dev/null +++ b/mkdocs/index.md @@ -0,0 +1,3 @@ +# CMBA Rulebooks + +Use the navigation to view the documents. diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml new file mode 100644 index 0000000..8f47d62 --- /dev/null +++ b/mkdocs/mkdocs.yml @@ -0,0 +1,48 @@ +site_name: CMBA Rulebook +docs_dir: .. +site_dir: ./dist + +# Prevent accidental publishing of repo/CI/dev clutter +exclude_docs: | + .github/ + .git/ + .venv/ + venv/ + "*.sh" + "*.py" + "*.yml" + "*.yaml" + "README.md" +theme: + name: material + palette: + scheme: default + primary: blue + accent: red + logo: ../assets/cmba-logo.svg + favicon: ../assets/favicon.png + features: + - navigation.instant + - navigation.tracking + - navigation.top + - toc.integrate + - search.highlight + - search.suggest +extra_css: + - mkdocs/styles/extra.css +markdown_extensions: + - toc: + permalink: true + - admonition: + # - tables +nav: + - Home: mkdocs/index.md + - Constitution: cmba-constitution.md + - Bylaws: cmba-bylaws.md + +plugins: + - search + - enumerate-headings: + toc_depth: 2 + exclude: + - mkdocs/index.md \ No newline at end of file diff --git a/mkdocs/requirements.txt b/mkdocs/requirements.txt new file mode 100644 index 0000000..bd08198 --- /dev/null +++ b/mkdocs/requirements.txt @@ -0,0 +1,2 @@ +mkdocs-enumerate-headings-plugin==0.6 +mkdocs-material==9.7 \ No newline at end of file diff --git a/mkdocs/styles/extra.css b/mkdocs/styles/extra.css new file mode 100644 index 0000000..4c08ea0 --- /dev/null +++ b/mkdocs/styles/extra.css @@ -0,0 +1,10 @@ +@import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,opsz,wght@0,18..144,300..900;1,18..144,300..900&display=swap'); + +:root { + --md-text-font: "Merriweather" +} + +.md-nav__title { + font-family: "Nunito Sans"; +} \ No newline at end of file diff --git a/pandoc-filters/shift-numbering.lua b/pandoc-filters/shift-numbering.lua deleted file mode 100644 index 5007535..0000000 --- a/pandoc-filters/shift-numbering.lua +++ /dev/null @@ -1,20 +0,0 @@ --- Based on https://github.com/jgm/pandoc/issues/5071#issuecomment-856918980 --- For LaTeX documents, shift all headings down by one. --- Make Level 1 unnumbered, or remove it if it contains the "hidden" class. (`# Title {.hidden}`) -function Header(el) - if el.level == 1 then - --check for hidden class. (Why didn't pandoc make this a set? `{class_name = true}`) - for i, v in ipairs(el.classes) do - if v == "hidden" then - --The empty list means remove the element - return {} - end - end - -- this may mean that there are two unnumbered classes, but that doesn't matter. - el.classes[#el.classes + 1] = "unnumbered" - else - -- all headings > 1 are shifted down so that you don't end up with 0.x.y headings. - el.level = el.level - 1 - end - return el -end \ No newline at end of file