Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Allow nested categories with recursive navigation. #982

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
7 changes: 6 additions & 1 deletion lib/jazzy/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,12 @@ def expand_path(path)
description: ['Custom navigation categories to replace the standard '\
'“Classes, Protocols, etc.”', 'Types not explicitly named '\
'in a custom category appear in generic groups at the end.',
'Example: https://git.io/v4Bcp'],
'You can add another category in the children array instead '\
'of using a type name, to create subcategories.',
'This can be repeated ad infinitum, provided '\
'your theme supports it.',
'Currently all integrated themes support a maximum of 3 levels.',
'Example: https://git.io/fNvGB'],
default: []

config_attr :custom_head,
Expand Down
51 changes: 43 additions & 8 deletions lib/jazzy/doc_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,24 @@ def self.doc_structure_for_docs(docs)
children = doc.children
.sort_by { |c| [c.nav_order, c.name, c.usr || ''] }
.flat_map do |child|
# FIXME: include arbitrarily nested extensible types
[{ name: child.name, url: child.url }] +
Array(child.children.select do |sub_child|
sub_child.type.swift_extensible? || sub_child.type.extension?
end).map do |sub_child|
{ name: "– #{sub_child.name}", url: sub_child.url }
end
if child.type == SourceDeclaration::Type.category
doc_structure_for_docs([child])
else
# FIXME: include arbitrarily nested extensible types
[{ name: child.name, url: child.url, children: nil}] +
Array(child.children.select do |sub_child|
sub_child.type.swift_extensible? || sub_child.type.extension?
end).map do |sub_child|
{ name: "– #{sub_child.name}", url: sub_child.url, children: nil }
end
end
end

{
section: doc.name,
url: doc.url,
children: children,
level: doc.level,
}
end
end
Expand Down Expand Up @@ -105,6 +111,13 @@ def self.each_doc(output_dir, docs, &block)
doc.children,
&block
)
if doc.subsections != nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change not required, subsections unused.

each_doc(
output_dir,
doc.subsections,
&block
)
end
end
end

Expand Down Expand Up @@ -383,6 +396,26 @@ def self.render_tasks(source_module, children)
end
end

def self.render_subsections(subsections, source_module)
subsections.map do |subsection|
overview = (subsection.abstract || '') + (subsection.discussion || '')
alternative_abstract = subsection.alternative_abstract
if alternative_abstract
overview = render(subsection, alternative_abstract) + overview
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to pull out these 5-6 lines instead of duplicating them.

tasks = render_tasks(source_module, subsection.children.select { |c| c.type != SourceDeclaration::Type.category })

{
name: subsection.name,
overview: overview,
uid: URI.encode(subsection.name),
url: subsection.url,
level: subsection.level,
tasks: tasks,
}
end
end

# rubocop:disable Metrics/MethodLength
# Build Mustache document from single parsed doc
# @param [Config] options Build options
Expand Down Expand Up @@ -412,7 +445,9 @@ def self.document(source_module, doc_model, path_to_root)
doc[:declaration] = doc_model.display_declaration
doc[:overview] = overview
doc[:structure] = source_module.doc_structure
doc[:tasks] = render_tasks(source_module, doc_model.children)
categories, children = doc_model.children.partition { |c| c.type == SourceDeclaration::Type.category }
doc[:tasks] = render_tasks(source_module, children)
doc[:subsections] = render_subsections(categories, source_module)
doc[:module_name] = source_module.name
doc[:author_name] = source_module.author_name
doc[:github_url] = source_module.github_url
Expand Down
78 changes: 78 additions & 0 deletions lib/jazzy/source_category.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
require 'jazzy/source_declaration'
require 'jazzy/config'
require 'jazzy/source_mark'
require 'jazzy/jazzy_markdown'

module Jazzy
# Category (group, contents) pages generated by jazzy
class SourceCategory < SourceDeclaration
extend Config::Mixin

def initialize(group, name, abstract, url_name, level = 1)
super()
self.type = SourceDeclaration::Type.category
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is supposed to be rename of Type.overview or something else - if a rename then need to delete overview and add a category? helper to get rid of the manual comparisons everywhere. Usually better to do refactorings in separate commits to new code, makes it easier for people to read.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a rename, an additional type. I think it makes the filter and partition calls in doc_builder more logical. Will add a helper.

