Skip to content

Commit 0f13e64

Browse files
authored
add support for external links to dartdoc categories (#3631)
1 parent 80ef7a8 commit 0f13e64

File tree

9 files changed

+241
-9
lines changed

9 files changed

+241
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Remove explicit library names. (#3609)
44
* No longer write the dartdoc version into generated html files.
55
* Add support for [GitHub markdown alert syntax][].
6+
* Add support for external links to dartdoc categories (#3617).
67
* Require Dart 3.2.0 or later.
78
* Require `analyzer: ^6.4.0`.
89

lib/resources/styles.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@
9696
cursor: pointer;
9797
}
9898

99+
li .material-symbols-outlined, dt .material-symbols-outlined {
100+
font-size: 1em;
101+
vertical-align: text-bottom;
102+
}
103+
104+
dt .material-symbols-outlined {
105+
text-indent: 0;
106+
}
107+
99108
.light-theme #light-theme-button {
100109
display: none;
101110
}

lib/src/dartdoc_options.dart

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,15 @@ class CategoryDefinition {
7171
/// (or null if undocumented).
7272
final String? documentationMarkdown;
7373

74-
CategoryDefinition(this.name, this._displayName, this.documentationMarkdown);
74+
/// The external items defined for this category.
75+
final List<ExternalItem> externalItems;
76+
77+
CategoryDefinition(
78+
this.name,
79+
this._displayName,
80+
this.documentationMarkdown, {
81+
this.externalItems = const [],
82+
});
7583

7684
/// Returns the [_displayName], if available, or else simply [name].
7785
String get displayName => _displayName ?? name ?? '';
@@ -107,8 +115,39 @@ class CategoryConfiguration {
107115
'the missing file $documentationMarkdown');
108116
}
109117
}
110-
newCategoryDefinitions[name] =
111-
CategoryDefinition(name, displayName, documentationMarkdown);
118+
final externalItems = <ExternalItem>[];
119+
var items = categoryMap['external'] as List?;
120+
if (items != null) {
121+
for (var item in items) {
122+
if (item is! Map) {
123+
throw DartdocOptionError("'external' field should be a map");
124+
} else {
125+
final itemName = item['name'] as String?;
126+
if (itemName == null) {
127+
throw DartdocOptionError(
128+
"'external' item missing required field 'name'");
129+
}
130+
131+
final itemUrl = item['url'] as String?;
132+
if (itemUrl == null) {
133+
throw DartdocOptionError(
134+
"'external' item missing required field 'url'");
135+
}
136+
137+
externalItems.add(ExternalItem(
138+
name: itemName,
139+
url: itemUrl,
140+
docs: item['docs'] as String?,
141+
));
142+
}
143+
}
144+
}
145+
newCategoryDefinitions[name] = CategoryDefinition(
146+
name,
147+
displayName,
148+
documentationMarkdown,
149+
externalItems: externalItems,
150+
);
112151
}
113152
}
114153
return CategoryConfiguration._(newCategoryDefinitions);

lib/src/generator/templates.aot_renderers_for_html.dart

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,10 +1048,31 @@ String renderIndex(PackageTemplateData context0) {
10481048
<h3>''');
10491049
buffer.writeEscaped(context8.name);
10501050
buffer.write('''</h3>''');
1051-
var context9 = context8.publicLibrariesSorted;
1051+
var context9 = context8.externalItems;
10521052
for (var context10 in context9) {
1053+
buffer.writeln();
1054+
buffer.write('''
1055+
<dt>
1056+
<span class="name">
1057+
<a href="''');
1058+
buffer.writeEscaped(context10.url);
1059+
buffer.write('''" target="_blank">
1060+
''');
1061+
buffer.writeEscaped(context10.name);
1062+
buffer.writeln();
1063+
buffer.write('''
1064+
<span class="material-symbols-outlined">open_in_new</span>
1065+
</a>
1066+
</span>
1067+
</dt>
1068+
<dd>''');
1069+
buffer.writeEscaped(context10.docs);
1070+
buffer.write('''</dd>''');
1071+
}
1072+
var context11 = context8.publicLibrariesSorted;
1073+
for (var context12 in context11) {
10531074
buffer.write('\n ');
1054-
buffer.write(_renderIndex_partial_library_2(context10));
1075+
buffer.write(_renderIndex_partial_library_2(context12));
10551076
}
10561077
}
10571078
buffer.writeln();
@@ -4336,12 +4357,28 @@ String _deduplicated_lib_templates_html__packages_html(
43364357
<li class="section-subtitle">''');
43374358
buffer.writeEscaped(context9.name);
43384359
buffer.write('''</li>''');
4339-
var context10 = context9.publicLibrariesSorted;
4360+
var context10 = context9.externalItems;
43404361
for (var context11 in context10) {
4362+
buffer.writeln();
4363+
buffer.write('''
4364+
<li class="section-subitem">
4365+
<a href="''');
4366+
buffer.writeEscaped(context11.url);
4367+
buffer.write('''" target="_blank">
4368+
''');
4369+
buffer.writeEscaped(context11.name);
4370+
buffer.writeln();
4371+
buffer.write('''
4372+
<span class="material-symbols-outlined">open_in_new</span>
4373+
</a>
4374+
</li>''');
4375+
}
4376+
var context12 = context9.publicLibrariesSorted;
4377+
for (var context13 in context12) {
43414378
buffer.writeln();
43424379
buffer.write('''
43434380
<li class="section-subitem">''');
4344-
buffer.write(context11.linkedName);
4381+
buffer.write(context13.linkedName);
43454382
buffer.write('''</li>''');
43464383
}
43474384
}

