Skip to content

Commit 8c49e83

Browse files
committed
Refactor code block styling to enhance language identity for Jac and Python
1 parent 7b93399 commit 8c49e83

4 files changed

Lines changed: 188 additions & 108 deletions

File tree

docs/extra.css

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -136,87 +136,18 @@
136136
border: 1px solid var(--border);
137137
}
138138

139-
/* ── Language identity: distinguish Jac from Python code blocks ──
140-
Works without any markup changes: `pygments_lang_class: true` stamps each
141-
rendered fence with `language-jac` / `language-python` on the .highlight
142-
wrapper. Jac gets the Jaseci-orange identity; Python stays neutral. */
143-
144-
/* Static (non-interactive) blocks: accent left border + corner badge */
145-
.md-typeset .highlight.language-jac,
146-
.md-typeset .highlight.language-python {
147-
position: relative;
148-
border-left: 3px solid var(--border);
149-
border-radius: 4px;
150-
}
151-
.md-typeset .highlight.language-jac {
152-
border-left-color: var(--primary);
153-
}
154-
155-
.md-typeset .highlight.language-jac::before,
156-
.md-typeset .highlight.language-python::before {
157-
position: absolute;
158-
top: 0;
159-
right: 0;
160-
z-index: 1;
161-
font: 600 0.62rem/1 var(--font-family-code);
162-
letter-spacing: 0.08em;
163-
padding: 0.3rem 0.5rem;
164-
border-bottom-left-radius: 4px;
165-
pointer-events: none;
166-
}
167-
.md-typeset .highlight.language-jac::before {
168-
content: "JAC";
169-
color: var(--primary);
170-
background: var(--md-accent-bg-color);
171-
}
172-
.md-typeset .highlight.language-python::before {
173-
content: "PY";
174-
color: var(--text-muted);
175-
background: rgba(255, 255, 255, 0.04);
176-
}
177-
178-
/* Make room for the badge: nudge Material's copy button left of it */
179-
.md-typeset .highlight.language-jac > .md-clipboard,
180-
.md-typeset .highlight.language-python > .md-clipboard {
181-
right: 2.6rem;
182-
}
183-
184-
/* Interactive blocks already carry a toolbar + Monaco editor, so they don't
185-
need the overlay badge. Identity is marked on the wrapper border (see the
186-
.code-block component rule below); here we just suppress the inner static
187-
treatment to avoid doubling up. */
188-
.code-block .highlight.language-jac,
189-
.code-block .highlight.language-python {
190-
border-left: none;
191-
border-radius: 0;
192-
}
193-
.code-block .highlight.language-jac::before,
194-
.code-block .highlight.language-python::before {
195-
display: none;
196-
}
197-
.code-block .highlight.language-jac > .md-clipboard,
198-
.code-block .highlight.language-python > .md-clipboard {
199-
right: 0.5rem;
200-
}
201-
202139
/* ── Jac Code Block Component ── */
203140
.code-block {
204141
margin: 1.2em 0;
205142
padding: 0;
206143
background: #0d1117;
207144
color: #c9d1d9;
208145
border: 1px solid #21262d;
209-
border-left: 3px solid var(--border);
210146
border-radius: 6px;
211147
overflow: hidden;
212148
transition: border-color 0.2s ease, box-shadow 0.2s ease;
213149
}
214150

215-
/* Jac identity on interactive blocks (Python interactive blocks stay neutral) */
216-
.code-block[data-lang="jac"] {
217-
border-left-color: var(--primary);
218-
}
219-
220151
.code-block.jcb-running {
221152
border-color: #f0883e;
222153
}

main.jac

Lines changed: 142 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,77 @@ def:priv extract_peek_markdown(body: str) -> str {
212212
}
213213

214214

