14 Commits

Author SHA1 Message Date
08c50299f0 Update RELEASING.md
All checks were successful
CI - Docs build check / build-check (push) Successful in 6s
2026-01-19 22:42:21 +00:00
27cb3e5c25 some fixes propagating to other workflows
All checks were successful
CI - Docs build check / build-check (push) Successful in 5s
Pre-release - Build and publish docs / prerelease (push) Successful in 9m7s
Release - Build and publish docs / release (push) Has been skipped
2026-01-19 14:47:52 -06:00
557a202a68 attempt setting env variable
Some checks failed
CI - Docs build check / build-check (push) Successful in 5s
Pre-release - Build and publish docs / prerelease (push) Failing after 4m36s
Release - Build and publish docs / release (push) Has been skipped
2026-01-19 14:30:42 -06:00
47485b05b2 disable cache, keep setup python
Some checks failed
CI - Docs build check / build-check (push) Failing after 21s
2026-01-19 14:26:45 -06:00
9b97a466bd test mkdocs_strict
Some checks failed
CI - Docs build check / build-check (push) Failing after 4s
2026-01-19 14:25:27 -06:00
a850448f0a skip pip cache. 2026-01-19 14:22:33 -06:00
fb4b4be4d0 fix wrong env variable
Some checks failed
CI - Docs build check / build-check (push) Failing after 4m36s
2026-01-19 14:13:13 -06:00
ef8fd7eae6 add releasing, change ci build check directory
Some checks failed
CI - Docs build check / build-check (push) Failing after 4m35s
2026-01-19 13:41:55 -06:00
dded348ba4 fix checks
Some checks failed
CI - Docs build check / build-check (push) Failing after 4m35s
2026-01-19 13:30:38 -06:00
Tony
890c773c01 fix release check
Some checks failed
Release - Build and publish docs / release (push) Failing after 4s
2026-01-19 13:04:27 -06:00
Tony
cc90896be8 restructure workflows
Some checks failed
Release - Build and publish docs / release (push) Failing after 26s
2026-01-19 12:57:46 -06:00
6faab5aa19 fix 2
All checks were successful
Build and publish CMBA rulebooks (Gitea) / build-release (push) Successful in 6s
2026-01-16 16:02:54 -06:00
900305df1d fix
Some checks failed
Build and publish CMBA rulebooks (Gitea) / build-release (push) Failing after 5s
2026-01-16 16:00:28 -06:00
ab397c18dd cleanup, migrate to mkdocs
Some checks failed
Build and publish CMBA rulebooks (Gitea) / build-release (push) Failing after 49s
2026-01-16 15:56:03 -06:00
16 changed files with 587 additions and 319 deletions

49
.github/workflows/ci-docs.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: CI - Docs build check
on:
pull_request:
branches: [release-candidate]
push:
branches: [release-candidate]
workflow_dispatch:
permissions:
contents: read
env:
MKDOCS_STRICT: ${{ vars.MKDOCS_STRICT || 'true' }}
jobs:
build-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
# if: false
uses: actions/setup-python@v5
with:
python-version: "3.11"
# cache: "pip"
# cache-dependency-path: mkdocs/requirements.txt
- name: Install dependencies
run: |
set -euo pipefail
pip install -r mkdocs/requirements.txt
- name: MkDocs build
run: |
set -euo pipefail
echo "MKDOCS_STRICT: $MKDOCS_STRICT"
MKDOCS_STRICT="${MKDOCS_STRICT:-true}"
STRICT_FLAG=""
if [ "${MKDOCS_STRICT}" = "true" ]; then
STRICT_FLAG="--strict"
fi
OFFLINE=true mkdocs build ${STRICT_FLAG} -f mkdocs/mkdocs.yml -d $RUNNER_TEMP/