lib/src/generator/templates.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const _visibleTypes = {
7171
Enum,
7272
Extension,
7373
ExtensionType,
74+
ExternalItem,
7475
FeatureSet,
7576
FunctionTypeElementType,
7677
InheritingContainer,

lib/src/generator/templates.runtime_renderers.dart

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,19 @@ class _Renderer_Category extends RendererBase<Category> {
12761276
_render_Extension(e, ast, r.template, sink, parent: r));
12771277
},
12781278
),
1279+
'externalItems': Property(
1280+
getValue: (CT_ c) => c.externalItems,
1281+
renderVariable: (CT_ c, Property<CT_> self,
1282+
List<String> remainingNames) =>
1283+
self.renderSimpleVariable(
1284+
c, remainingNames, 'Iterable<ExternalItem>'),
1285+
renderIterable: (CT_ c, RendererBase<CT_> r,
1286+
List<MustachioNode> ast, StringSink sink) {
1287+
return c.externalItems.map((e) => _render_ExternalItem(
1288+
e, ast, r.template, sink,
1289+
parent: r));
1290+
},
1291+
),
12791292
'filePath': Property(
12801293
getValue: (CT_ c) => c.filePath,
12811294
renderVariable:
@@ -5822,6 +5835,99 @@ class _Renderer_ExtensionTypeTemplateData<T extends ExtensionType>
58225835
}
58235836
}
58245837

5838+
void _render_ExternalItem(ExternalItem context, List<MustachioNode> ast,
5839+
Template template, StringSink sink,
5840+
{RendererBase<Object>? parent}) {
5841+
var renderer = _Renderer_ExternalItem(context, parent, template, sink);
5842+
renderer.renderBlock(ast);
5843+
}
5844+
5845+
class _Renderer_ExternalItem extends RendererBase<ExternalItem> {
5846+
static final Map<Type, Object> _propertyMapCache = {};
5847+
static Map<String, Property<CT_>> propertyMap<CT_ extends ExternalItem>() =>
5848+
_propertyMapCache.putIfAbsent(
5849+
CT_,
5850+
() => {
5851+
..._Renderer_Object.propertyMap<CT_>(),
5852+
'docs': Property(
5853+
getValue: (CT_ c) => c.docs,
5854+
renderVariable:
5855+
(CT_ c, Property<CT_> self, List<String> remainingNames) {
5856+
if (remainingNames.isEmpty) {
5857+
return self.getValue(c).toString();
5858+
}
5859+
var name = remainingNames.first;
5860+
var nextProperty =
5861+
_Renderer_String.propertyMap().getValue(name);
5862+
return nextProperty.renderVariable(
5863+
self.getValue(c) as String,
5864+
nextProperty,
5865+
[...remainingNames.skip(1)]);
5866+
},
5867+
isNullValue: (CT_ c) => false,
5868+
renderValue: (CT_ c, RendererBase<CT_> r,
5869+
List<MustachioNode> ast, StringSink sink) {
5870+
_render_String(c.docs, ast, r.template, sink, parent: r);
5871+
},
5872+
),
5873+
'name': Property(
5874+
getValue: (CT_ c) => c.name,
5875+
renderVariable:
5876+
(CT_ c, Property<CT_> self, List<String> remainingNames) {
5877+
if (remainingNames.isEmpty) {
5878+
return self.getValue(c).toString();
5879+
}
5880+
var name = remainingNames.first;
5881+
var nextProperty =
5882+
_Renderer_String.propertyMap().getValue(name);
5883+
return nextProperty.renderVariable(
5884+
self.getValue(c) as String,
5885+
nextProperty,
5886+
[...remainingNames.skip(1)]);
5887+
},
5888+
isNullValue: (CT_ c) => false,
5889+
renderValue: (CT_ c, RendererBase<CT_> r,
5890+
List<MustachioNode> ast, StringSink sink) {
5891+
_render_String(c.name, ast, r.template, sink, parent: r);
5892+
},
5893+
),
5894+
'url': Property(
5895+
getValue: (CT_ c) => c.url,
5896+
renderVariable:
5897+
(CT_ c, Property<CT_> self, List<String> remainingNames) {
5898+
if (remainingNames.isEmpty) {
5899+
return self.getValue(c).toString();
5900+
}
5901+
var name = remainingNames.first;
5902+
var nextProperty =
5903+
_Renderer_String.propertyMap().getValue(name);
5904+
return nextProperty.renderVariable(
5905+
self.getValue(c) as String,
5906+
nextProperty,
5907+
[...remainingNames.skip(1)]);
5908+
},
5909+
isNullValue: (CT_ c) => false,
5910+
renderValue: (CT_ c, RendererBase<CT_> r,
5911+
List<MustachioNode> ast, StringSink sink) {
5912+
_render_String(c.url, ast, r.template, sink, parent: r);
5913+
},
5914+
),
5915+
}) as Map<String, Property<CT_>>;
5916+
5917+
_Renderer_ExternalItem(ExternalItem context, RendererBase<Object>? parent,
5918+
Template template, StringSink sink)
5919+
: super(context, parent, template, sink);
5920+
5921+
@override
5922+
Property<ExternalItem>? getProperty(String key) {
5923+
if (propertyMap<ExternalItem>().containsKey(key)) {
5924+
return propertyMap<ExternalItem>()[key];
5925+
} else {
5926+
return null;
5927+
}
5928+
}
5929+
}
5930+
58255931
class _Renderer_FeatureSet extends RendererBase<FeatureSet> {
58265932
static final Map<Type, Object> _propertyMapCache = {};
58275933
static Map<String, Property<CT_>> propertyMap<CT_ extends FeatureSet>() =>
@@ -12764,13 +12870,13 @@ class _Renderer_PackageTemplateData extends RendererBase<PackageTemplateData> {
1276412870
}
1276512871
}
1276612872

