Skip to content

Commit 5403948

Browse files
feat: summary
1 parent fe95072 commit 5403948

File tree

4 files changed

+207
-26
lines changed

4 files changed

+207
-26
lines changed

main.typ

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,11 @@ $ integral f dif x $
13491349

13501350
= #bbl(en: [Addendum], zh: [附录])
13511351

1352+
== #bbl(en: [Summary], zh: [概要])
1353+
1354+
#import "typ/respec.typ": summary
1355+
#summary
1356+
13521357
== #bbl(en: [Environment of the examples], zh: [例子的环境信息])
13531358

13541359
- #bbl(en: [Update date], zh: [更新日期]) \

src/respec/structure.css

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,77 @@ a.permalink {
1313
content: "§";
1414
}
1515
}
16+
17+
/* Summary */
18+
ol#summary {
19+
/* Numbers are inlined into source */
20+
list-style-type: none;
21+
padding-left: 0;
22+
23+
display: grid;
24+
grid-template-columns: repeat(auto-fit, minmax(15em, 1fr));
25+
row-gap: 1em;
26+
27+
> li > a {
28+
height: 100%;
29+
display: flex;
30+
flex-direction: column;
31+
32+
padding-left: 0.5em;
33+
padding-right: 0.5em;
34+
35+
color: var(--toclink-text);
36+
37+
/* Switch to using border-bottom */
38+
text-decoration: none;
39+
border-bottom: 3px solid transparent;
40+
margin-bottom: -2px;
41+
42+
&:hover {
43+
background: var(--a-hover-bg);
44+
border-bottom-color: var(--toclink-underline);
45+
}
46+
47+
/* heading + v(1fr) + dots + report */
48+
> * {
49+
margin-top: 0;
50+
margin-bottom: 0;
51+
}
52+
> .dots {
53+
margin-top: auto;
54+
}
55+
56+
> .dots {
57+
display: flex;
58+
flex-wrap: wrap-reverse;
59+
60+
> * {
61+
border-radius: 50%;
62+
display: inline-block;
63+
64+
border-width: 2px;
65+
width: calc(0.8em - 2px * 2);
66+
height: calc(0.8em - 2px * 2);
67+
margin: 0.25em;
68+
vertical-align: -15%;
69+
70+
border-style: solid;
71+
border-color: transparent;
72+
}
73+
}
74+
75+
:root:not(.dark) &:hover > .dots > * {
76+
/* Make sure tbd-level dots are visible */
77+
border-color: var(--main-bg-color);
78+
width: calc(0.8em - 2px);
79+
height: calc(0.8em - 2px);
80+
margin: calc(0.25em - 1px);
81+
vertical-align: -16%;
82+
}
83+
84+
> .report {
85+
font-size: small;
86+
color: var(--gray-color);
87+
}
88+
}
89+
}

src/respec/structure.ts

Lines changed: 123 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@ function filterHeader(h: HTMLElement): void {
118118
});
119119
}
120120

121-
function createTableOfContents(ol: HTMLElement): void {
121+
function createTableOfContents(ol: HTMLElement): HTMLElement {
122122
const nav = html`
123123
<nav id="toc"></nav>
124-
`;
124+
` as HTMLElement;
125125
const h2 = html`
126126
<h2 class="introductory">
127127
<span lang="en" its-locale-filter-list="en">Contents</span>
@@ -139,6 +139,8 @@ function createTableOfContents(ol: HTMLElement): void {
139139
ref.after(nav);
140140
}
141141
}
142+
143+
return nav;
142144
}
143145

144146
/** Add permalinks to headings */
@@ -160,10 +162,16 @@ function addPermalinks(): void {
160162
}
161163
}
162164