78
.github/workflows/deploy-main-pages.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
name: Deploy main to GitHub Pages (stable)
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
jobs:
deploy-main-pages:
if: ${{ env.ENABLE_DEPLOY == 'true' && env.ACT != 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout (main)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: mkdocs/requirements.txt
- name: Install dependencies
run: |
set -euo pipefail
pip install -r mkdocs/requirements.txt
- name: Build (MKDOCS_STRICT)
run: |
set -euo pipefail
MKDOCS_STRICT="${MKDOCS_STRICT:-true}"
STRICT_FLAG=""
if [ "${MKDOCS_STRICT}" = "true" ]; then
STRICT_FLAG="--strict"
fi
OFFLINE=true mkdocs build ${STRICT_FLAG} -f mkdocs/mkdocs.yml -d site_build
- name: Checkout gh-pages branch
run: |
set -euo pipefail
git fetch origin gh-pages:gh-pages || true
if git show-ref --verify --quiet refs/heads/gh-pages; then
git switch gh-pages
else
git switch --orphan gh-pages
rm -rf ./*
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit --allow-empty -m "Initialize gh-pages"
fi
- name: Publish stable site to root (preserve rc/)
run: |
set -euo pipefail
mkdir -p _keep
if [ -d rc ]; then cp -a rc _keep/; fi
rm -rf ./*
if [ -d _keep/rc ]; then mv _keep/rc ./rc; fi
rm -rf _keep
cp -a ../site_build/. .
git add -A
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "Deploy stable site from main" || echo "No changes to commit"
git push origin gh-pages

91
.github/workflows/deploy-rc-pages.yml vendored Normal file
View File

@@ -0,0 +1,91 @@
name: Deploy RC preview to GitHub Pages
on:
push:
tags:
- "v*-rc.*"
workflow_dispatch:
permissions:
contents: write
jobs:
deploy-rc-pages:
if: ${{ env.ENABLE_DEPLOY == 'true' && env.CI_PROVIDER == 'github' && env.ACT != 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout (tag)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure tag commit is on release-candidate
run: |
set -euo pipefail
git fetch origin release-candidate:refs/remotes/origin/release-candidate
if ! git merge-base --is-ancestor "${GITHUB_SHA}" "origin/release-candidate"; then
echo "ERROR: Tagged commit ${GITHUB_SHA} is not on release-candidate. Refusing RC deploy."
exit 1
fi
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: mkdocs/requirements.txt
- name: Install dependencies
run: |
set -euo pipefail
pip install -r mkdocs/requirements.txt
- name: Build (MKDOCS_STRICT)
run: |
set -euo pipefail
MKDOCS_STRICT="${MKDOCS_STRICT:-true}"
STRICT_FLAG=""
if [ "${MKDOCS_STRICT}" = "true" ]; then
STRICT_FLAG="--strict"
fi
OFFLINE=true mkdocs build ${STRICT_FLAG} -f mkdocs/mkdocs.yml -d site_build
- name: Checkout gh-pages branch
run: |
set -euo pipefail
git fetch origin gh-pages:gh-pages || true
if git show-ref --verify --quiet refs/heads/gh-pages; then
git switch gh-pages
else
git switch --orphan gh-pages
rm -rf ./*
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit --allow-empty -m "Initialize gh-pages"
fi
- name: Publish RC preview under /rc/<tag>/
run: |
set -euo pipefail
REF="${{ github.ref_name }}"
mkdir -p "rc/${REF}"
rm -rf "rc/${REF:?}/"* || true
cp -a ../site_build/. "rc/${REF}/"
mkdir -p rc
if [ ! -f rc/index.html ]; then
cat > rc/index.html << 'EOF'
<!doctype html><meta charset="utf-8"><title>RC Previews</title>
<h1>RC Previews</h1><p>Browse rc/&lt;tag&gt;/</p>
EOF
fi
git add -A
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "Deploy RC preview ${REF}" || echo "No changes to commit"
git push origin gh-pages

88
.github/workflows/prerelease-docs.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: Pre-release - Build and publish docs
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: write
env:
MKDOCS_STRICT: ${{ vars.MKDOCS_STRICT || 'true' }}
jobs:
prerelease:
if: >
startsWith(github.ref_name, 'v')
&& contains(github.ref_name, '-rc')}}
runs-on: ubuntu-latest
steps:
- name: Checkout (tag)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure tag commit is on release-candidate
run: |
set -euo pipefail
git fetch origin release-candidate:refs/remotes/origin/release-candidate
if ! git merge-base --is-ancestor "${GITHUB_SHA}" "origin/release-candidate"; then
echo "ERROR: Tagged commit ${GITHUB_SHA} is not on release-candidate. Refusing prerelease."
exit 1
fi
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: mkdocs/requirements.txt
- name: Install dependencies
run: |
set -euo pipefail
pip install -r mkdocs/requirements.txt
- name: CI gate (MKDOCS_STRICT)
run: |
set -euo pipefail
MKDOCS_STRICT="${MKDOCS_STRICT:-true}"
STRICT_FLAG=""
if [ "${MKDOCS_STRICT}" = "true" ]; then
STRICT_FLAG="--strict"
fi
OFFLINE=true mkdocs build ${STRICT_FLAG} -f mkdocs/mkdocs.yml -d $RUNNER_TEMP
- name: Build artifact (non-strict)
env:
MKDOCS_STRICT: "false"
run: |
set -euo pipefail
MKDOCS_STRICT="${MKDOCS_STRICT:-true}"
STRICT_FLAG=""
if [ "${MKDOCS_STRICT}" = "true" ]; then
STRICT_FLAG="--strict"
fi
mkdir -p "${RUNNER_TEMP}/dist"
OFFLINE=true mkdocs build ${STRICT_FLAG} -f mkdocs/mkdocs.yml -d "${RUNNER_TEMP}/dist/${{ github.ref_name }}"
- name: Zip artifact
run: |
set -euo pipefail
cd "${RUNNER_TEMP}/dist"
zip -r "${{ github.ref_name }}.zip" "./${{ github.ref_name }}"
- name: Publish prerelease (skip on act)
if: ${{ env.ENABLE_RELEASE == 'true' && env.ACT != 'true' }}
uses: softprops/action-gh-release@v2
with:
prerelease: true
files: |
${{ runner.temp }}/dist/${{ github.ref_name }}.zip

View File

@@ -1,135 +1,86 @@
name: Build and publish CMBA rulebooks (Gitea)
name: Release - Build and publish docs
on:
push:
tags:
- "v*"
permissions:
contents: write
env:
MKDOCS_STRICT: ${{ vars.MKDOCS_STRICT || 'true' }}
jobs:
build-release:
release:
if: >
startsWith(github.ref_name, 'v')
&& !contains(github.ref_name, '-rc')}}
runs-on: ubuntu-latest
steps:
- name: Checkout
- name: Checkout (tag)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure tag commit is on main
run: |
set -euo pipefail
git fetch origin main:refs/remotes/origin/main
if ! git merge-base --is-ancestor "${GITHUB_SHA}" "origin/main"; then
echo "ERROR: Tagged commit ${GITHUB_SHA} is not on main. Refusing release."
exit 1
fi
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: mkdocs/requirements.txt
- name: Install dependencies
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
set -euo pipefail
pip install -r mkdocs/requirements.txt
- name: CI gate (MKDOCS_STRICT)
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
MKDOCS_STRICT="${MKDOCS_STRICT:-true}"
STRICT_FLAG=""
if [ "${MKDOCS_STRICT}" = "true" ]; then
STRICT_FLAG="--strict"
fi
for f in "${files[@]}"; do
{
echo "<script>"
echo "$f"
echo "</script>"
} >> "$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"
OFFLINE=true mkdocs build ${STRICT_FLAG} -f mkdocs/mkdocs.yml -d $RUNNER_TEMP
# 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
- name: Build artifact (non-strict)
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"
MKDOCS_STRICT: "false"
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)."
MKDOCS_STRICT="${MKDOCS_STRICT:-true}"
STRICT_FLAG=""
if [ "${MKDOCS_STRICT}" = "true" ]; then
STRICT_FLAG="--strict"
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 '[]')"
mkdir -p "${RUNNER_TEMP}/dist"
OFFLINE=true mkdocs build ${STRICT_FLAG} -f mkdocs/mkdocs.yml -d "${RUNNER_TEMP}/dist/${{ github.ref_name }}"
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)"
- name: Zip artifact
run: |
set -euo pipefail
cd "${RUNNER_TEMP}/dist"
zip -r "${{ github.ref_name }}.zip" "./${{ github.ref_name }}"
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}"
- name: Publish release (skip on act; allowed on gitea)
if: ${{ env.ENABLE_RELEASE == 'true' && env.ACT != 'true' }}
uses: softprops/action-gh-release@v2
with:
files: |
${{ runner.temp }}/dist/${{ github.ref_name }}.zip

5
.gitignore vendored
View File

@@ -7,8 +7,13 @@
/dataSources/
/dataSources.local.xml
# Vitrtual Environments
.venv/
venv/
.idea/
*.bbprojectd
.vscode
# Backups created by aspell
*.md.bak*

154
RELEASING.md Normal file
View File

@@ -0,0 +1,154 @@
# Technical Release & Automation Notes
> ⚠️ This document describes **technical automation and versioning**
> used by the repository (CI/CD, tags, and deployments).
>
> It does **not** define:
> - the organizational process for approving bylaws or constitutional changes
> - who is authorized to make those changes
> - when changes are considered “official” by the organization
>
> Those governance decisions within the organizations constitution.
This document exists solely to explain how **Git version tags, automated checks,
and publishing workflows** are wired together so that future maintainers
(dozens of months from now) do not accidentally trigger or break them.
# Release & CI Process
This repository uses a deliberately strict and explicit release process.
It exists to prevent accidental releases, deployments, or CI runs.
If you are changing tags, workflows, or branches, read this first.
## Branches
- `development`
- Day-to-day work
- No releases or deployments happen from this branch
- `release-candidate`
- Stabilization branch
- CI runs here with strict checks
- Release candidates are tagged from here
- `main`
- Stable, releasable state
- Final releases are tagged from here
- Stable GitHub Pages content is deployed from here
## Tag Naming Policy
### Final Releases
- Tags **must** start with `v`
- Tags **must not** contain `-rc`
Examples:
- `v2026.1.0`
- `v1.0.0`
### Release Candidates
- Tags **must** start with `v`
- Tags **must** contain `-rc`
Examples:
- `v2026.1.0-rc.1`
- `v1.0.0-rc.2`
This naming policy is intentional and is enforced by CI.
## CI and Workflows Overview
| Workflow | Trigger | Purpose |
|-------------------|----------------------------------|----------------------------------------|
| CI Docs | Push / PR to `release-candidate` | Strict MkDocs build validation |
| Prerelease | Tag `v*` containing `-rc` | Build and publish prerelease artifacts |
| Release | Tag `v*` not containing `-rc` | Build and publish final release |
| RC Pages Deploy | RC tag | Publish preview docs under `/rc//` |
| Main Pages Deploy | Push to `main` | Publish stable docs to root |
## Why both release workflows trigger on `v*`
GitHub Actions does **not** support negative tag filters.
Because of this:
- Both release and prerelease workflows trigger on `v*`
- Each workflow uses a job-level `if:` to decide whether it should run
This ensures:
- Symmetry between workflows
- Clear, explicit logic
- No reliance on fragile glob patterns
## Safety Checks (Intentional Redundancy)
Releases and deployments are guarded by **multiple independent checks**:
1. **Tag name checks**
- Release vs prerelease is decided by presence of `-rc`
2. **Branch ancestry checks**
- Final releases must be reachable from `main`
- RC releases must be reachable from `release-candidate`
3. **Strict MkDocs CI**
- Controlled by `MKDOCS_STRICT` (defaults to true)
4. **Environment guards**
- Releases are skipped when running under `act`
- Deployments only run on GitHub, never on Gitea or act
This redundancy is intentional.
## Environment Variables
These variables control CI and release behavior:
| Variable | Purpose |
|-------------------|-------------------------------------|
| `MKDOCS_STRICT` | Enable/disable strict MkDocs builds |
| `ENABLE_RELEASE` | Master switch for releases |
| `ENABLE_DEPLOY` | Master switch for deployments |
| `CI_PROVIDER` | `github`, `gitea`, or `act` |
| Main Pages Deploy | Push to `main` |
Defaults are defined in repository settings.
## Common Mistakes (and What Happens)
- Tagging `v2026.1.0` on a non-`main` commit
→ Release workflow runs but fails early with a clear error
- Tagging `v2026.1.0-rc.1` on `main`
→ Prerelease workflow runs but branch check fails
- Running workflows locally with `act`
→ Builds run, but no release or deploy occurs
## Changing This Process
If you change:
- Tag patterns
- Branch names
- Workflow triggers
- CI guard logic
Update this document **and** the workflows together.
This process is designed to be boring, explicit, and safe.

BIN
assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -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)

View File

@@ -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 = `
<span class="quote-icon" aria-hidden="true"></span>
`;
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 `
<blockquote>
${innerHtml}
<p><a href="${safeHref}">${safeText}</a></p>
</blockquote>
`.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("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
function escapeHtmlAttr(s) {
return escapeHtml(s);
}
})();

View File

@@ -1,5 +0,0 @@
---
title: Chicago Metropolitan Baseball Association
subtitle: Constitution and By-Laws
date: 2024-06-06
---

3
mkdocs/index.md Normal file
View File

@@ -0,0 +1,3 @@
# CMBA Rulebooks
Use the navigation to view the documents.

48
mkdocs/mkdocs.yml Normal file
View File

@@ -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

2
mkdocs/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
mkdocs-enumerate-headings-plugin==0.6
mkdocs-material==9.7

10
mkdocs/styles/extra.css Normal file
View File

@@ -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";
}

View File

@@ -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