5353 git push
5454
5555 - id : build_page
56- name : Build index.html with custom template + grid sidebar (dynamic URLs)
56+ name : Build index.html with sidebar TOC from README headings
5757 env :
5858 REPO_HTML_TITLE : ${{ github.repository }}
5959 OWNER : ${{ github.repository_owner }}
@@ -65,15 +65,13 @@ jobs:
6565 # === Compute dynamic URLs ===
6666 REPO_NAME="${REPO_FULL#*/}"
6767 REPO_URL="https://github.com/${REPO_FULL}"
68-
69- # Pages URL: special-case owner.github.io
7068 if [ "$REPO_NAME" = "${OWNER}.github.io" ]; then
7169 PAGES_URL="https://${OWNER}.github.io/"
7270 else
7371 PAGES_URL="https://${OWNER}.github.io/${REPO_NAME}/"
7472 fi
7573
76- # Minimal Pandoc template (no default CSS)
74+ # Minimal Pandoc template
7775 cat > pandoc_template.html <<'TPL'
7876 <!doctype html>
7977 <html>
@@ -97,47 +95,41 @@ jobs:
9795 if [ -f index.html ]; then
9896 echo "Keeping existing index.html (not regenerating)."
9997 elif [ -f README.md ]; then
100- echo "Generating index.html from README.md..."
98+ echo "Generating index.html from README.md with a TOC ..."
10199 pandoc README.md -f markdown -t html -s \
102100 --template=pandoc_template.html \
103101 -o index.html \
104- --metadata title="$REPO_HTML_TITLE"
102+ --metadata title="$REPO_HTML_TITLE" \
103+ --toc --toc-depth=3 -V toc-title=""
105104 GENERATED=1
106105 else
107106 echo "No index.html or README.md found; writing minimal page."
108107 printf '%s\n' '<!doctype html><html><head><meta charset="utf-8"><title>Site</title></head><body>' '<h1>Site</h1>' '<p>No README.md found. Add one and push to regenerate this page.</p>' '</body></html>' > index.html
109108 GENERATED=1
110109 fi
111110
112- # Styles: grid layout + "full-height" painted sidebar column
111+ # Styles ( grid layout + sidebar + nice TOC)
113112 CSS='<style>
114113 html, body { width:100%; max-width:none; margin:0; padding:0; font-family: Arial, Helvetica, sans-serif; color:#222; }
115114 h1, h2, h3, h4, h5, h6 { font-family: Arial, Helvetica, sans-serif; }
116115 pre code { display:block; padding:.75em; background:#f6f8fa; border-radius:6px; font-size:90%; overflow:auto; }
117116 code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
118117
119- /* Page uses CSS Grid; paint the left column so the sidebar appears full-height */
120- .page {
121- display: grid;
122- grid-template-columns: 220px 1fr;
123- min-height: 100vh;
124- background: linear-gradient(to right, #f4f4f4 0, #f4f4f4 220px, transparent 220px);
125- }
126-
127- .sidebar {
128- padding: 20px;
129- position: sticky;
130- top: 0;
131- height: 100vh; /* sidebar content scrolls independently if long */
132- overflow: auto;
133- }
134- .sidebar a { display:block; margin-bottom:10px; color:#0366d6; text-decoration:none; font-weight:600; }
135- .sidebar a:hover { text-decoration:underline; }
136-
137- .content { width: 100%; }
138- .container { width:100%; max-width: 1400px; margin: 0 auto; padding: 48px; }
118+ .page { display:grid; grid-template-columns: 240px 1fr; min-height:100vh;
119+ background: linear-gradient(to right, #f4f4f4 0, #f4f4f4 240px, transparent 240px); }
120+ .sidebar { padding:20px; position:sticky; top:0; height:100vh; overflow:auto; }
121+ .sidebar a.home { display:block; margin-bottom:14px; color:#0366d6; text-decoration:none; font-weight:700; }
122+ .sidebar a.home:hover { text-decoration:underline; }
123+ .sidebar nav.toc { font-size:14px; line-height:1.4; }
124+ .sidebar nav.toc ul { list-style:none; padding-left:0; margin:0; }
125+ .sidebar nav.toc li { margin:4px 0; }
126+ .sidebar nav.toc a { color:#24292f; text-decoration:none; }
127+ .sidebar nav.toc a:hover { text-decoration:underline; }
128+ .sidebar nav.toc ul ul { padding-left:14px; border-left:2px solid #e5e7eb; margin-left:6px; }
129+
130+ .content { width:100%; }
131+ .container { width:100%; max-width: 1400px; margin:0 auto; padding:48px; }
139132
140- /* Extras block spacing */
141133 .extras { margin-top: 48px; }
142134 .extras h2, .extras h3 { margin-top: 1.2em; }
143135
@@ -148,44 +140,60 @@ jobs:
148140 }
149141 </style>'
150142
151- # Inject CSS into <head> (or wrap if missing)
143+ # Inject CSS into <head>
152144 if grep -qi '</head>' index.html; then
153145 awk -v css="$CSS" 'BEGIN{IGNORECASE=1} { if (!done && match(tolower($0), /<\/head>/)) { sub(/<\/head>/, css"</head>"); done=1 } print }' index.html > index.html.tmp
154146 mv index.html.tmp index.html
155147 else
156148 TMP=$(mktemp)
157149 printf '%s\n' '<!doctype html><html><head><meta charset="utf-8">' "$CSS" '</head><body>' > "$TMP"
158- # Keep body content (if any) after opening <body>
159150 sed '1,/<body[^>]*>/d' index.html >> "$TMP" || true
160151 printf '%s\n' '</body></html>' >> "$TMP"
161152 mv "$TMP" index.html
162153 fi
163154
164- # === Use dynamic Pages URL for the sidebar "Home" link ===
155+ # === Sidebar with Home + placeholder for TOC links ===
165156 HOMELINK="https://democracy-lab.github.io/"
166-
167- # Wrap body with grid + sidebar + content container (idempotent)
168157 if grep -qi '<div class="page"' index.html; then
169158 echo "Wrapper already present; skipping."
170159 else
171160 awk -v home="$HOMELINK" 'BEGIN{IGNORECASE=1}
172161 {
173162 if (!done && match(tolower($0), /<body[^>]*>/)) {
174- sub(/<body[^>]*>/, "&\n<div class=\"page\"><aside class=\"sidebar\"><a href=\"" home "\" aria-label=\"Go to home\">← Home</a></aside>\n<main class=\"content\"><div class=\"container\">");
163+ sub(/<body[^>]*>/,
164+ "&\n<div class=\"page\"><aside class=\"sidebar\">" \
165+ "<a class=\"home\" href=\"" home "\" aria-label=\"Go to home\">← Home</a>" \
166+ "<nav class=\"toc\"><!--SIDEBAR_LINKS--></nav>" \
167+ "</aside>\n<main class=\"content\"><div class=\"container\">");
175168 done=1
176169 }
177170 print
178171 }' index.html > index.html.tmp && mv index.html.tmp index.html
179172
180- # Close container/main/page before </body>
181173 awk 'BEGIN{IGNORECASE=1} { sub(/<\/body>/, "</div></main></div></body>"); print }' index.html > index.html.tmp && mv index.html.tmp index.html
182174 fi
183175
184- # === Replace placeholders anywhere in the page ===
176+ # === Extract Pandoc TOC and move it into the sidebar ===
177+ # Grab inner HTML of <nav id="TOC"> ... </nav>
178+ TOC_INNER=$(perl -0777 -ne 'if (m|<nav\s+id="TOC"[^>]*>(.*?)</nav>|si){print $1}' index.html || true)
179+
180+ # If a TOC exists, clean it and inject; then remove original TOC
181+ if [ -n "$TOC_INNER" ]; then
182+ # Remove any <h2>Contents</h2> title inside the nav
183+ TOC_CLEAN=$(printf "%s" "$TOC_INNER" | perl -0777 -pe 's|<h[1-6][^>]*>.*?</h[1-6]>||si')
184+ # Inject into sidebar placeholder
185+ awk -v block="$TOC_CLEAN" 'BEGIN{IGNORECASE=1}
186+ { sub(/<!--SIDEBAR_LINKS-->/, block); print }' index.html > index.html.tmp && mv index.html.tmp index.html
187+ # Remove original nav#TOC from main content
188+ perl -0777 -pe 's|<nav\s+id="TOC"[^>]*>.*?</nav>||si' -i index.html
189+ else
190+ echo "No TOC found; leaving sidebar placeholder empty."
191+ fi
192+
193+ # Replace any placeholders, just in case
185194 esc() { printf '%s' "$1" | sed 's/[&/\\]/\\&/g'; }
186195 REPO_URL_ESC=$(esc "$REPO_URL")
187196 PAGES_URL_ESC=$(esc "$PAGES_URL")
188-
189197 sed -i \
190198 -e "s/GITHUB_REPO_URL/${REPO_URL_ESC}/g" \
191199 -e "s/GITHUB_REPO/${REPO_URL_ESC}/g" \
@@ -212,7 +220,6 @@ jobs:
212220 LAST_UPDATE=$(git log -1 --format=%cI || date -u +%Y-%m-%dT%H:%M:%SZ)
213221 YEAR=$(date -u -d "$LAST_UPDATE" +%Y 2>/dev/null || date -u +%Y)
214222
215- # Gather human contributors (optional enhancement)
216223 logins=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \
217224 "https://api.github.com/repos/$REPO/contributors?per_page=100&anon=false" \
218225 | jq -r '.[] | select((.type // "") != "Bot") | select(.login != "ghost") | .login' | sort -fu)
@@ -246,7 +253,6 @@ jobs:
246253 note = {Last updated: $LAST_UPDATE}
247254 }"
248255
249- # Build the extras HTML block (includes both URLs)
250256 EXTRAS=$(cat <<HTML
251257 <section class="extras">
252258 <hr>
@@ -268,7 +274,6 @@ jobs:
268274 HTML
269275 )
270276
271- # Inject JUST BEFORE the wrapper closes: </div></main></div></body>
272277 awk -v block="$EXTRAS" 'BEGIN{IGNORECASE=1}
273278 {
274279 line=$0
@@ -298,4 +303,3 @@ jobs:
298303 run : |
299304 echo "### Deployed Pages URL" >> "$GITHUB_STEP_SUMMARY"
300305 echo "${{ steps.deployment.outputs.page_url }}" >> "$GITHUB_STEP_SUMMARY"
301-
0 commit comments