165+
interface PriorityLevel {
166+
paint: string;
167+
human: string;
168+
}
169+
type LevelName = "tbd" | "na" | "ok" | "advanced" | "basic" | "broken";
170+
163171
/**
164172
* Configuration of levels, sorted.
165173
*/
166-
const PRIORITY_CONFIG: Record<string, { paint: string; human: string }> = {
174+
const PRIORITY_CONFIG: Record<LevelName, PriorityLevel> = {
167175
tbd: { paint: "eeeeee", human: "To be done" },
168176
na: { paint: "008000", human: "Not applicable" },
169177
ok: { paint: "008000", human: "OK" },
@@ -172,18 +180,65 @@ const PRIORITY_CONFIG: Record<string, { paint: string; human: string }> = {
172180
broken: { paint: "ff0000", human: "Broken" },
173181
};
174182

183+
/**
184+
* Collect all priority levels in `element`.
185+
*/
186+
function collectPriorityLevels(elements: HTMLElement[]): LevelName[] {
187+
return elements.flatMap((el) => {
188+
const tags = Array.from(
189+
el.querySelectorAll<HTMLElement>("[data-priority-level]"),
190+
);
191+
return tags.map((t) => t.dataset.priorityLevel as LevelName);
192+
});
193+
}
194+
195+
/**
196+
* Calculate statistics of priority levels
197+
*
198+
* The returned `counts` will sorted from the most important to the least important according to `PRIORITY_CONFIG`.
199+
* If `levels` is empty, return `null`
200+
*/
201+
function countPriorityLevels(
202+
levels: LevelName[],
203+
): { worst: PriorityLevel; report: string } | null {
204+
if (levels.length === 0) {
205+
return null;
206+
}
207+
208+
// Calculate `worst` and `report`
209+
const ordering = Object.keys(PRIORITY_CONFIG) as LevelName[];
210+
const worst = PRIORITY_CONFIG[
211+
ordering[
212+
Math.max(...levels.map((l) => ordering.indexOf(l)))
213+
]
214+
];
215+
216+
const counts: Map<LevelName, number> = levels.reduce(
217+
(last, l) => last.set(l, (last.get(l) as number) + 1),
218+
new Map(ordering.reverse().map((l) => [l, 0])),
219+
);
220+
const report = Array.from(counts.entries()).filter(([_level, n]) => n > 0)
221+
.map(([level, n]) => `${n} ${PRIORITY_CONFIG[level].human}`).join(", ");
222+
223+
return { worst, report };
224+
}
225+
175226
/**
176227
* Calculate priority levels for all sections, and insert levels in them
177-
@param sections The section tree
228+
* @param sections The section tree
229+
* @returns Top sections’ levels if this is the top level
178230
*/
179231
function insertPriorityLevel(
180232
sections: Section[],
181233
siblings: HTMLElement[] | null = null,
182-
): void {
234+
): Map<HTMLElement, LevelName[]> | undefined {
183235
if (sections.length === 0) {
184236
return;
185237
}
186238

239+
/** Whether the current iteration is the top level */
240+
const isTop = siblings === null;
241+
187242
// If not given, fill with the default
188243
if (siblings === null) {
189244
const parent = sections[0].header.parentElement as HTMLElement;
@@ -203,16 +258,14 @@ function insertPriorityLevel(
203258
siblings,
204259
);
205260

261+
const topSectionLevels = new Map<HTMLElement, LevelName[]>();
262+
263+
// Insert levels recursively, and save `topSectionLevels`
206264
sections.forEach((sec, i) => {
207265
const start = startIndices[i];
208266
const end = startIndices.at(i + 1);
209267

210-
const levels = siblings.slice(start, end).flatMap((el) => {
211-
const tags = Array.from(
212-
el.querySelectorAll<HTMLElement>("[data-priority-level]"),
213-
);
214-
return tags.map((t) => t.dataset.priorityLevel);
215-
}) as string[];
268+
const levels = collectPriorityLevels(siblings.slice(start, end));
216269

217270
// if leaf section
218271
if (sec.subsections.length === 0) {
@@ -222,20 +275,11 @@ function insertPriorityLevel(
222275
sec,
223276
);
224277
} else if (levels.length > 0) {
225-
// Calculate `worst` and `report`
226-
const ordering = Object.keys(PRIORITY_CONFIG);
227-
const worst = PRIORITY_CONFIG[
228-
ordering[
229-
Math.max(...levels.map((l) => ordering.indexOf(l)))
230-
]
231-
];
232-
const counts = levels.reduce(
233-
// @ts-ignore
234-
(last, l) => last.set(l, last.get(l) + 1),
235-
new Map(ordering.reverse().map((l) => [l, 0])),
236-
);
237-
const report = Array.from(counts.entries()).filter(([_level, n]) => n > 0)
238-
.map(([level, n]) => `${n} ${PRIORITY_CONFIG[level].human}`).join(", ");
278+
if (isTop) {
279+
topSectionLevels.set(sec.header, levels);
280+
}
281+
282+
const { worst, report } = countPriorityLevels(levels)!;
239283

240284
// Find the first non prompt element
241285
let pos = sec.header;
@@ -262,6 +306,58 @@ function insertPriorityLevel(
262306
insertPriorityLevel(sec.subsections, siblings.slice(start, end));
263307
}
264308
});
309+
310+
if (topSectionLevels.size > 0) {
311+
return topSectionLevels;
312+
}
313+
}
314+
315+
/**
316+
* Draw top sections’ levels at `ol#summary`.
317+
*/
318+
function createSummary(
319+
topSectionLevels: Map<HTMLElement, LevelName[]>,
320+
): void {
321+
// return
322+
const ol = document.querySelector<HTMLOListElement>("ol#summary")!;
323+
324+
for (const [sec, levels] of topSectionLevels) {
325+
console.assert(levels.length > 0);
326+
327+
// Draw dots
328+
const ordering = Object.keys(PRIORITY_CONFIG) as LevelName[];
329+
const sortedLevels = [...levels].sort((a, b) => {
330+
return ordering.indexOf(b) - ordering.indexOf(a);
331+
});
332+
333+
const dots = html`
334+
<p class="dots" />
335+
`;
336+
sortedLevels.forEach((level) => {
337+
const { paint } = PRIORITY_CONFIG[level];
338+
const dot = html`
339+
<span style="background: #${paint}" class="dot" />
340+
` as HTMLElement;
341+
dots.appendChild(dot);
342+
});
343+
344+
// Save elements
345+
const secWrapper = html`
346+
<slot><p /></slot>
347+
` as HTMLElement;
348+
secWrapper.firstElementChild!.append(...sec.cloneNode(true).childNodes);
349+
350+
// `row` is <li>, and `inner` is `<a>`
351+
const row = createTocListItem(secWrapper, sec.id);
352+
const inner = row.firstChild!;
353+
354+
inner.appendChild(dots);
355+
inner.appendChild(html`
356+
<p class="report">${countPriorityLevels(levels)!.report}</p>
357+
`);
358+
359+
ol.appendChild(row);
360+
}
265361
}
266362

267363
interface Configuration {
@@ -277,5 +373,6 @@ export function createStructure(conf: Configuration = {}): void {
277373

278374
addPermalinks();
279375

280-
insertPriorityLevel(sectionTree);
376+
const topSectionLevels = insertPriorityLevel(sectionTree)!;
377+
createSummary(topSectionLevels);
281378
}

typ/respec.typ

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@
44
///
55
/// Usage: `#show outline: toc`
66
#let toc = html.elem("nav", attrs: (id: "toc"))
7+
8+
/// The summary that will be created by this module
9+
///
10+
/// Usage: `#summary`
11+
#let summary = html.elem("ol", attrs: (id: "summary"))

0 commit comments

Comments
 (0)