12767-
String renderIndex(PackageTemplateData context, Template template) {
12873+
String renderError(PackageTemplateData context, Template template) {
1276812874
var buffer = StringBuffer();
1276912875
_render_PackageTemplateData(context, template.ast, template, buffer);
1277012876
return buffer.toString();
1277112877
}
1277212878

12773-
String renderError(PackageTemplateData context, Template template) {
12879+
String renderIndex(PackageTemplateData context, Template template) {
1277412880
var buffer = StringBuffer();
1277512881
_render_PackageTemplateData(context, template.ast, template, buffer);
1277612882
return buffer.toString();

lib/src/model/category.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class Category extends Nameable
6767
config.categories.categoryDefinitions[_name.orDefault] ??
6868
CategoryDefinition(_name, null, null);
6969

70+
Iterable<ExternalItem> get externalItems => _categoryDefinition.externalItems;
71+
7072
void addClass(Class class_) {
7173
if (class_.isErrorOrException) {
7274
_exceptions.add(class_);
@@ -184,3 +186,21 @@ class Category extends Nameable
184186
extension on String? {
185187
String get orDefault => this ?? '<default>';
186188
}
189+
190+
/// A external link for an item in a dartdoc category.
191+
///
192+
/// This is a name, url, and optional documentation text.
193+
class ExternalItem {
194+
final String name;
195+
final String url;
196+
final String docs;
197+
198+
ExternalItem({
199+
required this.name,
200+
required this.url,
201+
required String? docs,
202+
}) : docs = docs ?? '';
203+
204+
@override
205+
String toString() => '$name $url';
206+
}

lib/templates/html/_packages.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
{{/defaultCategory.publicLibrariesSorted}}
1818
{{#categoriesWithPublicLibraries}}
1919
<li class="section-subtitle">{{name}}</li>
20+
{{#externalItems}}
21+
<li class="section-subitem">
22+
<a href="{{url}}" target="_blank">
23+
{{name}}
24+
<span class="material-symbols-outlined">open_in_new</span>
25+
</a>
26+
</li>
27+
{{/externalItems}}
2028
{{#publicLibrariesSorted}}
2129
<li class="section-subitem">{{{linkedName}}}</li>
2230
{{/publicLibrariesSorted}}

lib/templates/html/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ <h2>{{name}}</h2>
1919
{{/defaultCategory.publicLibrariesSorted}}
2020
{{#categoriesWithPublicLibraries}}
2121
<h3>{{name}}</h3>
22+
{{#externalItems}}
23+
<dt>
24+
<span class="name">
25+
<a href="{{url}}" target="_blank">
26+
{{name}}
27+
<span class="material-symbols-outlined">open_in_new</span>
28+
</a>
29+
</span>
30+
</dt>
31+
<dd>{{docs}}</dd>
32+
{{/externalItems}}
2233
{{#publicLibrariesSorted}}
2334
{{>library}}
2435
{{/publicLibrariesSorted}}

0 commit comments

Comments
 (0)