Skip to content

Commit cf5b68e

Browse files
Generate uniform <h1> headings for all modules
In #223, we began generating default `<h1>` headings for modules. However, these headings were only generated for modules that had a description. If a module had no comment to begin with, it would not get a generated heading. Furthermore, since #280, each module's full name has been prominently displayed at the top of its page. These names are visually redundant with generated headings, which also use the full name. To unify the design and ensure that all modules have an `<h1>`, this commit converts each module's full name into an `<h1>` heading inside an `<hgroup>`, and uses postprocessing to pull any comment-based `<h1>` into the `<hgroup>`. This commit also renames the "Included Modules" section to "Inherits From", and moves any listed base class from the top of the page to the top of that section. This change focuses the `<h1>`, both visually and from an SEO perspective.
1 parent 947abf9 commit cf5b68e

File tree

10 files changed

+157
-88
lines changed

10 files changed

+157
-88
lines changed

lib/rdoc/generator/template/rails/_context.rhtml

+16-21
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
<div id="context">
22
<% unless (description = context.description).empty? %>
3-
<% unless context.comment_title %>
4-
<h1><%= context.title %></h1>
5-
<% end %>
63
<div class="description">
74
<%= description %>
85
</div>
96
<% end %>
107

118

9+
<%# File only: requires %>
1210
<% unless context.requires.empty? %>
13-
<!-- File only: requires -->
1411
<div class="content__divider">Required Files</div>
1512
<ul>
1613
<% context.requires.each do |req| %>
@@ -20,6 +17,20 @@
2017
<% end %>
2118

2219

20+
<%# Module only: ancestors %>
21+
<% unless context.is_a?(RDoc::TopLevel) || (ancestors = module_ancestors(context)).empty? %>
22+
<div class="content__divider">Inherits From</div>
23+
<ul>
24+
<% ancestors.each do |kind, ancestor| %>
25+
<li>
26+
<span class="kind"><%= kind %></span>
27+
<%= ancestor.is_a?(String) ? full_name(ancestor) : link_to(ancestor) %>
28+
</li>
29+
<% end %>
30+
</ul>
31+
<% end %>
32+
33+
2334
<% sections = context.sections.select { |s| s.title }.sort_by{ |s| s.title.to_s } %>
2435
<% unless sections.empty? then %>
2536
<!-- Sections -->
@@ -51,22 +62,6 @@
5162
</dl>
5263
<% end %>
5364

54-
<% unless context.includes.empty? %>
55-
<!-- Includes -->
56-
<div class="content__divider">Included Modules</div>
57-
<ul>
58-
<% context.includes.each do |inc| %>
59-
<li>
60-
<% if inc.module.is_a?(String) %>
61-
<%= full_name inc.name %>
62-
<% else %>
63-
<%= link_to inc.module %>
64-
<% end %>
65-
</li>
66-
<% end %>
67-
</ul>
68-
<% end %>
69-
7065

7166

7267
<% context.each_section do |section, constants, attributes| %>
@@ -185,7 +180,7 @@
185180
<ul>
186181
<% (context.modules.sort + context.classes.sort).each do |mod| %>
187182
<li>
188-
<span class="type"><%= mod.type.upcase %></span>
183+
<span class="kind"><%= mod.type %></span>
189184
<%= link_to mod %>
190185
</li>
191186
<% end %>

lib/rdoc/generator/template/rails/class.rhtml

+3-8
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,9 @@
1919
<%= include_template '_panel.rhtml' %>
2020

2121
<main id="content">
22-
<div class="content__full-name">
23-
<span class="qualifier"><%= klass.type %></span>
24-
<%= module_breadcrumbs klass %>
25-
<% if !klass.module? && superclass = klass.superclass %>
26-
<span class="qualifier">&lt;</span>
27-
<%= superclass.is_a?(String) ? full_name(superclass) : link_to(superclass) %>
28-
<% end %>
29-
</div>
22+
<hgroup class="content__title">
23+
<h1><span class="kind"><%= klass.type %></span> <%= module_breadcrumbs klass %></h1>
24+
</hgroup>
3025

3126
<%= include_template '_context.rhtml', {:context => klass} %>
3227

lib/rdoc/generator/template/rails/file.rhtml

+3-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616
<%= include_template '_panel.rhtml' %>
1717

