|
2 | 2 | All the drafts are built by the build-specs workflow itself. |
3 | 3 | This handles the rest of the work: |
4 | 4 |
|
5 | | -* creates a root page that just redirects to the draft server root listing. |
| 5 | +* creates an index page listing all specs |
6 | 6 | * creates symlinks for unlevelled urls, linking to the appropriate levelled folder |
7 | | -* builds timestamps.json, which provides a bunch of metadata about the specs which is consumed by some W3C tooling. |
| 7 | +* builds timestamps.json, which provides metadata about the specs |
8 | 8 | """ |
9 | 9 |
|
| 10 | +import glob |
10 | 11 | import json |
11 | 12 | import os |
12 | 13 | import os.path |
13 | 14 | import re |
14 | | -import sys |
15 | 15 | import subprocess |
16 | 16 | from collections import defaultdict |
| 17 | +from datetime import datetime, timezone |
17 | 18 |
|
18 | 19 | import bikeshed |
19 | 20 | from html.parser import HTMLParser |
@@ -122,6 +123,21 @@ def create_symlink(shortname, spec_folder): |
122 | 123 | pass |
123 | 124 |
|
124 | 125 |
|
| 126 | +def format_timestamp(ts): |
| 127 | + """Format a Unix timestamp as a human-readable date string.""" |
| 128 | + dt = datetime.fromtimestamp(ts, tz=timezone.utc) |
| 129 | + return dt.strftime("%Y-%m-%d") |
| 130 | + |
| 131 | + |
| 132 | +def escape_html(text): |
| 133 | + """Escape HTML special characters.""" |
| 134 | + return (text |
| 135 | + .replace("&", "&") |
| 136 | + .replace("<", "<") |
| 137 | + .replace(">", ">") |
| 138 | + .replace('"', """)) |
| 139 | + |
| 140 | + |
125 | 141 | CURRENT_WORK_EXCEPTIONS = { |
126 | 142 | "css-conditional": 5, |
127 | 143 | "css-easing": 2, |
@@ -159,6 +175,9 @@ def create_symlink(shortname, spec_folder): |
159 | 175 |
|
160 | 176 | metadata["dir"] = entry.name |
161 | 177 | metadata["currentWork"] = False |
| 178 | + issues_files = sorted(f for f in glob.glob(os.path.join(entry.path, "issues-*.html")) |
| 179 | + if not f.endswith(".bsi.html")) |
| 180 | + metadata["issues"] = [os.path.basename(f) for f in issues_files] |
162 | 181 | specgroups[metadata["shortname"]].append(metadata) |
163 | 182 |
|
164 | 183 | # Reorder the specs with common shortname based on their level (or year, for |
@@ -195,13 +214,120 @@ def create_symlink(shortname, spec_folder): |
195 | 214 | with open('./timestamps.json', 'w') as f: |
196 | 215 | json.dump(timestamps, f, indent = 2, sort_keys=True) |
197 | 216 |
|
| 217 | +# Build the index page |
| 218 | +rows = [] |
| 219 | +for shortname in sorted(specgroups.keys()): |
| 220 | + specgroup = specgroups[shortname] |
| 221 | + is_group = len(specgroup) > 1 |
| 222 | + |
| 223 | + if is_group: |
| 224 | + rows.append( |
| 225 | + f' <tr class="group-header">\n' |
| 226 | + f' <td colspan="2">{escape_html(shortname)}</td>\n' |
| 227 | + f' </tr>' |
| 228 | + ) |
| 229 | + |
| 230 | + for spec in specgroup: |
| 231 | + title = escape_html(spec["title"] or spec["dir"]) |
| 232 | + current_label = ' <span class="current-work">(Current Work)</span>' if spec["currentWork"] else "" |
| 233 | + dir_name = spec["dir"] |
| 234 | + |
| 235 | + ts = timestamps.get(dir_name) |
| 236 | + date_str = format_timestamp(ts) if ts else "" |
| 237 | + |
| 238 | + issues_html = "" |
| 239 | + if spec["issues"]: |
| 240 | + links = [] |
| 241 | + for fname in spec["issues"]: |
| 242 | + label = fname.replace("issues-", "").replace(".html", "") |
| 243 | + links.append(f'<a href="./{dir_name}/{fname}">{label}</a>') |
| 244 | + issues_html = '<br><span class="issues">DoC: ' + " \u00b7 ".join(links) + '</span>' |
| 245 | + |
| 246 | + indent_class = ' class="grouped-spec"' if is_group else "" |
| 247 | + rows.append( |
| 248 | + f' <tr{indent_class}>\n' |
| 249 | + f' <td><a href="./{dir_name}/">{title}</a>{current_label}{issues_html}</td>\n' |
| 250 | + f' <td>{date_str}</td>\n' |
| 251 | + f' </tr>' |
| 252 | + ) |
| 253 | + |
| 254 | +rows_html = "\n".join(rows) |
| 255 | + |
198 | 256 | with open("./index.html", mode='w', encoding="UTF-8") as f: |
199 | | - f.write(""" |
| 257 | + f.write(f"""\ |
200 | 258 | <!doctype html> |
201 | | -<meta charset=utf-8> |
202 | | -<title>Redirecting to the Drafts listing...</title> |
203 | | -<meta http-equiv=Refresh content="0; url='https://drafts.csswg.org'"> |
204 | | -<script> |
205 | | -window.location.href = "https://drafts.csswg.org"; |
206 | | -</script> |
| 259 | +<html lang="en"> |
| 260 | +<head> |
| 261 | + <meta charset="utf-8"> |
| 262 | + <title>CSS Working Group Editor Drafts</title> |
| 263 | + <style> |
| 264 | + body {{ |
| 265 | + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; |
| 266 | + max-width: 900px; |
| 267 | + margin: 2em auto; |
| 268 | + padding: 0 1em; |
| 269 | + color: #333; |
| 270 | + }} |
| 271 | + h1 {{ |
| 272 | + border-bottom: 1px solid #ccc; |
| 273 | + padding-bottom: 0.3em; |
| 274 | + }} |
| 275 | + table {{ |
| 276 | + width: 100%; |
| 277 | + border-collapse: collapse; |
| 278 | + margin-top: 1em; |
| 279 | + }} |
| 280 | + th, td {{ |
| 281 | + text-align: left; |
| 282 | + padding: 0.5em 0.75em; |
| 283 | + border-bottom: 1px solid #eee; |
| 284 | + }} |
| 285 | + th {{ |
| 286 | + border-bottom: 2px solid #ccc; |
| 287 | + font-weight: 600; |
| 288 | + }} |
| 289 | + td:last-child {{ |
| 290 | + white-space: nowrap; |
| 291 | + color: #666; |
| 292 | + }} |
| 293 | + a {{ |
| 294 | + color: #0366d6; |
| 295 | + text-decoration: none; |
| 296 | + }} |
| 297 | + a:hover {{ |
| 298 | + text-decoration: underline; |
| 299 | + }} |
| 300 | + .current-work {{ |
| 301 | + color: #080; |
| 302 | + font-size: 0.9em; |
| 303 | + }} |
| 304 | + .group-header td {{ |
| 305 | + font-weight: 600; |
| 306 | + padding-top: 1em; |
| 307 | + border-bottom: 1px solid #ccc; |
| 308 | + }} |
| 309 | + .grouped-spec td:first-child {{ |
| 310 | + padding-left: 2em; |
| 311 | + }} |
| 312 | + .issues {{ |
| 313 | + font-size: 0.85em; |
| 314 | + color: #666; |
| 315 | + }} |
| 316 | + </style> |
| 317 | +</head> |
| 318 | +<body> |
| 319 | + <h1>CSS Working Group Editor Drafts</h1> |
| 320 | + <table> |
| 321 | + <thead> |
| 322 | + <tr> |
| 323 | + <th>Specification</th> |
| 324 | + <th>Last Update</th> |
| 325 | + </tr> |
| 326 | + </thead> |
| 327 | + <tbody> |
| 328 | +{rows_html} |
| 329 | + </tbody> |
| 330 | + </table> |
| 331 | +</body> |
| 332 | +</html> |
207 | 333 | """) |
0 commit comments