Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [Enable TOC by default](#enable-toc-by-default)
- [Skip TOC](#skip-toc)
- [Skip TOC Sectionally](#skip-toc-sectionally)
- [TOC Only Direct Text](#toc-only-direct-text)
- [CSS Styling](#css-styling)
- [Custom CSS Class and ID](#custom-css-class-and-id)
- [Using Unordered/Ordered lists](#using-unorderedordered-lists)
Expand Down Expand Up @@ -162,6 +163,7 @@ toc:
sublist_class: ''
item_class: toc-entry
item_prefix: toc-
toc_only_direct_text: false
```

### TOC levels
Expand Down Expand Up @@ -232,6 +234,43 @@ toc:
- your_custom_skip_class_name
```

### toc_only_direct_text

By default, the TOC includes all text content from headings, including text within nested HTML elements. The `toc_only_direct_text` option allows you to extract only the direct text nodes from headings, excluding all child elements.

This is particularly useful when using custom HTML components or tags within headings (like badges, icons, or status indicators) that you don't want to appear in the table of contents.

```yml
# _config.yml
toc:
toc_only_direct_text: true # default: false
```

**Example:**

Given this heading:

```html
<h2>formatName <tag>Required</tag></h2>
```

- With `toc_only_direct_text: false` (default): TOC shows "formatName Required"
- With `toc_only_direct_text: true`: TOC shows "formatName"

**Per-page override:**

You can override this setting for specific pages using front matter:

```yml
---
layout: post
title: "My Post"
toc: true
toc_config:
toc_only_direct_text: true
---
```

### CSS Styling

The toc can be modified with CSS. The sample CSS is the following.
Expand Down
7 changes: 5 additions & 2 deletions lib/table_of_contents/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module TableOfContents
# jekyll-toc configuration class
class Configuration
attr_reader :toc_levels, :no_toc_class, :ordered_list, :no_toc_section_class,
:list_id, :list_class, :sublist_class, :item_class, :item_prefix
:list_id, :list_class, :sublist_class, :item_class, :item_prefix,
:toc_only_direct_text

DEFAULT_CONFIG = {
'min_level' => 1,
Expand All @@ -16,7 +17,8 @@ class Configuration
'list_class' => 'section-nav',
'sublist_class' => '',
'item_class' => 'toc-entry',
'item_prefix' => 'toc-'
'item_prefix' => 'toc-',
'toc_only_direct_text' => false
}.freeze

def initialize(options)
Expand All @@ -31,6 +33,7 @@ def initialize(options)
@sublist_class = options['sublist_class']
@item_class = options['item_class']
@item_prefix = options['item_prefix']
@toc_only_direct_text = options['toc_only_direct_text']
end

private
Expand Down
8 changes: 8 additions & 0 deletions lib/table_of_contents/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ def generate_toc_id(text)
.tr(' ', '-') # replace spaces with dash
CGI.escape(text)
end

def extract_text(node, only_direct_text: false)
if only_direct_text
node.children.select { |child| child.text? }.map { |child| child.text.strip }.reject(&:empty?).join(' ')
else
node.text.strip
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/table_of_contents/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def parse_content
(@doc.css(toc_headings) - @doc.css(toc_headings_in_no_toc_section))
.reject { |n| n.classes.include?(@configuration.no_toc_class) }
.inject([]) do |entries, node|
text = node.text
text = extract_text(node, only_direct_text: @configuration.toc_only_direct_text)
id = node.attribute('id') || generate_toc_id(text)

suffix_num = headers[id]
Expand Down
78 changes: 78 additions & 0 deletions test/parser/test_toc_only_direct_text.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

require 'test_helper'

class TestTocOnlyDirectText < Minitest::Test
def test_default_includes_nested_elements
html = '<h1>formatName <wa-tag>Required</wa-tag></h1>'
parser = Jekyll::TableOfContents::Parser.new(html)
toc_html = parser.build_toc

assert_includes(toc_html, 'formatName Required')
end

def test_toc_only_direct_text_excludes_all_elements
html = '<h1>formatName <wa-tag>Required</wa-tag></h1>'
parser = Jekyll::TableOfContents::Parser.new(html, 'toc_only_direct_text' => true)
toc_html = parser.build_toc

assert_includes(toc_html, 'formatName')
refute_includes(toc_html, 'Required')
end

def test_multiple_text_nodes_with_proper_spacing
html = '<h2>Text before <span>element</span> text after</h2>'
parser = Jekyll::TableOfContents::Parser.new(html, 'toc_only_direct_text' => true)
toc_html = parser.build_toc

assert_includes(toc_html, 'Text before text after')
refute_includes(toc_html, 'element')
end

def test_web_awesome_components
html = <<~HTML
<h1>formatName <wa-tag>Required</wa-tag></h1>
<h2>iconName <wa-badge>New</wa-badge></h2>
<h3>displayName <wa-icon>star</wa-icon></h3>
HTML

parser = Jekyll::TableOfContents::Parser.new(html, 'toc_only_direct_text' => true)
toc_html = parser.build_toc

assert_includes(toc_html, 'formatName')
refute_includes(toc_html, 'Required')
assert_includes(toc_html, 'iconName')
refute_includes(toc_html, 'New')
assert_includes(toc_html, 'displayName')
refute_includes(toc_html, 'star')
end

def test_various_element_types
html = <<~HTML
<h1>Title with <code>code</code> element</h1>
<h2>Title with <span>span</span> element</h2>
<h3>Title with <strong>strong</strong> element</h3>
<h4>Title with <em>emphasis</em> element</h4>
<h5>Title with <badge>badge</badge> element</h5>
HTML

parser = Jekyll::TableOfContents::Parser.new(html, 'toc_only_direct_text' => true)
toc_html = parser.build_toc

assert_includes(toc_html, 'Title with')
refute_includes(toc_html, 'code')
refute_includes(toc_html, 'span')
refute_includes(toc_html, 'strong')
refute_includes(toc_html, 'emphasis')
refute_includes(toc_html, 'badge')
end

def test_heading_with_only_nested_content
html = '<h1><span>Only Nested Content</span></h1>'
parser = Jekyll::TableOfContents::Parser.new(html, 'toc_only_direct_text' => true)
toc_html = parser.build_toc

# When there's no direct text, we should get an empty TOC entry or handle gracefully
refute_includes(toc_html, 'Only Nested Content')
end
end