1818
<main id="content">
19-
<h1 class="content__full-name">
20-
<span class="qualifier">File</span>
21-
<%= full_name file %>
22-
</h1>
19+
<hgroup class="content__title">
20+
<h1><span class="kind">File</span> <%= full_name file %></h1>
21+
</hgroup>
2322

2423
<% if source_url = github_url(file.relative_name) %>
2524
<p><%= link_to_external "View on GitHub", source_url %></p>

lib/rdoc/generator/template/rails/resources/css/main.css

+23-9
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@ a {
5555
a:has(> code:only-child) {
5656
text-decoration: none;
5757
}
58-
code a {
59-
text-decoration: none;
60-
}
6158

6259
/* TODO: Remove this hack when Firefox supports `:has()` */
6360
a code {
@@ -74,6 +71,15 @@ a code {
7471
display: none;
7572
}
7673

74+
.kind {
75+
font-family: monospace;
76+
font-weight: bold;
77+
}
78+
79+
.kind::after {
80+
content: " "; /* Ensure trailing space has width of 1 monospace char */
81+
}
82+
7783
table {
7884
border-collapse: collapse;
7985
}
@@ -380,18 +386,22 @@ html {
380386
font-style: normal;
381387
}
382388

383-
.content__full-name {
384-
font-family: monospace;
385-
font-size: 1.4em;
389+
.content__title :is(h1, p) {
390+
font-size: 1.6em;
386391
line-height: 1.25;
387-
padding: 0.125em 0;
392+
}
393+
394+
.content__title h1 {
395+
font-weight: normal;
388396

389397
margin-left: 1em;
390398
text-indent: -1em;
391399
}
392400

393-
.content__full-name .qualifier {
394-
font-weight: bold;
401+
.content__title p {
402+
font-style: italic;
403+
404+
margin-top: 0;
395405
}
396406

397407
.content__section-title {
@@ -513,6 +523,10 @@ html {
513523
* Description of method or module
514524
*/
515525

526+
#context > .description {
527+
margin-top: var(--space-lg);
528+
}
529+
516530
.description :is(h1, h2, h3, h4, h5, h6) {
517531
line-height: 1.25;
518532
padding: 0.125em 0;

lib/sdoc/generator.rb

-10
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,6 @@ class RDoc::Options
2727
attr_accessor :search_index
2828
end
2929

30-
module RDoc::Generator::Markup
31-
def comment_title
32-
@comment_title ||= @comment.to_s.match(/\A[=#] (.*)$/) {|match| match[1] }
33-
end
34-
35-
def title
36-
comment_title || full_name
37-
end
38-
end
39-
4030
class RDoc::Generator::SDoc
4131
RDoc::RDoc.add_generator self
4232

lib/sdoc/helpers.rb

+11
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,17 @@ def module_breadcrumbs(rdoc_module)
127127
"<code>#{crumbs.join("::<wbr>")}</code>"
128128
end
129129

130+
def module_ancestors(rdoc_module)
131+
ancestors = rdoc_module.includes.map { |inc| ["module", inc.module] }
132+
133+
if !rdoc_module.module? && superclass = rdoc_module.superclass
134+
superclass_name = superclass.is_a?(String) ? superclass : superclass.full_name
135+
ancestors.unshift(["class", superclass]) unless superclass_name == "Object"
136+
end
137+
138+
ancestors
139+
end
140+
130141
def method_signature(rdoc_method)
131142
if rdoc_method.call_seq
132143
rdoc_method.call_seq.split(/\n+/).map do |line|

lib/sdoc/postprocessor.rb

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def process(rendered)
1313
version_rails_guides_urls!(document)
1414
unlink_unintentional_ref_links!(document)
1515
style_ref_links!(document)
16+
unify_h1_headings!(document)
1617
highlight_code_blocks!(document)
1718

1819
document.to_s
@@ -88,6 +89,15 @@ def style_ref_links!(document)
8889
end
8990
end
9091

92+
def unify_h1_headings!(document)
93+
if h1 = document.at_css("#context > .description h1:first-child")
94+
if hgroup = document.at_css("#content > hgroup")
95+
h1.remove
96+
hgroup.add_child(%(<p>#{h1.inner_html}</p>))
97+
end
98+
end
99+
end
100+
91101
def highlight_code_blocks!(document)
92102
document.css(".description pre > code, .method__source pre > code").each do |element|
93103
code = element.inner_text

spec/helpers_spec.rb

+38
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,44 @@ module Foo; module Bar; module Qux; end; end; end
523523
end
524524
end
525525

526+
describe "#module_ancestors" do
527+
it "returns a list with the base class (if applicable) and included modules" do
528+
# RDoc chokes on ";" when parsing includes, so replace with "\n".
529+
top_level = rdoc_top_level_for <<~RUBY.gsub(";", "\n")
530+
module M1; end
531+
module M2; end
532+
class C1; end
533+
534+
module Foo; include M1; include M2; end
535+
class Bar < C1; include M2; include M1; end
536+
class Qux < Cx; include Foo; include Mx; end
537+
RUBY
538+
539+
m1, m2, c1, foo, bar, qux = %w[M1 M2 C1 Foo Bar Qux].map { |name| top_level.find_module_named(name) }
540+
541+
_(@helpers.module_ancestors(foo)).must_equal [["module", m1], ["module", m2]]
542+
_(@helpers.module_ancestors(bar)).must_equal [["class", c1], ["module", m2], ["module", m1]]
543+
_(@helpers.module_ancestors(qux)).must_equal [["class", "Cx"], ["module", foo], ["module", "Mx"]]
544+
end
545+
546+
it "excludes the default base class (Object) from the result" do
547+
# RDoc chokes on ";" when parsing includes, so replace with "\n".
548+
top_level = rdoc_top_level_for <<~RUBY.gsub(";", "\n")
549+
class Object; end
550+
class Foo; include M1; end
551+
RUBY
552+
553+
_(@helpers.module_ancestors(top_level.find_module_named("Object"))).must_equal [["class", "BasicObject"]]
554+
_(@helpers.module_ancestors(top_level.find_module_named("Foo"))).must_equal [["module", "M1"]]
555+
556+
top_level = rdoc_top_level_for <<~RUBY.gsub(";", "\n")
557+
class Foo; include M1; end
558+
RUBY
559+
560+
_(@helpers.module_ancestors(top_level.find_module_named("Foo"))).must_equal [["module", "M1"]]
561+
end
562+
end
563+
526564
describe "#method_signature" do
527565
it "returns the method signature wrapped in <code>" do
528566
method = rdoc_top_level_for(<<~RUBY).find_module_named("Foo").find_method("bar", false)

spec/postprocessor_spec.rb

+53
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,59 @@
124124
_(SDoc::Postprocessor.process(rendered)).must_include expected
125125
end
126126

127+
it "unifies <h1> headings for a context" do
128+
rendered = <<~HTML
129+
<div id="content">
130+
<hgroup><h1>module Foo</h1></hgroup>
131+
132+
<div id="context">
133+
<div class="description"><h1>The Foo</h1><p>Lorem ipsum.</p></div>
134+
</div>
135+
</div>
136+
HTML
137+
138+
expected = <<~HTML
139+
<div id="content">
140+
<hgroup><h1>module Foo</h1><p>The Foo</p></hgroup>
141+
142+
<div id="context">
143+
<div class="description"><p>Lorem ipsum.</p></div>
144+
</div>
145+
</div>
146+
HTML
147+
148+
_(SDoc::Postprocessor.process(rendered)).must_include expected
149+
end
150+
151+
it "does not relocate non-leading <h1> headings" do
152+
rendered = <<~HTML
153+
<div id="content">
154+
<hgroup><h1>module Foo</h1></hgroup>
155+
156+
<div id="context">
157+
<div class="description"><p>Lorem ipsum.</p><h1>Red Herring</h1></div>
158+
<div class="method">
159+
<div class="description"><h1>Red Herring</h1></div>
160+
</div>
161+
</div>
162+
</div>
163+
HTML
164+
165+
_(SDoc::Postprocessor.process(rendered)).must_include rendered
166+
end
167+
168+
it "does not relocate <h1> headings when <hgroup> is not present" do
169+
rendered = <<~HTML
170+
<div id="content">
171+
<div id="context">
172+
<div class="description"><h1>Main Page</h1></div>
173+
</div>
174+
</div>
175+
HTML
176+
177+
_(SDoc::Postprocessor.process(rendered)).must_include rendered
178+
end
179+
127180
it "highlights code blocks" do
128181
rendered = <<~HTML
129182
<div class="description">

spec/rdoc_generator_markup_spec.rb

-36
This file was deleted.

0 commit comments

Comments
 (0)