Skip to content

Commit d842fdd

Browse files
authored
Include more content in the HTML output (#1390)
* Include more page content in the HTML output * Also add deprecation summaries to the HTML output
1 parent 077afe1 commit d842fdd

File tree

2 files changed

+333
-24
lines changed

2 files changed

+333
-24
lines changed

Sources/SwiftDocC/Model/Rendering/HTML/HTMLRenderer.swift

Lines changed: 191 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -157,25 +157,30 @@ struct HTMLRenderer {
157157
mutating func renderArticle(_ article: Article) -> RenderedPageInfo {
158158
let node = context.documentationCache[reference]!
159159

160-
let main = XMLElement(name: "main")
161160
let articleElement = XMLElement(name: "article")
162-
main.addChild(articleElement)
163-
164161
let hero = XMLElement(name: "section")
165162
articleElement.addChild(hero)
166163

164+
// Breadcrumbs and Eyebrow
165+
hero.addChild(renderer.breadcrumbs(
166+
references: (context.shortestFinitePath(to: reference) ?? [context.soleRootModuleReference!]).map { $0.url },
167+
currentPageNames: .single(.conceptual(node.name.plainText))
168+
))
169+
addEyebrow(text: article.topics == nil ? "Article": "API Collection", to: hero)
170+
167171
// Title
168172
hero.addChild(
169173
.element(named: "h1", children: [.text(node.name.plainText)])
170174
)
171175

172176
// Abstract
173177
if let abstract = article.abstract {
174-
let paragraph = renderer.visit(abstract) as! XMLElement
175-
if goal == .richness {
176-
paragraph.addAttribute(XMLNode.attribute(withName: "id", stringValue: "abstract") as! XMLNode)
177-
}
178-
hero.addChild(paragraph)
178+
addAbstract(abstract, to: hero)
179+
}
180+
181+
// Deprecation message
182+
if let deprecationMessage = article.deprecationSummary?.elements {
183+
addDeprecationSummary(markup: deprecationMessage, to: hero)
179184
}
180185

181186
// Discussion
@@ -185,8 +190,31 @@ struct HTMLRenderer {
185190
)
186191
}
187192

193+
// Topics
194+
if let topics = article.topics {
195+
separateSectionsIfNeeded(in: articleElement)
196+
197+
// TODO: Support language specific topic sections, indicated using @SupportedLanguage directives (rdar://166308418)
198+
articleElement.addChildren(
199+
renderer.groupedSection(named: "Topics", groups: [
200+
.swift: topics.taskGroups.map { group in
201+
.init(title: group.heading?.title, content: group.content, references: group.links.compactMap {
202+
$0.destination.flatMap { URL(string: $0) }
203+
})
204+
}
205+
])
206+
)
207+
}
208+
// Articles don't have _automatic_ topic sections.
209+
210+
// See Also
211+
if let seeAlso = article.seeAlso {
212+
addSeeAlso(seeAlso, to: articleElement)
213+
}
214+
// _Automatic_ See Also sections are very heavily tied into the RenderJSON model and require information from the JSON to determine.
215+
188216
return RenderedPageInfo(
189-
content: goal == .richness ? main : articleElement,
217+
content: articleElement,
190218
metadata: .init(
191219
title: article.title?.plainText ?? node.name.plainText,
192220
plainDescription: article.abstract?.plainText
@@ -195,13 +223,19 @@ struct HTMLRenderer {
195223
}
196224

197225
mutating func renderSymbol(_ symbol: Symbol) -> RenderedPageInfo {
198-
let main = XMLElement(name: "main")
199-
let articleElement = XMLElement(name: "article")
200-
main.addChild(articleElement)
226+
let node = context.documentationCache[reference]!
201227

228+
let articleElement = XMLElement(name: "article")
202229
let hero = XMLElement(name: "section")
203230
articleElement.addChild(hero)
204231

232+
// Breadcrumbs and Eyebrow
233+
hero.addChild(renderer.breadcrumbs(
234+
references: (context.linkResolver.localResolver.breadcrumbs(of: reference, in: reference.sourceLanguage) ?? []).map { $0.url },
235+
currentPageNames: node.makeNames(goal: goal)
236+
))
237+
addEyebrow(text: symbol.roleHeading, to: hero)
238+
205239
// Title
206240
switch symbol.titleVariants.values(goal: goal) {
207241
case .single(let title):
@@ -223,11 +257,72 @@ struct HTMLRenderer {
223257

224258
// Abstract
225259
if let abstract = symbol.abstract {
226-
let paragraph = renderer.visit(abstract) as! XMLElement
227-
if goal == .richness {
228-
paragraph.addAttribute(XMLNode.attribute(withName: "id", stringValue: "abstract") as! XMLNode)
260+
addAbstract(abstract, to: hero)
261+
}
262+
263+
// Availability
264+
if let availability = symbol.availability?.availability.filter({ $0.domain != nil }).sorted(by: \.domain!.rawValue),
265+
!availability.isEmpty
266+
{
267+
hero.addChild(
268+
renderer.availability(availability.map { item in
269+
.init(
270+
name: item.domain!.rawValue, // Verified non-empty above
271+
introduced: item.introducedVersion.map { "\($0.major).\($0.minor)" },
272+
deprecated: item.deprecatedVersion.map { "\($0.major).\($0.minor)" },
273+
isBeta: false // TODO: Derive and pass beta information
274+
)
275+
})
276+
)
277+
}
278+
279+
// Declaration
280+
if !symbol.declarationVariants.allValues.isEmpty {
281+
// TODO: Display platform specific declarations
282+
283+
var fragmentsByLanguage = [SourceLanguage: [SymbolGraph.Symbol.DeclarationFragments.Fragment]]()
284+
for (trait, variant) in symbol.declarationVariants.allValues {
285+
guard let language = trait.sourceLanguage else { continue }
286+
fragmentsByLanguage[language] = variant.values.first?.declarationFragments
229287
}
230-
hero.addChild(paragraph)
288+
289+
if fragmentsByLanguage.values.contains(where: { !$0.isEmpty }) {
290+
hero.addChild( renderer.declaration(fragmentsByLanguage) )
291+
}
292+
}
293+
294+
// Deprecation message
295+
if let deprecationMessage = symbol.deprecatedSummary?.content {
296+
addDeprecationSummary(markup: deprecationMessage, to: hero)
297+
}
298+
299+
// Parameters
300+
if let parameterSections = symbol.parametersSectionVariants
301+
.values(goal: goal, by: { $0.parameters.elementsEqual($1.parameters, by: { $0.name == $1.name }) })
302+
.valuesByLanguage()
303+
{
304+
articleElement.addChildren(renderer.parameters(
305+
parameterSections.mapValues { section in
306+
section.parameters.map {
307+
MarkdownRenderer<ContextLinkProvider>.ParameterInfo(name: $0.name, content: $0.contents)
308+
}
309+
}
310+
))
311+
}
312+
313+
// Return value
314+
if !symbol.returnsSectionVariants.allValues.isEmpty {
315+
articleElement.addChildren(
316+
renderer.returns(
317+
.init(
318+
symbol.returnsSectionVariants.allValues.map { trait, returnSection in (
319+
key: trait.sourceLanguage ?? .swift,
320+
value: returnSection.content
321+
)},
322+
uniquingKeysWith: { _, new in new }
323+
)
324+
)
325+
)
231326
}
232327

233328
// Mentioned In
@@ -246,6 +341,31 @@ struct HTMLRenderer {
246341
)
247342
}
248343

344+
// Topics
345+
do {
346+
// TODO: Support language specific topic sections, indicated using @SupportedLanguage directives (rdar://166308418)
347+
var taskGroupInfo: [MarkdownRenderer<ContextLinkProvider>.TaskGroupInfo] = []
348+
349+
if let authored = symbol.topics?.taskGroups {
350+
taskGroupInfo.append(contentsOf: authored.map { group in
351+
.init(title: group.heading?.title, content: group.content, references: group.links.compactMap {
352+
$0.destination.flatMap { URL(string: $0) }
353+
})
354+
})
355+
}
356+
if let automatic = try? AutomaticCuration.topics(for: node, withTraits: [.swift, .objectiveC], context: context) {
357+
taskGroupInfo.append(contentsOf: automatic.map { group in
358+
.init(title: group.title, content: [], references: group.references.compactMap { $0.url })
359+
})
360+
}
361+
362+
if !taskGroupInfo.isEmpty {
363+
separateSectionsIfNeeded(in: articleElement)
364+
365+
articleElement.addChildren(renderer.groupedSection(named: "Topics", groups: [.swift: taskGroupInfo]))
366+
}
367+
}
368+
249369
// Relationships
250370
if let relationships = symbol.relationshipsVariants
251371
.values(goal: goal, by: { $0.groups.elementsEqual($1.groups, by: { $0 == $1 }) })
@@ -265,14 +385,68 @@ struct HTMLRenderer {
265385
)
266386
}
267387

388+
// See Also
389+
if let seeAlso = symbol.seeAlso {
390+
addSeeAlso(seeAlso, to: articleElement)
391+
}
392+
268393
return RenderedPageInfo(
269-
content: goal == .richness ? main : articleElement,
394+
content: articleElement,
270395
metadata: .init(
271396
title: symbol.title,
272397
plainDescription: symbol.abstract?.plainText
273398
)
274399
)
275400
}
401+
402+
private func addEyebrow(text: String, to element: XMLElement) {
403+
element.addChild(
404+
.element(named: "p", children: [.text(text)], attributes: goal == .richness ? ["id": "eyebrow"] : [:])
405+
)
406+
}
407+
408+
private func addAbstract(_ abstract: Paragraph, to element: XMLElement) {
409+
let paragraph = renderer.visit(abstract) as! XMLElement
410+
if goal == .richness {
411+
paragraph.addAttribute(XMLNode.attribute(withName: "id", stringValue: "abstract") as! XMLNode)
412+
}
413+
element.addChild(paragraph)
414+
}
415+
416+
private func addDeprecationSummary(markup: [any Markup], to element: XMLElement) {
417+
var children: [XMLNode] = [
418+
.element(named: "p", children: [.text("Deprecated")], attributes: ["class": "label"])
419+
]
420+
for child in markup {
421+
children.append(renderer.visit(child))
422+
}
423+
424+
element.addChild(
425+
.element(named: "blockquote", children: children, attributes: ["class": "aside deprecated"])
426+
)
427+
}
428+
429+
private func separateSectionsIfNeeded(in element: XMLElement) {
430+
guard goal == .richness, ((element.children ?? []).last as? XMLElement)?.name == "section" else {
431+
return
432+
}
433+
434+
element.addChild(.element(named: "hr")) // Separate the sections with a thematic break
435+
}
436+
437+
private func addSeeAlso(_ seeAlso: SeeAlsoSection, to element: XMLElement) {
438+
separateSectionsIfNeeded(in: element)
439+
440+
element.addChildren(
441+
renderer.groupedSection(named: "See Also", groups: [
442+
.swift: seeAlso.taskGroups.map { group in
443+
.init(title: group.heading?.title, content: group.content, references: group.links.compactMap {
444+
$0.destination.flatMap { URL(string: $0) }
445+
})
446+
}
447+
])
448+
)
449+
}
276450

277451
// TODO: As a future enhancement, add another layer on top of this that creates complete HTML pages (both `<head>` and `<body>`) (rdar://165912669)
278452
}

0 commit comments

Comments
 (0)