Should I move this to a separate commit? Or just leave it for now?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. overview is the type used for these category pages. You could rename it (as long as this doesn't change existing URLs) but we don't need an additional type.

self.name = name
self.url_name = url_name
self.abstract = Markdown.render(abstract)
self.children = group
self.parameters = []
self.mark = SourceMark.new
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need a mark if it's not going into the children/tasks render structure.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I left it from your code. Will remove.

self.level = level
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubocop complains about this line because it can't figure out what the spaces are for: either use just 1 or line up the = with the line above.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A gotcha, will fix.

end

# Group root-level docs into custom categories or by type
def self.group_docs(docs)
custom_categories, docs =
group_custom_categories(docs, config.custom_categories)
type_categories, uncategorized = group_type_categories(
docs, custom_categories.any? ? 'Other ' : ''
)
custom_categories + type_categories + uncategorized
end

def self.group_custom_categories(docs, categories, level = 1)
group = categories.map do |category|
children = category['children'].flat_map do |child|
if child.is_a?(Hash)
# Nested category, recurse
docs_with_name, docs = group_custom_categories(docs, [child], level + 1)
else
# Doc name, find it
docs_with_name, docs = docs.partition { |doc| doc.name == child }
if docs_with_name.empty?
STDERR.puts(
'WARNING: No documented top-level declarations match ' \
"name \"#{child}\" specified in categories file",
)
end
end
docs_with_name
end
# Category config overrides alphabetization
children.each.with_index { |child, i| child.nav_order = i }
make_group(children, category['name'], '', nil, level)
end
[group.compact, docs]
end

def self.group_type_categories(docs, type_category_prefix)
group = SourceDeclaration::Type.all.map do |type|
children, docs = docs.partition { |doc| doc.type == type }
make_group(
children,
type_category_prefix + type.plural_name,
"The following #{type.plural_name.downcase} are available globally.",
type_category_prefix + type.plural_url_name,
)
end
[group.compact, docs]
end

def self.make_group(group, name, abstract, url_name = nil, level = 1)
group.reject! { |doc| doc.name.empty? }
unless group.empty?
SourceCategory.new(group, name, abstract, url_name, level)
end
end
end
end
2 changes: 2 additions & 0 deletions lib/jazzy/source_declaration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ def display_other_language_declaration
attr_accessor :end_line
attr_accessor :nav_order
attr_accessor :url_name
attr_accessor :level
attr_accessor :subsections
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(subsections not used any more)


def alternative_abstract
if file = alternative_abstract_file
Expand Down
9 changes: 9 additions & 0 deletions lib/jazzy/source_declaration/type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ def self.overview
Type.new('Overview')
end

def self.category
Type.new('Category')
end

def hash
kind.hash
end
Expand All @@ -150,6 +154,11 @@ def ==(other)
dash: 'Section',
}.freeze,

'Category' => {
jazzy: nil,
dash: 'Section',
}.freeze,

# Objective-C
'sourcekitten.source.lang.objc.decl.unexposed' => {
jazzy: 'Unexposed',
Expand Down
59 changes: 5 additions & 54 deletions lib/jazzy/sourcekitten.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require 'jazzy/source_declaration'
require 'jazzy/source_mark'
require 'jazzy/stats'
require 'jazzy/source_category'

ELIDED_AUTOLINK_TOKEN = '36f8f5912051ae747ef441d6511ca4cb'.freeze

Expand Down Expand Up @@ -59,58 +60,6 @@ def self.undocumented_abstract
).freeze
end

# Group root-level docs by custom categories (if any) and type
def self.group_docs(docs)
custom_categories, docs = group_custom_categories(docs)
type_categories, uncategorized = group_type_categories(
docs, custom_categories.any? ? 'Other ' : ''
)
custom_categories + type_categories + uncategorized
end

def self.group_custom_categories(docs)
group = Config.instance.custom_categories.map do |category|
children = category['children'].flat_map do |name|
docs_with_name, docs = docs.partition { |doc| doc.name == name }
if docs_with_name.empty?
STDERR.puts 'WARNING: No documented top-level declarations match ' \
"name \"#{name}\" specified in categories file"
end
docs_with_name
end
# Category config overrides alphabetization
children.each.with_index { |child, i| child.nav_order = i }
make_group(children, category['name'], '')
end
[group.compact, docs]
end

