diff --git a/card.js b/card.js index e7d8192..0daf354 100644 --- a/card.js +++ b/card.js @@ -69,16 +69,28 @@ return a; } function resolve(data, text){ for (var i = 0; i < data.entities.length; i++) if (data.entities[i].name === text) return data.entities[i].id; return null; } - // Two span-level marks reach a card from the model's fixed shape. Inline code becomes + // Three span-level marks reach a card from the model's fixed shape. Inline code becomes // code.mono, and bold becomes b, because a list such as a surface's What it shows writes every - // item as a bold name and a sentence. Code is split out first, so asterisks inside backticks - // stay characters. A URL inside a sentence becomes a link. Appended as nodes, never as - // innerHTML: these strings come out of the data block, and the day a name contains a "<" an - // innerHTML assignment would start parsing it as markup. - function inline(el, text){ + // item as a bold name and a sentence. A Markdown link becomes what it points at: an external + // link for a web address, and otherwise the entity its text names through `ref`, since a + // relative path such as phases/shape.md is a file in the model, which has no address here, and + // the node that file became does. Code is split out first, so asterisks and brackets inside + // backticks stay characters. A bare URL inside a sentence becomes a link. Appended as nodes, + // never as innerHTML: these strings come out of the data block, and the day a name contains a + // "<" an innerHTML assignment would start parsing it as markup. + var MD_LINK = /\[([^\]]+)\]\(([^)\s]+)\)/; + function inline(el, text, ref){ String(text).split(/`([^`]+)`/).forEach(function(part, i){ if (!part) return; if (i % 2) { el.appendChild(h("code", part, "mono")); return; } + var m; + while ((m = MD_LINK.exec(part))) { + if (m.index) inline(el, part.slice(0, m.index), ref); + if (/^https?:\/\//.test(m[2])) { var x = extLink(m[2]); x.textContent = m[1]; el.appendChild(x); } + else el.appendChild(ref ? ref(m[1]) : document.createTextNode(m[1])); + part = part.slice(m.index + m[0].length); + } + if (!part) return; part.split(/\*\*([^*]+)\*\*/).forEach(function(run, k){ if (!run) return; var into = el; @@ -91,7 +103,7 @@ }); return el; } - function para(text, cls){ return inline(h("p", null, cls), text); } + function para(text, cls, ref){ return inline(h("p", null, cls), text, ref); } function render(e, bodyEl, footEl, opts){ var data = opts.data, lang = opts.lang === "de" ? "de" : "en", link = opts.link; @@ -159,16 +171,30 @@ }); tbl.appendChild(tb); bodyEl.appendChild(tbl); }); - // A block whose lines each open with "- " is a list in the file, and is drawn as one, - // with the marker stripped and a hanging indent. - if (s.text) s.text.split(/\n\n+/).forEach(function(par){ - if (/^-\s/.test(par)) { + // A block is one of four things in the file, and is drawn as that. A line opening with + // "###" is a subheading inside the section — a phase's Activities splits into Code and + // Prose that way — and the rest of its block, if any, is read again as a block. A block + // whose lines each open with "- " is a list, and one whose lines open with a number and a + // period is a numbered list; either is drawn with the marker stripped and a hanging + // indent, a continuation line joined to its item. Anything else is a paragraph. + if (s.text) s.text.split(/\n\n+/).forEach(function block(par){ + var sub = /^#{3,6}\s+(.+)(?:\n([\s\S]*))?$/.exec(par); + if (sub) { + bodyEl.appendChild(inline(h("h5", null, "sub"), sub[1], ref)); + if (sub[2] && sub[2].trim()) block(sub[2]); + } else if (/^-\s/.test(par)) { var ul = h("ul", null, "prose"); par.split(/\n(?=-\s)/).forEach(function(item){ - ul.appendChild(inline(h("li"), item.replace(/^-\s+/, "").replace(/\n\s*/g, " "))); + ul.appendChild(inline(h("li"), item.replace(/^-\s+/, "").replace(/\n\s*/g, " "), ref)); }); bodyEl.appendChild(ul); - } else bodyEl.appendChild(para(par.replace(/\n/g, " "))); + } else if (/^\d+\.\s/.test(par)) { + var ol = h("ol", null, "prose"); + par.split(/\n(?=\d+\.\s)/).forEach(function(item){ + ol.appendChild(inline(h("li"), item.replace(/^\d+\.\s+/, "").replace(/\n\s*/g, " "), ref)); + }); + bodyEl.appendChild(ol); + } else bodyEl.appendChild(para(par.replace(/\n/g, " "), null, ref)); }); }); // Skills last, and grouped. Each skill file names a `group`, and the card reads it off diff --git a/model/og.sha b/model/og.sha index 8f037fc..48b517f 100644 --- a/model/og.sha +++ b/model/og.sha @@ -1 +1 @@ -8878817e742ec62bdd885e79680145cc8ee405623220061487ae4bd767001752 +510ba29139eeb770b264592bee14c0b18c1bb1eea713c32b5bb084788eb07a1a diff --git a/package-lock.json b/package-lock.json index 25197cd..9768a04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "blust-ch", "version": "1.0.0", "devDependencies": { - "@robertblust/design": "github:robertblust/design#v0.62.0", + "@robertblust/design": "github:robertblust/design#v0.63.0", "companygraph-meta-model": "github:companygraph/meta-model#v0.27.0", "pdf-lib": "^1.17.1", "playwright": "^1.63.0" @@ -35,8 +35,8 @@ } }, "node_modules/@robertblust/design": { - "version": "0.62.0", - "resolved": "git+ssh://git@github.com/robertblust/design.git#cd6ea8184e98330c1caeb4a8767ce7aa1876e870", + "version": "0.63.0", + "resolved": "git+ssh://git@github.com/robertblust/design.git#f0515aad4dd30b571b7cbce68c6c11271f53ffb0", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index ec0088e..36e20af 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "pin:check": "node pin-check.mjs" }, "devDependencies": { - "@robertblust/design": "github:robertblust/design#v0.62.0", + "@robertblust/design": "github:robertblust/design#v0.63.0", "companygraph-meta-model": "github:companygraph/meta-model#v0.27.0", "pdf-lib": "^1.17.1", "playwright": "^1.63.0" diff --git a/stage.css b/stage.css index 09e8184..ef54cfa 100644 --- a/stage.css +++ b/stage.css @@ -113,7 +113,13 @@ .cbody h4{margin-top:1.4rem; padding-top:1.1rem; border-top:1px solid var(--rule); font-family:"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif; font-size:1.02rem; font-weight:700; letter-spacing:-.015em; line-height:1.2} - .cbody h4 + p, .cbody h4 + ul.prose{margin-top:.45rem} + .cbody h4 + p, .cbody h4 + ul.prose, .cbody h4 + ol.prose{margin-top:.45rem} + /* A subheading inside a section, which the file writes as "###": a phase's Activities is Code + and then Prose. It belongs to the section above it, so it carries no rule; it is ink in the + prose's face at the prose's size and weight 600, which is all a reader needs to see the + section turn, and it keeps the h4's order of air, more above than below. */ + .cbody h5.sub{margin-top:.9rem; font-size:.93rem; font-weight:600; line-height:1.3; color:var(--ink)} + .cbody h5.sub + p, .cbody h5.sub + ul.prose, .cbody h5.sub + ol.prose{margin-top:.3rem} /* Running text is prose, and prose on every page is dim: ink is for a title, a value and a control. A paragraph and a list share the rule, because an experience card is mostly a list. Inline code stays ink — it is quoted from the file, data rather than prose, and the @@ -129,11 +135,17 @@ `display:block` and not a flex or grid column for the same reason the page's rule was a problem: an element that is not laid out as a list item draws no marker. Rows are spaced by a margin between items. */ + /* A numbered list is the same list with its numbers: the file numbered it because the items + are a sequence, and the numbers say that. The padding is wider than the disc's so a + two-digit number still hangs outside the text. */ .cbody ul.prose{display:block; list-style:disc; margin:.4rem 0 0; padding-left:1.05rem; font-size:.93rem; line-height:1.5; color:var(--dim)} - .cbody ul.prose li + li{margin-top:.4rem} - .cbody ul.prose li::marker{color:var(--c-weak)} - .cbody p code, .cbody ul.prose code{color:var(--ink)} + .cbody ol.prose{display:block; list-style:decimal; margin:.4rem 0 0; padding-left:1.45rem; + font-size:.93rem; line-height:1.5; color:var(--dim)} + .cbody ul.prose li + li, .cbody ol.prose li + li{margin-top:.4rem} + .cbody ul.prose li::marker, .cbody ol.prose li::marker{color:var(--c-weak)} + .cbody ol.prose li::marker{font-variant-numeric:tabular-nums} + .cbody p code, .cbody ul.prose code, .cbody ol.prose code{color:var(--ink)} .cbody table{margin-top:.5rem; border-collapse:collapse; width:100%; font-size:.88rem} .cbody th{text-align:left; font-family:"Plex Mono",monospace; font-weight:600; font-size:.72rem; letter-spacing:.06em; color:var(--dim); padding:.3rem .6rem .3rem 0; border-bottom:1px solid var(--rule)} /* A cell is one of four things, and the renderer has already decided which: a reference is diff --git a/surfaces/og.sha b/surfaces/og.sha index 84484ea..99dd5b2 100644 --- a/surfaces/og.sha +++ b/surfaces/og.sha @@ -1 +1 @@ -4c677ab810a448e3c848bac2620eb47f8c6fb941d43d321626ba54cb62616e82 +a61d47fa9f6e7a13855865b80bc9acb7aee8576f5fec583b80ddfa3dd2a1b7fd diff --git a/team/og.sha b/team/og.sha index c7f7f7d..3ea5eb2 100644 --- a/team/og.sha +++ b/team/og.sha @@ -1 +1 @@ -078111dd3f7352dcb47a4007364d50aa25e00d69a9e305bd069346faf237c5f8 +e005216941e7629d55cbf9f57f8260a9ef82fea72facf2007b44a0cbd7d43245 diff --git a/timeline/og.sha b/timeline/og.sha index 976b580..0769ef7 100644 --- a/timeline/og.sha +++ b/timeline/og.sha @@ -1 +1 @@ -08c0a9cbb09ff8f603dbb6024bb8a446fa22ad3be5174a70716a8e5934647598 +f77cfbe72cdbff4f8a29530b2a54ec5fc76a3036727b4095c9df9b716ce654cf