@@ -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 */
179231function 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
267363interface 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}
0 commit comments