def self.group_type_categories(docs, type_category_prefix)
group = SourceDeclaration::Type.all.map do |type|
children, docs = docs.partition { |doc| doc.type == type }
make_group(
children,
type_category_prefix + type.plural_name,
"The following #{type.plural_name.downcase} are available globally.",
type_category_prefix + type.plural_url_name,
)
end
[group.compact, docs]
end

def self.make_group(group, name, abstract, url_name = nil)
group.reject! { |doc| doc.name.empty? }
unless group.empty?
SourceDeclaration.new.tap do |sd|
sd.type = SourceDeclaration::Type.overview
sd.name = name
sd.url_name = url_name
sd.abstract = Markdown.render(abstract)
sd.children = group
end
end
end

def self.sanitize_filename(doc)
unsafe_filename = doc.url_name || doc.name
sanitzation_enabled = Config.instance.use_safe_filenames
Expand All @@ -132,7 +81,9 @@ def self.make_doc_urls(docs)
subdir_for_doc(doc) +
[sanitize_filename(doc) + '.html']
).join('/')
doc.children = make_doc_urls(doc.children)
if doc.children.count > 0
doc.children = make_doc_urls(doc.children)
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think undo this change, original code is better.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be leftover from your branch with the other changes, will remove.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This url construction part leaves all category pages in the same directory. I'm not sure it's obvious that users should not duplicate subcategory names even if they are in different parent trees.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you rather have them moved to subdirectories or have a note in the config info?

I thought about adding a section to the README anyways.

Copy link
Collaborator

@johnfairh johnfairh Jul 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requiring subcategory name uniqueness feels like a footgun/defect creator. Putting the pages in subdirs should work.

else
# Don't create HTML page for this doc if it doesn't have children
# Instead, make its link a hash-link on its parent's page
Expand Down Expand Up @@ -806,7 +757,7 @@ def self.parse(sourcekitten_output, min_acl, skip_undocumented, inject_docs)
docs = docs.reject { |doc| doc.type.swift_enum_element? }
end
ungrouped_docs = docs
docs = group_docs(docs)
docs = SourceCategory.group_docs(docs)
make_doc_urls(docs)
autolink(docs, ungrouped_docs)
[docs, @stats]
Expand Down
38 changes: 37 additions & 1 deletion lib/jazzy/themes/fullwidth/assets/css/jazzy.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,12 @@ pre code {
.nav-group-tasks {
margin: $gutter/2 0;
padding: 0 0 0 $gutter/2;

.nav-group-name {
list-style-type: none;
border: 0px;
padding: 0px;
}
}

.nav-group-task {
Expand All @@ -332,6 +338,24 @@ pre code {
color: $navigation_task_color;
}

@mixin nav_heading($font-size: 1.5rem, $margin: 1.0rem 0px 0px 0px) {
font-size: $font-size;
font-weight: $heading_weight;
margin: $margin;
}

.nav-groups {
h1 {
@include nav_heading(1.75rem);
}
h2 {
@include nav_heading(1.5rem);
}
h3 {
@include nav_heading(1.25rem);
}
}

// ===========================================================================
//
// Content
Expand Down Expand Up @@ -379,6 +403,18 @@ pre code {
padding-top: 0px;
}

.nested-tasks {
.task-group {
margin-top: 10px;
}
}

.nested-tasks-line {
border: none;
border-top: $gray_border;
margin-top: 15px;
}

.task-name-container {
a[name] {
&:before {
Expand Down Expand Up @@ -612,4 +648,4 @@ form[role=search] {
.tt-suggestion.tt-cursor .doc-parent-name {
color: #fff;
}
}
}
2 changes: 1 addition & 1 deletion lib/jazzy/themes/fullwidth/templates/doc.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
</section>

{{> tasks}}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(can tidy this up)

</article>
</div>
{{> footer}}
Expand Down
11 changes: 1 addition & 10 deletions lib/jazzy/themes/fullwidth/templates/nav.mustache
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
<nav class="navigation">
<ul class="nav-groups">
{{#structure}}
<li class="nav-group-name">
<a class="nav-group-name-link" href="{{path_to_root}}{{url}}">{{section}}</a>
<ul class="nav-group-tasks">
{{#children}}
<li class="nav-group-task">
<a class="nav-group-task-link" href="{{path_to_root}}{{url}}">{{name}}</a>
</li>
{{/children}}
</ul>
</li>
{{> nav_section}}
{{/structure}}
</ul>
</nav>
Loading