-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Expand file tree
/
Copy pathheader_processor.dart
More file actions
71 lines (60 loc) · 2.11 KB
/
header_processor.dart
File metadata and controls
71 lines (60 loc) · 2.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// Copyright 2025 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:jaspr_content/jaspr_content.dart';
/// A node-processing, page extension for Jaspr Content that
/// wraps each `h1` -> `h5` element in a div with the `header-wrapper` class
/// and adds an anchor that can clicked and linked to.
final class HeaderWrapperExtension implements PageExtension {
const HeaderWrapperExtension();
@override
Future<List<Node>> apply(Page page, List<Node> nodes) async {
final usedIds = <String>{};
return _processNodes(nodes, usedIds);
}
List<Node> _processNodes(List<Node> nodes, Set<String> usedIds) {
return [for (final node in nodes) _processNode(node, usedIds)];
}
Node _processNode(Node node, Set<String> usedIds) {
if (node is! ElementNode) return node;
final tagName = node.tag.toLowerCase();
if (!const {'h2', 'h3', 'h4', 'h5'}.contains(tagName)) {
// If it's not any of the supported heading levels,
// recurse into its children.
final nodeChildren = node.children;
return ElementNode(
node.tag,
node.attributes,
nodeChildren != null ? _processNodes(nodeChildren, usedIds) : null,
);
}
final rawHeaderId = node.attributes['id'];
if (rawHeaderId == null) return node;
// Account for headers with the same base id,
// appending a dash and an integer until the header has a unique id.
var headerId = rawHeaderId;
for (var i = 1; usedIds.contains(headerId); i += 1) {
headerId = '$rawHeaderId-$i';
}
usedIds.add(headerId);
// Update header ID to be the unique one as well.
node.attributes['id'] = headerId;
final headerText = node.innerText;
return ElementNode(
'div',
{'class': 'header-wrapper'},
[
node,
ElementNode(
'a',
{
'class': 'heading-link',
'href': '#$headerId',
'aria-label': "Link to '$headerText' section",
},
const [TextNode('#')],
),
],
);
}
}