@@ -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