<%= @context.type %> <%= module_breadcrumbs @context %>
diff --git a/lib/rdoc/generator/template/rails/file.rhtml b/lib/rdoc/generator/template/rails/file.rhtml index e0802e37..c930b353 100644 --- a/lib/rdoc/generator/template/rails/file.rhtml +++ b/lib/rdoc/generator/template/rails/file.rhtml @@ -4,7 +4,7 @@
<%= "" if @context.comment.empty? %>
<%= full_name @context %>
diff --git a/lib/rdoc/generator/template/rails/index.rhtml b/lib/rdoc/generator/template/rails/index.rhtml
index e17b1008..fc806e25 100644
--- a/lib/rdoc/generator/template/rails/index.rhtml
+++ b/lib/rdoc/generator/template/rails/index.rhtml
@@ -4,16 +4,16 @@
`
);
+ // Handle query button clicks.
+ document.addEventListener("click", ({ target }) => {
+ const query = target.closest(".query-button")?.dataset?.query;
+ if (query) {
+ search.query = query;
+ search.focus();
+ }
+ });
+
const query = new URL(document.location).searchParams.get("q");
if (query) {
search.feelingLucky(query);
@@ -40,14 +49,17 @@ document.addEventListener("turbo:load", () => {
}, { once: true });
document.addEventListener("turbo:load", function() {
- // Only initialize panel if not yet initialized
- if(!$('#panel .tree ul li').length) {
- $('#links').hide();
- var panel = new Searchdoc.Panel($('#panel'), tree);
- panel.toggle(JSON.parse($('meta[name="data-tree-keys"]').attr("content")));
- }
+ $('#links').hide();
+});
+
+
+// Hide menu on mobile when navigating to a named anchor on the current page.
+// For example, when clicking on a method in the method list.
+window.addEventListener("hashchange", () => {
+ document.getElementById("panel__state").checked = false;
});
+
// Because search results are in a `data-turbo-permanent` element, manually blur
// to hide them when navigating.
document.addEventListener("turbo:click", ({ target }) => {
@@ -56,19 +68,21 @@ document.addEventListener("turbo:click", ({ target }) => {
}
});
-// Keep scroll position for panel
+
+// Keep scroll position for search results across Turbo page loads.
(function() {
var scrollTop = 0;
addEventListener("turbo:before-render", function() {
- scrollTop = document.querySelector(".panel__tree").scrollTop
+ scrollTop = document.getElementById("results").scrollTop
})
addEventListener("turbo:render", function() {
- document.querySelector(".panel__tree").scrollTop = scrollTop
+ document.getElementById("results").scrollTop = scrollTop
})
})()
+
document.addEventListener("turbo:load", function () {
var backToTop = $("a.back-to-top");
diff --git a/lib/rdoc/generator/template/rails/resources/js/search.js b/lib/rdoc/generator/template/rails/resources/js/search.js
index 3ea893f0..af7529a6 100644
--- a/lib/rdoc/generator/template/rails/resources/js/search.js
+++ b/lib/rdoc/generator/template/rails/resources/js/search.js
@@ -18,7 +18,7 @@ export class Search {
}
search() {
- const bitPositions = this.compileQuery(this.inputEl.value);
+ const bitPositions = this.compileQuery(this.query);
let worst;
this.clearResults();
@@ -97,8 +97,7 @@ export class Search {
}
feelingLucky(query) {
- this.inputEl.value = query;
- this.search();
+ this.query = query;
this.clickCursor();
}
@@ -119,6 +118,15 @@ export class Search {
this.outputEl.classList.toggle("active", value);
}
+ get query() {
+ return this.inputEl.value;
+ }
+
+ set query(value) {
+ this.inputEl.value = value;
+ this.search();
+ }
+
get cursorEl() {
return this._cursorEl;
}
diff --git a/lib/rdoc/generator/template/rails/resources/js/searchdoc.js b/lib/rdoc/generator/template/rails/resources/js/searchdoc.js
deleted file mode 100644
index 6680d064..00000000
--- a/lib/rdoc/generator/template/rails/resources/js/searchdoc.js
+++ /dev/null
@@ -1,323 +0,0 @@
-Searchdoc = {};
-
-// navigation.js ------------------------------------------
-
-Searchdoc.Navigation = new function() {
- this.initNavigation = function() {
- var _this = this;
-
- $(document).keydown(function(e) {
- _this.onkeydown(e);
- }).keyup(function(e) {
- _this.onkeyup(e);
- });
- };
-
- this.navigationActive = function() {
- this.$searchInput ??= document.getElementById("search");
- this.$searchOutput ??= document.getElementById("results");
- return document.activeElement !== this.$searchInput &&
- !this.$searchOutput.contains(document.activeElement);
- };
-
-
- this.onkeyup = function(e) {
- if (!this.navigationActive()) return;
- switch (e.keyCode) {
- case 37: //Event.KEY_LEFT:
- case 38: //Event.KEY_UP:
- case 39: //Event.KEY_RIGHT:
- case 40: //Event.KEY_DOWN:
- this.clearMoveTimeout();
- break;
- }
- };
-
- this.onkeydown = function(e) {
- if (!this.navigationActive()) return;
-
- switch (e.keyCode) {
- case 37: //Event.KEY_LEFT:
- if (this.moveLeft()) e.preventDefault();
- break;
- case 38: //Event.KEY_UP:
- if (this.moveUp()) e.preventDefault();
- this.startMoveTimeout(false);
- break;
- case 39: //Event.KEY_RIGHT:
- if (this.moveRight()) e.preventDefault();
- break;
- case 40: //Event.KEY_DOWN:
- if (this.moveDown()) e.preventDefault();
- this.startMoveTimeout(true);
- break;
- case 13: //Event.KEY_RETURN:
- if(e.target.dataset["turbo"]) { break; }
- if (this.$current) this.select(this.$current);
- break;
- }
- };
-
- this.clearMoveTimeout = function() {
- clearTimeout(this.moveTimeout);
- this.moveTimeout = null;
- };
-
- this.startMoveTimeout = function(isDown) {
- if (this.moveTimeout) this.clearMoveTimeout();
- var _this = this;
-
- var go = function() {
- if (!_this.moveTimeout) return;
- _this[isDown ? 'moveDown' : 'moveUp']();
- _this.moveTimout = setTimeout(go, 100);
- };
- this.moveTimeout = setTimeout(go, 200);
- };
-
- this.moveRight = function() {};
-
- this.moveLeft = function() {};
-
- this.move = function(isDown) {};
-
- this.moveUp = function() {
- return this.move(false);
- };
-
- this.moveDown = function() {
- return this.move(true);
- };
-};
-
-
-// scrollIntoView.js --------------------------------------
-
-function scrollIntoView(element, view) {
- var offset, viewHeight, viewScroll, height;
- offset = element.offsetTop;
- height = element.offsetHeight;
- viewHeight = view.offsetHeight;
- viewScroll = view.scrollTop;
- if (offset - viewScroll + height > viewHeight) {
- view.scrollTop = offset - viewHeight + height;
- }
- if (offset < viewScroll) {
- view.scrollTop = offset;
- }
-}
-
-// panel.js -----------------------------------------------
-
-Searchdoc.Panel = function(element, tree) {
- this.$element = $(element);
- this.$current = null;
- this.tree = new Searchdoc.Tree($('.tree', element), tree, this);
-};
-
-Searchdoc.Panel.prototype = $.extend({}, Searchdoc.Navigation, new function() {
- this.toggle = function(keys) {
- var keysIndex = 0;
- var _tree = this.tree;
- var _this = this;
- $.each(_tree.$list[0].children, function(i, li) {
- if(keys.length > keysIndex) {
- $li = $(li);
- if($li.text().split(" ")[0] == keys[keysIndex]) {
- if($li.find('.icon').length > 0) {
- _tree.toggle($li);
- }
- keysIndex += 1;
- if(keysIndex == keys.length) {
- _tree.highlight($li)
- }
- }
- }
- });
- };
-
- this.open = function(src) {
- var location = $("base").attr("href") + src;
- Turbo.visit(location);
- if (this.highlight) this.highlight(src);
- };
-});
-
-// tree.js ------------------------------------------------
-
-Searchdoc.Tree = function(element, tree, panel) {
- this.$element = $(element);
- this.$list = $('ul', element);
- this.tree = tree;
- this.panel = panel;
- this.init();
-};
-
-Searchdoc.Tree.prototype = $.extend({}, Searchdoc.Navigation, new function() {
- this.init = function() {
- var stopper = document.createElement('li');
- stopper.className = 'stopper';
- this.$list[0].appendChild(stopper);
- for (var i = 0, l = this.tree.length; i < l; i++) {
- buildAndAppendItem.call(this, this.tree[i], 0, stopper);
- }
- var _this = this;
- this.$list.click(function(e) {
- var $target = $(e.target),
- $li = $target.closest('li');
- if ($target.hasClass('icon')) {
- _this.toggle($li);
- } else {
- _this.select($li);
- }
- });
-
- this.initNavigation();
- };
-
- this.select = function($li) {
- this.highlight($li);
- var path = $li[0].searchdoc_tree_data.path;
- if (path) this.panel.open(path);
- };
-
- this.highlight = function($li) {
- if (this.$current) this.$current.removeClass('current');
- this.$current = $li.addClass('current');
- };
-
- this.toggle = function($li) {
- var closed = !$li.hasClass('closed'),
- children = $li[0].searchdoc_tree_data.children;
- $li.toggleClass('closed');
- for (var i = 0, l = children.length; i < l; i++) {
- toggleVis.call(this, $(children[i].li), !closed);
- }
- };
-
- this.moveRight = function() {
- if (!this.$current) {
- this.highlight(this.$list.find('li:first'));
- return;
- }
- if (this.$current.hasClass('closed')) {
- this.toggle(this.$current);
- }
- };
-
- this.moveLeft = function() {
- if (!this.$current) {
- this.highlight(this.$list.find('li:first'));
- return;
- }
- if (!this.$current.hasClass('closed')) {
- this.toggle(this.$current);
- } else {
- var level = this.$current[0].searchdoc_tree_data.level;
- if (level === 0) return;
- var $next = this.$current.prevAll('li.level_' + (level - 1) + ':visible:first');
- this.$current.removeClass('current');
- $next.addClass('current');
- scrollIntoView($next[0], this.$element[0]);
- this.$current = $next;
- }
- };
-
- this.move = function(isDown) {
- if (!this.$current) {
- this.highlight(this.$list.find('li:first'));
- return true;
- }
- var next = this.$current[0];
- if (isDown) {
- do {
- next = next.nextSibling;
- if (next && next.style && next.style.display != 'none') break;
- } while (next);
- } else {
- do {
- next = next.previousSibling;
- if (next && next.style && next.style.display != 'none') break;
- } while (next);
- }
- if (next && next.className.indexOf('stopper') == -1) {
- this.$current.removeClass('current');
- $(next).addClass('current');
- scrollIntoView(next, this.$element[0]);
- this.$current = $(next);
- }
- return true;
- };
-
- function toggleVis($li, show) {
- var closed = $li.hasClass('closed'),
- children = $li[0].searchdoc_tree_data.children;
- $li.css('display', show ? '' : 'none');
- if (!show && this.$current && $li[0] == this.$current[0]) {
- this.$current.removeClass('current');
- this.$current = null;
- }
- for (var i = 0, l = children.length; i < l; i++) {
- toggleVis.call(this, $(children[i].li), show && !closed);
- }
- }
-
- function buildAndAppendItem(item, level, before) {
- var li = renderItem(item, level),
- list = this.$list[0];
- item.li = li;
- list.insertBefore(li, before);
- for (var i = 0, l = item[3].length; i < l; i++) {
- buildAndAppendItem.call(this, item[3][i], level + 1, before);
- }
- return li;
- }
-
- function renderItem(item, level) {
- var li = document.createElement('li'),
- cnt = document.createElement('div'),
- h1 = document.createElement('h1'),
- p = document.createElement('p'),
- icon, i;
-
- li.appendChild(cnt);
- li.style.paddingLeft = getOffset(level);
- cnt.className = 'entry';
- if (!item[1]) li.className = 'empty ';
- cnt.appendChild(h1);
- // cnt.appendChild(p);
- h1.appendChild(document.createTextNode(item[0]));
- // p.appendChild(document.createTextNode(item[4]));
- if (item[2]) {
- i = document.createElement('i');
- i.appendChild(document.createTextNode(item[2]));
- h1.appendChild(i);
- }
- if (item[3].length > 0) {
- icon = document.createElement('div');
- icon.className = 'icon';
- cnt.appendChild(icon);
- }
-
- // user direct assignement instead of $()
- // it's 8x faster
- // $(li).data('path', item[1])
- // .data('children', item[3])
- // .data('level', level)
- // .css('display', level == 0 ? '' : 'none')
- // .addClass('level_' + level)
- // .addClass('closed');
- li.searchdoc_tree_data = {
- path: item[1],
- children: item[3],
- level: level
- };
- li.style.display = level === 0 ? '' : 'none';
- li.className += 'level_' + level + ' closed';
- return li;
- }
-
- function getOffset(level) {
- return 5 + 18 * level + 'px';
- }
-});
diff --git a/lib/sdoc/generator.rb b/lib/sdoc/generator.rb
index fd61cd66..7fe8c77c 100644
--- a/lib/sdoc/generator.rb
+++ b/lib/sdoc/generator.rb
@@ -10,12 +10,6 @@
require "sdoc/search_index"
require "sdoc/version"
-class RDoc::ClassModule
- def with_documentation?
- document_self_or_methods || classes_and_modules.any?{ |c| c.with_documentation? }
- end
-end
-
class RDoc::Options
attr_accessor :github
attr_accessor :search_index
@@ -26,8 +20,6 @@ class RDoc::Generator::SDoc
DESCRIPTION = 'Searchable HTML documentation'
- TREE_FILE = File.join 'panel', 'tree.js'
-
FILE_DIR = 'files'
CLASS_DIR = 'classes'
@@ -86,7 +78,6 @@ def generate
copy_resources
generate_search_index
generate_file_links
- generate_class_tree
generate_index_file
generate_file_files
@@ -153,36 +144,6 @@ def generate_file_links
render_file("file_links.rhtml", "panel/file_links.html", @files)
end
- ### Create class tree structure and write it as json
- def generate_class_tree
- debug_msg "Generating class tree"
- topclasses = @classes.select {|klass| !(RDoc::ClassModule === klass.parent) }
- tree = generate_file_tree + generate_class_tree_level(topclasses)
- file = @output_dir + TREE_FILE
- debug_msg " writing class tree to %s" % file
- File.open(file, "w", 0644) do |f|
- f.write('var tree = '); f.write(tree.to_json(:max_nesting => 0))
- end unless @options.dry_run
- end
-
- ### Recursivly build class tree structure
- def generate_class_tree_level(classes, visited = {})
- tree = []
- classes.select do |klass|
- !visited[klass] && klass.with_documentation?
- end.sort.each do |klass|
- visited[klass] = true
- item = [
- klass.name,
- klass.document_self_or_methods ? klass.path : '',
- klass.module? ? '' : (klass.superclass ? " < #{String === klass.superclass ? klass.superclass : klass.superclass.full_name}" : ''),
- generate_class_tree_level(klass.classes_and_modules, visited)
- ]
- tree << item
- end
- tree
- end
-
def generate_search_index
debug_msg "Generating search index"
unless @options.dry_run
@@ -202,41 +163,4 @@ def copy_resources
debug_msg "Copying #{resources_path}/** to #{@output_dir}/**"
FileUtils.cp_r resources_path.to_s, @output_dir.to_s unless @options.dry_run
end
-
- class FilesTree
- attr_reader :children
- def add(path, url)
- path = path.split(File::SEPARATOR) unless Array === path
- @children ||= {}
- if path.length == 1
- @children[path.first] = url
- else
- @children[path.first] ||= FilesTree.new
- @children[path.first].add(path[1, path.length], url)
- end
- end
- end
-
- def generate_file_tree
- if @files.length > 1
- @files_tree = FilesTree.new
- @files.each do |file|
- @files_tree.add(file.relative_name, file.path)
- end
- [['', '', 'files', generate_file_tree_level(@files_tree)]]
- else
- []
- end
- end
-
- def generate_file_tree_level(tree)
- tree.children.keys.sort.map do |name|
- child = tree.children[name]
- if String === child
- [name, child, '', []]
- else
- ['', '', name, generate_file_tree_level(child)]
- end
- end
- end
end
diff --git a/lib/sdoc/helpers.rb b/lib/sdoc/helpers.rb
index 96879ce8..cbf77544 100644
--- a/lib/sdoc/helpers.rb
+++ b/lib/sdoc/helpers.rb
@@ -37,6 +37,11 @@ def link_to_external(text, url, html_attributes = {})
link_to(text, url, html_attributes)
end
+ def button_to_search(query, display_name: full_name(query))
+ query = query.full_name if query.is_a?(RDoc::CodeObject)
+ %()
+ end
+
def full_name(named)
named = named.full_name if named.is_a?(RDoc::CodeObject)
"" if @context.comment.empty? %>
<%= full_name @context %>
diff --git a/lib/rdoc/generator/template/rails/index.rhtml b/lib/rdoc/generator/template/rails/index.rhtml
index e17b1008..fc806e25 100644
--- a/lib/rdoc/generator/template/rails/index.rhtml
+++ b/lib/rdoc/generator/template/rails/index.rhtml
@@ -4,16 +4,16 @@
<%= page_title %>
- <% inline "_head.rhtml", {tree_keys: []} %>
+ <% inline "_head.rhtml" %>
Skip to Content
Skip to Search
- <% inline "_panel.rhtml" %>
+ <% inline("_panel.rhtml") { inline("_index_nav.rhtml") } %>
-
+
<% inline "_context.rhtml" %>
diff --git a/lib/rdoc/generator/template/rails/resources/css/main.css b/lib/rdoc/generator/template/rails/resources/css/main.css
index 85b10fa6..a492bf78 100644
--- a/lib/rdoc/generator/template/rails/resources/css/main.css
+++ b/lib/rdoc/generator/template/rails/resources/css/main.css
@@ -67,6 +67,27 @@ a code {
background-size: 1.1em;
}
+.query-button {
+ border: none;
+ text-align: left;
+ font-family: inherit;
+ font-size: 1em;
+
+ background: url('../i/filter.svg') no-repeat;
+ background-position-y: 1px;
+ background-size: 1.1em;
+ padding: 0 0 0 1.3em;
+ color: var(--link-color);
+
+ word-break: break-word;
+}
+
+@media (hover: hover) {
+ .query-button:hover {
+ color: var(--link-hover-color);
+ }
+}
+
.kind {
font-family: monospace;
}
@@ -89,30 +110,6 @@ th
font-weight: bold;
}
-.methods dt
-{
- width: 1em;
- font-size: 1.5em;
- color:#AAA;
- position: absolute;
-}
-
-.methods dd
-{
- margin-top: var(--space);
- min-height: 1.8em;
- -height: 1.8em;
-}
-
-
-.methods ul li
-{
- margin-right: 0.7em;
- margin-left: 0;
- list-style: none;
- display: inline;
-}
-
.attr-rw {
padding-right: 1em;
text-align: center;
@@ -289,7 +286,7 @@ html {
box-shadow: 1px 1px 4px color-mix(in srgb, currentColor 50%, transparent) inset;
}
-.panel__results, .panel__tree {
+.panel__results, .panel__nav {
position: relative;
height: calc(100dvh - var(--banner-height) - var(--search-height));
width: var(--panel-width);
@@ -300,7 +297,7 @@ html {
}
/* Force scrolling in order to always contain scroll events (on mobile) */
-:is(.panel__results, .panel__tree)::after {
+:is(.panel__results, .panel__nav)::after {
content: "";
height: 1px;
width: 1px;
@@ -311,7 +308,7 @@ html {
.panel__results:not(.active),
.panel__search:placeholder-shown ~ .panel__results,
-.panel__search:not(:placeholder-shown) ~ .panel__results.active ~ .panel__tree {
+.panel__search:not(:placeholder-shown) ~ .panel__results.active ~ .panel__nav {
/* `display: none` disables animations, so simulate it instead */
max-height: 0;
max-width: 0;
@@ -320,10 +317,6 @@ html {
opacity: 0;
}
-.panel__tree {
- overflow-x: hidden;
-}
-
/*
* Navigation panel - Search results
@@ -388,6 +381,69 @@ html {
}
+/*
+ * Navigation panel - Page nav
+ */
+
+.panel__nav {
+ padding: var(--space);
+ font-size: 0.95em;
+ line-height: calc(0.85 * var(--line-height));
+}
+
+
+* + .nav__outline {
+ margin-top: var(--space-lg);
+}
+
+.nav__outline ul {
+ margin-top: var(--space-sm);
+ padding-left: 1em;
+}
+
+.nav__outline ul ul ul {
+ display: none; /* Only show two levels deep */
+}
+
+.nav__outline li {
+ word-break: break-word;
+}
+
+.nav__outline a {
+ text-decoration: underline;
+}
+
+
+.nav__heading {
+ margin-top: var(--space-lg);
+ font-size: 1.3em;
+}
+
+.nav__heading + .nav__list {
+ margin-top: var(--space-sm);
+}
+
+
+.nav__list {
+ padding: 0;
+ list-style: none;
+}
+
+.nav__list li {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+
+.nav__method-link code::before {
+ content: "#";
+}
+
+.nav__method-link--singleton code::before {
+ content: "::";
+}
+
+
/*
* Navigation panel on desktop
*/
@@ -411,7 +467,7 @@ html {
width: 60ch;
}
- :is(.panel__results, .panel__tree)::after {
+ :is(.panel__results, .panel__nav)::after {
display: none;
}
}
@@ -434,29 +490,29 @@ html {
}
}
-:where(#content) *,
-:where(#content) :is(br, wbr) {
+:where(.content) *,
+:where(.content) :is(br, wbr) {
margin: 0;
}
-:where(#content) * + * {
+:where(.content) * + * {
margin-top: var(--space);
}
-:where(#content) :is(ol, ul, dd) {
+:where(.content) :is(ol, ul, dd) {
padding: 0 0 0 1.25em;
}
-:where(#content) * + :is(li, dd) {
+:where(.content) * + :is(li, dd) {
margin-top: var(--space-sm);
}
/* Increase top margin for list items when any item has more than one paragraph */
-:where(#content :is(ol, ul):has(> li > p:not(:only-child))) * + li {
+:where(.content :is(ol, ul):has(> li > p:not(:only-child))) * + li {
margin-top: var(--space);
}
-:where(#content) code {
+:where(.content) code {
font-style: normal;
}
diff --git a/lib/rdoc/generator/template/rails/resources/css/panel.css b/lib/rdoc/generator/template/rails/resources/css/panel.css
deleted file mode 100644
index 09fe1051..00000000
--- a/lib/rdoc/generator/template/rails/resources/css/panel.css
+++ /dev/null
@@ -1,166 +0,0 @@
-/* Panel (begin) */
-:root {
- --panel-hover-background-color: #d0d0d0;
- --panel-alternate-background-color: #F0EFEF;
- --panel-current-background-color: #B61D1D;
- --panel-highlight-color: #000;
-}
-
- /* Tree (begin) */ /**/
- .panel .tree
- {
- font-family: "Helvetica Neue", Arial, sans-serif;
- }
-
- .panel .tree ul:first-child
- {
- background: url(../i/tree_bg.svg);
- background-size: 1px 60px;
- }
-
- .panel .tree ul {
- list-style: none;
- margin: 0;
- padding: 0;
- }
-
- .panel .tree li
- {
- cursor: pointer;
- height: 30px;
- line-height: 100%;
- margin: 0;
- }
-
-
- .panel .tree li .entry
- {
- padding-left: 18px;
- padding-top: 5px;
- height: 18px;
- position: relative;
- }
-
- .panel .tree li .icon
- {
- width: 10px;
- height: 9px;
- background: url(../i/arrow-down.svg);
- background-size: 10px;
- position: absolute;
- left: 1px;
- top: 8px;
- cursor: default;
- }
-
- .panel .tree li.closed .icon
- {
- background: url(../i/arrow-right.svg);
- background-size: 10px;
- }
-
- .panel .tree ul li h1
- {
- font-size: 13px;
- font-weight: normal;
- color: var(--text-color);
- margin-top: 0;
- margin-bottom: 2px;
- white-space: nowrap;
- }
-
- .panel .tree ul li p
- {
- font-size: 11px;
- color: #666;
- margin-bottom: 2px;
- white-space: nowrap;
- }
-
- .panel .tree ul li h1 i
- {
- color: #999;
- font-style: normal;
- }
-
- .panel .tree ul li.current h1 i
- {
- color: #CCC;
- }
-
- .panel .tree ul li.empty
- {
- cursor: text;
- }
-
- .panel .tree ul li.empty h1,
- .panel .tree ul li.empty p
- {
- color: #666;
- font-style: italic;
- }
-
- .panel .tree ul li.current
- {
- background: var(--panel-current-background-color);
- }
-
- .panel .tree ul li.current .icon
- {
- background: url(../i/arrow-down-current.svg);
- background-size: 10px;
- }
-
- .panel .tree ul li.current.closed .icon
- {
- background: url(../i/arrow-right-current.svg);
- background-size: 10px;
- }
-
- .panel .tree ul li.current h1
- {
- color: #FFF;
- }
-
- .panel .tree ul li.current p
- {
- color: #CCC;
- }
-
- .panel .tree ul li.current.empty h1,
- .panel .tree ul li.current.empty p
- {
- color: #999;
- }
-
- .panel .tree ul li:hover
- {
- background: var(--panel-hover-background-color);
- }
-
- .panel .tree ul li.current:hover
- {
- background: var(--panel-current-background-color);
- }
-
- .panel .tree .stopper
- {
- display: none;
- }
-
- @media (prefers-color-scheme: dark) {
- :root {
- --panel-hover-background-color: #3b3b3b;
- --panel-alternate-background-color: #000000;
- --panel-highlight-color: #fff;
- }
-
- .panel .tree ul:first-child {
- background: url(../i/dark/tree_bg.svg);
- background-size: 1px 60px;
- }
- }
-
- /* Tree (end) */ /**/
-
-/* Panel (end) */
diff --git a/lib/rdoc/generator/template/rails/resources/i/arrow-down-current.svg b/lib/rdoc/generator/template/rails/resources/i/arrow-down-current.svg
deleted file mode 100644
index abf5a99d..00000000
--- a/lib/rdoc/generator/template/rails/resources/i/arrow-down-current.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/lib/rdoc/generator/template/rails/resources/i/arrow-down.svg b/lib/rdoc/generator/template/rails/resources/i/arrow-down.svg
deleted file mode 100644
index 88c5138e..00000000
--- a/lib/rdoc/generator/template/rails/resources/i/arrow-down.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/lib/rdoc/generator/template/rails/resources/i/arrow-right-current.svg b/lib/rdoc/generator/template/rails/resources/i/arrow-right-current.svg
deleted file mode 100644
index 4fccc84f..00000000
--- a/lib/rdoc/generator/template/rails/resources/i/arrow-right-current.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/lib/rdoc/generator/template/rails/resources/i/arrow-right.svg b/lib/rdoc/generator/template/rails/resources/i/arrow-right.svg
deleted file mode 100644
index 5b1569e4..00000000
--- a/lib/rdoc/generator/template/rails/resources/i/arrow-right.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/lib/rdoc/generator/template/rails/resources/i/dark/tree_bg.svg b/lib/rdoc/generator/template/rails/resources/i/dark/tree_bg.svg
deleted file mode 100644
index 95dd2390..00000000
--- a/lib/rdoc/generator/template/rails/resources/i/dark/tree_bg.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/lib/rdoc/generator/template/rails/resources/i/filter.svg b/lib/rdoc/generator/template/rails/resources/i/filter.svg
new file mode 100644
index 00000000..0e7eafa5
--- /dev/null
+++ b/lib/rdoc/generator/template/rails/resources/i/filter.svg
@@ -0,0 +1 @@
+
diff --git a/lib/rdoc/generator/template/rails/resources/i/tree_bg.svg b/lib/rdoc/generator/template/rails/resources/i/tree_bg.svg
deleted file mode 100644
index fd6a2137..00000000
--- a/lib/rdoc/generator/template/rails/resources/i/tree_bg.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/lib/rdoc/generator/template/rails/resources/js/main.js b/lib/rdoc/generator/template/rails/resources/js/main.js
index 1851f58b..61604817 100644
--- a/lib/rdoc/generator/template/rails/resources/js/main.js
+++ b/lib/rdoc/generator/template/rails/resources/js/main.js
@@ -33,6 +33,15 @@ document.addEventListener("turbo:load", () => {
#{named.split(%r"(?<=./|.::)").map { |part| h part }.join("")}
"
@@ -97,10 +102,29 @@ def page_description(leading_html, max_length: 160)
h text
end
- def group_by_first_letter(rdoc_objects)
- rdoc_objects.sort_by(&:name).group_by do |object|
- object.name[/^[a-z]/i]&.upcase || "#"
+ def outline(context)
+ comment = context.respond_to?(:comment_location) ? context.comment_location : context.comment
+ return if comment.empty?
+
+ headings = context.parse(comment).table_of_contents
+ headings.shift if headings.one? { |heading| heading.level == 1 } && headings[0].level == 1
+
+ _outline_list(context, headings)
+ end
+
+ def _outline_list(context, headings, following: 0)
+ items = []
+ while headings[0] && headings[0].level > following
+ items << _outline_list_item(context, headings)
end
+ "- #{items.join}
" do
_(@helpers.full_name("Foo")).must_equal "Foo
"
@@ -470,27 +494,96 @@ module Foo; module Bar; module Qux; end; end; end
end
end
- describe "#group_by_first_letter" do
- it "groups RDoc objects by the first letter of their #name" do
+ describe "#outline" do
+ def expected(html, context:)
+ html.gsub(/\s/, "").gsub(/([^<]+)/, ' \1')
+ end
+
+ it "renders a nested list of heading links" do
context = rdoc_top_level_for(<<~RUBY).find_module_named("Foo")
- module Foo
- def bar; end
- def _bar; end
- def baa; end
+ # == L2-1
+ # == L2-2
+ # === L3-1
+ # ==== L4-1
+ # ===== L5-1
+ # ====== L6-1
+ # == L2-3
+ module Foo; end
+ RUBY
- def qux; end
- def _qux; end
- def Qux; end
- end
+ _(@helpers.outline(context)).must_equal expected(<<~HTML, context: context)
+
+ - L2-1
+ - L2-2
+ - L3-1
+ - L4-1
+ - L5-1
+ - L6-1
+
+
+
+
+ - L2-3
+
+ HTML
+ end
+
+ it "handles skipped heading levels" do
+ context = rdoc_top_level_for(<<~RUBY).find_module_named("Foo")
+ # === L3-1
+ # ===== L5-1
+ # == L2-1
+ # ==== L4-1
+ module Foo; end
RUBY
- expected = {
- "#" => [context.find_method("_bar", false), context.find_method("_qux", false)],
- "B" => [context.find_method("baa", false), context.find_method("bar", false)],
- "Q" => [context.find_method("Qux", false), context.find_method("qux", false)],
- }
+ _(@helpers.outline(context)).must_equal expected(<<~HTML, context: context)
+
+ - L3-1
+ - L5-1
+
+ - L2-1
+ - L4-1
+
+
+ HTML
+ end
+
+ it "omits the h1 heading when it is primary" do
+ context = rdoc_top_level_for(<<~RUBY).find_module_named("Foo")
+ # = L1-1
+ # == L2-1
+ # === L3-1
+ # == L2-2
+ module Foo; end
+ RUBY
- _(@helpers.group_by_first_letter(context.method_list)).must_equal expected
+ _(@helpers.outline(context)).must_equal expected(<<~HTML, context: context)
+
+ - L2-1
+ - L3-1
+
+ - L2-2
+
+ HTML
+ end
+
+ it "preserves all h1 headings when any are non-primary" do
+ context = rdoc_top_level_for(<<~RUBY).find_module_named("Foo")
+ # = L1-1
+ # == L2-1
+ # = L1-2
+ module Foo; end
+ RUBY
+
+ _(@helpers.outline(context)).must_equal expected(<<~HTML, context: context)
+
+ - L1-1
+ - L2-1
+
+ - L1-2
+
+ HTML
end
end
@@ -527,6 +620,28 @@ def ul(items)
end
end
+ describe "#top_modules" do
+ it "returns top-level classes and modules in sorted order" do
+ top_level = rdoc_top_level_for <<~RUBY
+ class Foo; module Hoge; end; end
+ module Bar; class Fuga; end; end
+ RUBY
+
+ _(@helpers.top_modules(top_level.store)).
+ must_equal [top_level.find_module_named("Bar"), top_level.find_module_named("Foo")]
+ end
+
+ it "handles flattened class and module declarations" do
+ top_level = rdoc_top_level_for <<~RUBY
+ class Foo::Hoge; end
+ module Bar::Fuga; end
+ RUBY
+
+ _(@helpers.top_modules(top_level.store)).
+ must_equal [top_level.find_module_named("Bar"), top_level.find_module_named("Foo")]
+ end
+ end
+
describe "#module_breadcrumbs" do
it "renders links for each of the module's parents" do
top_level = rdoc_top_level_for <<~RUBY
@@ -599,6 +714,28 @@ class Foo; include M1; end
end
end
+ describe "#module_methods" do
+ it "returns all methods of a given module, sorted by definition scope and name" do
+ rdoc_module = rdoc_top_level_for(<<~RUBY).find_module_named("Foo")
+ module Foo
+ def foo; end
+ class << self
+ private def foo; end
+ end
+ private def bar; end
+ def self.bar; end
+ end
+ RUBY
+
+ _(@helpers.module_methods(rdoc_module)).must_equal [
+ rdoc_module.find_method("bar", true),
+ rdoc_module.find_method("foo", true),
+ rdoc_module.find_method("bar", false),
+ rdoc_module.find_method("foo", false),
+ ]
+ end
+ end
+
describe "#method_signature" do
it "returns the method signature wrapped in " do
method = rdoc_top_level_for(<<~RUBY).find_module_named("Foo").find_method("bar", false)