@@ -141,7 +141,7 @@ def __init__(self) -> None:
141
141
self ._primary_url_map : dict [str , list [str ]] = {}
142
142
self ._secondary_url_map : dict [str , list [str ]] = {}
143
143
self ._title_map : dict [str , str ] = {}
144
- self ._backlink_page_map : dict [str , Page ] = {}
144
+ self ._breadcrumbs_map : dict [str , BacklinkCrumb ] = {}
145
145
self ._abs_url_map : dict [str , str ] = {}
146
146
self ._backlinks : dict [str , dict [str , set [str ]]] = defaultdict (lambda : defaultdict (set ))
147
147
# YORE: Bump 2: Remove line.
@@ -299,6 +299,7 @@ def on_env(self, env: Environment, /, *, config: MkDocsConfig, files: Files) ->
299
299
# ----------------------------------------------------------------------- #
300
300
# Utilities #
301
301
# ----------------------------------------------------------------------- #
302
+ # TODO: Maybe stop exposing this method in the future.
302
303
def map_urls (self , page : Page , anchor : AnchorLink ) -> None :
303
304
"""Recurse on every anchor to map its ID to its absolute URL.
304
305
@@ -308,6 +309,9 @@ def map_urls(self, page: Page, anchor: AnchorLink) -> None:
308
309
page: The page containing the anchors.
309
310
anchor: The anchor to process and to recurse on.
310
311
"""
312
+ return self ._map_urls (page , anchor )
313
+
314
+ def _map_urls (self , page : Page , anchor : AnchorLink , parent : BacklinkCrumb | None = None ) -> None :
311
315
# YORE: Bump 2: Remove block.
312
316
if isinstance (page , str ):
313
317
try :
@@ -316,8 +320,36 @@ def map_urls(self, page: Page, anchor: AnchorLink) -> None:
316
320
page = self .current_page
317
321
318
322
self .register_anchor (page , anchor .id , title = anchor .title , primary = True )
323
+ breadcrumb = self ._get_breadcrumb (page , anchor , parent )
319
324
for child in anchor .children :
320
- self .map_urls (page , child )
325
+ self ._map_urls (page , child , breadcrumb )
326
+
327
+ def _get_breadcrumb (
328
+ self ,
329
+ page : Page | Section ,
330
+ anchor : AnchorLink | None = None ,
331
+ parent : BacklinkCrumb | None = None ,
332
+ ) -> BacklinkCrumb :
333
+ parent_breadcrumb = None if page .parent is None else self ._get_breadcrumb (page .parent )
334
+ if parent is None :
335
+ if isinstance (page , Page ):
336
+ if (parent_url := page .url ) not in self ._breadcrumbs_map :
337
+ self ._breadcrumbs_map [parent_url ] = BacklinkCrumb (
338
+ title = page .title ,
339
+ url = parent_url ,
340
+ parent = parent_breadcrumb ,
341
+ )
342
+ parent = self ._breadcrumbs_map [parent_url ]
343
+ else :
344
+ parent = BacklinkCrumb (title = page .title , url = "" , parent = parent_breadcrumb )
345
+ if anchor is None :
346
+ return parent
347
+ if (url := f"{ page .url } #{ anchor .id } " ) not in self ._breadcrumbs_map : # type: ignore[union-attr]
348
+ # Skip the parent page if the anchor is a top-level heading, to reduce repetition.
349
+ if anchor .level == 1 :
350
+ parent = parent .parent
351
+ self ._breadcrumbs_map [url ] = BacklinkCrumb (title = anchor .title , url = url , parent = parent )
352
+ return self ._breadcrumbs_map [url ]
321
353
322
354
def _record_backlink (self , identifier : str , backlink_type : str , backlink_anchor : str , page_url : str ) -> None :
323
355
"""Record a backlink.
@@ -351,23 +383,22 @@ def get_backlinks(self, *identifiers: str, from_url: str) -> dict[str, set[Backl
351
383
backlinks = self ._backlinks .get (identifier , {})
352
384
for backlink_type , backlink_urls in backlinks .items ():
353
385
for backlink_url in backlink_urls :
354
- relative_backlinks [backlink_type ].add (self ._crumbs (from_url , backlink_url ))
386
+ relative_backlinks [backlink_type ].add (self ._get_backlink (from_url , backlink_url ))
355
387
return relative_backlinks
356
388
357
- def _crumbs (self , from_url : str , backlink_url : str ) -> Backlink :
358
- backlink_page : Page = self ._backlink_page_map [backlink_url ]
359
- backlink_title = self ._title_map .get (backlink_url , "" )
360
- crumbs : list [BacklinkCrumb ] = [
361
- BacklinkCrumb (backlink_title , relative_url (from_url , backlink_url )),
362
- BacklinkCrumb (backlink_page .title , relative_url (from_url , backlink_page .url + "#" )),
363
- ]
364
- page : Page | Section = backlink_page
365
- while page .parent :
366
- page = page .parent
367
- if url := getattr (page , "url" , "" ):
368
- url = relative_url (from_url , url + "#" )
369
- crumbs .append (BacklinkCrumb (page .title , url ))
370
- return Backlink (tuple (reversed (crumbs )))
389
+ def _get_backlink (self , from_url : str , backlink_url : str ) -> Backlink :
390
+ breadcrumbs = []
391
+ breadcrumb : BacklinkCrumb | None = self ._breadcrumbs_map [backlink_url ]
392
+ while breadcrumb :
393
+ breadcrumbs .append (
394
+ BacklinkCrumb (
395
+ title = breadcrumb .title ,
396
+ url = breadcrumb .url and relative_url (from_url , breadcrumb .url ),
397
+ parent = breadcrumb .parent ,
398
+ ),
399
+ )
400
+ breadcrumb = breadcrumb .parent
401
+ return Backlink (tuple (reversed (breadcrumbs )))
371
402
372
403
def register_anchor (
373
404
self ,
@@ -403,8 +434,6 @@ def register_anchor(
403
434
url_map [identifier ] = [url ]
404
435
if title and url not in self ._title_map :
405
436
self ._title_map [url ] = title
406
- if self .record_backlinks and url not in self ._backlink_page_map :
407
- self ._backlink_page_map [url ] = page
408
437
409
438
def register_url (self , identifier : str , url : str ) -> None :
410
439
"""Register that the identifier should be turned into a link to this URL.
0 commit comments