215+
# Exact prefix python-markdown + codehilite (Pygments) emits for every
216+
# highlighted block. We splice the source language back onto each one — see
217+
# tag_code_languages.
218+
glob HL_MARKER = "<div class=\"highlight\"><pre><span></span><code>";
219+
220+
221+
def:priv parse_info_lang(info: str) -> str {
222+
# Normalize a fence info string into a bare language name:
223+
# "jac" -> "jac", "{.jac}" -> "jac", "python title=x" -> "python".
224+
parts = info.strip().split();
225+
if not parts {
226+
return "";
227+
}
228+
token = parts[0].strip("{}").lstrip(".");
229+
if not token or ("=" in token) {
230+
return "";
231+
}
232+
return token;
233+
}
234+
235+
236+
def:priv extract_fence_langs(body: str) -> list {
237+
# Languages of each fenced code block, in source order. codehilite emits
238+
# one HL_MARKER per fence in the same order, so the lists align 1:1.
239+
langs = [];
240+
in_fence = False;
241+
fence = "";
242+
for raw in body.split("\n") {
243+
line = raw.strip();
244+
if not in_fence {
245+
if line.startswith("```") or line.startswith("~~~") {
246+
in_fence = True;
247+
fence = line[:3];
248+
langs.append(parse_info_lang(line[3:]));
249+
}
250+
} elif line.startswith(fence) {
251+
in_fence = False;
252+
fence = "";
253+
}
254+
}
255+
return langs;
256+
}
257+
258+
259+
def:priv tag_code_languages(html: str, body: str) -> str {
260+
# codehilite drops the fence language from its output, leaving jac and
261+
# python blocks indistinguishable. Splice `language-<lang>` back onto the
262+
# wrapper div and inner <code> so the frontend can label + style each.
263+
langs = extract_fence_langs(body);
264+
parts = html.split(HL_MARKER);
265+
if len(parts) <= 1 {
266+
return html;
267+
}
268+
out = parts[0];
269+
for i in range(1, len(parts)) {
270+
lang = "";
271+
if (i - 1) < len(langs) {
272+
lang = str(langs[i - 1]);
273+
}
274+
if lang {
275+
out = out + f"<div class=\"highlight language-{lang}\"><pre><span></span><code class=\"language-{lang}\">" + parts[i];
276+
} else {
277+
out = out + HL_MARKER + parts[i];
278+
}
279+
}
280+
return out;
281+
}
282+
283+
215284
def:priv render_markdown(body: str) -> str {
216-
return md.markdown(
285+
html = md.markdown(
217286
body,
218287
extensions=[
219288
"fenced_code",
@@ -230,6 +299,7 @@ def:priv render_markdown(body: str) -> str {
230299
"toc": {"permalink": True, "permalink_class": "headerlink"},
231300
},
232301
);
302+
return tag_code_languages(html, body);
233303
}
234304

235305

@@ -1839,6 +1909,76 @@ def:priv find_open_edit_prs(token: str, slug: str) -> list {
18391909
}
18401910

18411911

1912+
def:priv open_submission_prs(token: str) -> list {
1913+
# ALL open PRs that ADD a NEW post (a docs/blog/posts/*.md that isn't already
1914+
# a live post), found by the file the PR adds rather than a body marker — so
1915+
# it picks up submissions opened through the portal AND directly on GitHub.
1916+
# Edits to an existing post add no new file, so they don't match here; they
1917+
# are surfaced separately by open_edit_prs_by_slug. Mirrors that function's
1918+
# file-based detection so the review queue and the open-edits list stay
1919+
# consistent about direct-GitHub PRs.
1920+
out: list = [];
1921+
path_to_slug = post_path_to_slug();
1922+
try {
1923+
prs = requests.get(
1924+
f"{GH_API}/repos/{gh_repo()}/pulls",
1925+
headers=gh_headers(token),
1926+
params={"state": "open", "per_page": 100, "sort": "created", "direction": "desc"},
1927+
timeout=30,
1928+
).json();
1929+
} except Exception {
1930+
return out;
1931+
}
1932+
if not isinstance(prs, list) {
1933+
return out;
1934+
}
1935+
for pr in prs {
1936+
n = int(pr.get("number", 0));
1937+
try {
1938+
files = requests.get(
1939+
f"{GH_API}/repos/{gh_repo()}/pulls/{n}/files",
1940+
headers=gh_headers(token), params={"per_page": 100}, timeout=30,
1941+
).json();
1942+
} except Exception {
1943+
continue;
1944+
}
1945+
if not isinstance(files, list) {
1946+
continue;
1947+
}
1948+
adds_new_post = False;
1949+
for f in files {
1950+
fn = str(f.get("filename", ""));
1951+
status = str(f.get("status", ""));
1952+
name = fn.split("/")[-1];
1953+
if (
1954+
fn.startswith(f"{POSTS_DIR}/")
1955+
and fn.endswith(".md")
1956+
and not name.startswith("_")
1957+
and fn not in path_to_slug
1958+
and status == "added"
1959+
) {
1960+
adds_new_post = True;
1961+
break;
1962+
}
1963+
}
1964+
if not adds_new_post {
1965+
continue;
1966+
}
1967+
user = pr.get("user");
1968+
author = str(user.get("login", "")) if isinstance(user, dict) else "";
1969+
out.append({
1970+
"title": str(pr.get("title", "")),
1971+
"slug": "",
1972+
"author": author,
1973+
"pr_number": n,
1974+
"pr_url": str(pr.get("html_url", "")),
1975+
"created_at": str(pr.get("created_at", "")),
1976+
});
1977+
}
1978+
return out;
1979+
}
1980+
1981+
18421982
def:priv build_edited_markdown(
18431983
original_content: str, title: str, categories: list, description: str, body: str
18441984
) -> str {
@@ -2363,35 +2503,7 @@ walker:priv ListSubmissions {
23632503
report {"ok": False, "error": "reviewer access required"};
23642504
return;
23652505
}
2366-
try {
2367-
q = f'repo:{gh_repo()} type:pr state:open "{SUBMISSION_TAG}" in:body';
2368-
r = requests.get(
2369-
f"{GH_API}/search/issues",
2370-
headers=gh_headers(gh_token),
2371-
params={"q": q, "per_page": 50, "sort": "created", "order": "desc"},
2372-
timeout=30,
2373-
);
2374-
data = r.json();
2375-
} except Exception as e {
2376-
report {"ok": False, "error": f"could not load queue: {e}"};
2377-
return;
2378-
}
2379-
items = data.get("items", []) if isinstance(data, dict) else [];
2380-
out = [];
2381-
for it in items {
2382-
body = str(it.get("body", "") or "");
2383-
m = re.search(r"slug=([a-z0-9-]+)", body);
2384-
bm = re.search(r"by=([A-Za-z0-9-]+)", body);
2385-
out.append({
2386-
"title": str(it.get("title", "")),
2387-
"slug": m.group(1) if m else "",
2388-
"author": bm.group(1) if bm else "",
2389-
"pr_number": it.get("number"),
2390-
"pr_url": str(it.get("html_url", "")),
2391-
"created_at": str(it.get("created_at", "")),
2392-
});
2393-
}
2394-
report {"ok": True, "submissions": out};
2506+
report {"ok": True, "submissions": open_submission_prs(gh_token)};
23952507
}
23962508
}
23972509

pages/blog/posts/[slug].jac

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,19 @@ def install_load_helper() -> None {
9797

9898

9999
async def ensure_codemirror() -> None {
100-
if window.CodeMirror {
101-
return;
102-
}
103100
install_load_helper();
104-
await window.__loadCss("https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css");
105-
await window.__loadCss("https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/material-darker.min.css");
106-
await window.__loadScript("https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js");
107-
await window.__loadScript("https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js");
101+
if not window.CodeMirror {
102+
await window.__loadCss("https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css");
103+
await window.__loadCss("https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/material-darker.min.css");
104+
await window.__loadScript("https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js");
105+
}
106+
# Every code block is rendered with the Jac mode, which is defined inline
107+
# below (no network, no load race). Jac and Python share enough syntax —
108+
# keywords, strings, numbers, `#` comments — that Python reads as highlighted
109+
# too; this is the original behavior and is rock-solid. The CDN Python mode
110+
# was racy (it could no-op before core was ready, leaving the editor blank),
111+
# so we deliberately don't depend on it. Language identity is conveyed by the
112+
# coloured header + label instead.
108113
define_jac_mode();
109114
}
110115

@@ -196,7 +201,13 @@ async def enhance_code_blocks(container: any) -> None {
196201
}
197202

198203
shell = document.createElement("div");
204+
# Tag the shell with the language so CSS can give Jac vs Python a
205+
# distinct header accent (CodeMirror strips the original language-*
206+
# class, so we re-stamp it here).
199207
shell.className = "runnable-jac code-static";
208+
if lang {
209+
shell.className = f"runnable-jac code-static lang-{lang.lower()}";
210+
}
200211

201212
header = document.createElement("div");
202213
header.className = "runnable-header";
@@ -228,7 +239,8 @@ async def enhance_code_blocks(container: any) -> None {
228239
} except Exception {
229240
_ignore = None;
230241
}
231-
cm_mode = "jac" if (lang.lower() == "jac" or not lang) and has_jac else ("python" if lang.lower() in ["python", "py", "jac", ""] else lang);
242+
# All blocks render through the always-available inline Jac mode.
243+
cm_mode = "jac" if has_jac else "null";
232244
cm = window.CodeMirror(host, {
233245
"value": source,
234246
"mode": cm_mode,
@@ -507,7 +519,7 @@ async def mount_runnable_blocks(container: any) -> None {
507519
} except Exception {
508520
_ignore = None;
509521
}
510-
cm_mode = "jac" if (lang == "jac" and has_jac) else "python";
522+
cm_mode = "jac" if has_jac else "null";
511523
cm = window.CodeMirror(host, {
512524
"value": source,
513525
"mode": cm_mode,

styles/prose.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,31 @@
446446
font-weight: 600;
447447
color: var(--text-muted);
448448
}
449+
/* Language identity: colour the code-block header so Jac and Python read as
450+
distinct at a glance. The post page renders each block as a CodeMirror shell
451+
(.runnable-jac) tagged with lang-<lang> in [slug].jac. Jac → Jaseci orange,
452+
Python → Python-logo blue; other languages keep the neutral default. */
453+
.prose .runnable-jac.lang-jac {
454+
border-left: 3px solid #ff6b35;
455+
}
456+
.prose .runnable-jac.lang-jac .runnable-header {
457+
background: color-mix(in srgb, #ff6b35 14%, var(--code-bg));
458+
border-bottom-color: color-mix(in srgb, #ff6b35 34%, var(--code-border));
459+
}
460+
.prose .runnable-jac.lang-jac .code-lang {
461+
color: #ff6b35;
462+
}
463+
464+
.prose .runnable-jac.lang-python {
465+
border-left: 3px solid #3776ab;
466+
}
467+
.prose .runnable-jac.lang-python .runnable-header {
468+
background: color-mix(in srgb, #3776ab 14%, var(--code-bg));
469+
border-bottom-color: color-mix(in srgb, #3776ab 34%, var(--code-border));
470+
}
471+
.prose .runnable-jac.lang-python .code-lang {
472+
color: #3776ab;
473+
}
449474
.code-copy {
450475
background: transparent;
451476
border: 1px solid var(--code-border);

0 commit comments

Comments
 (0)