<?php
/**
* Dynamic Table of Contents Generator for WordPress
* Add this to your functions.php file
*/
// Function to generate dynamic table of contents
function generate_dynamic_toc($content) {
// Extract headings (h2, h3, h4) from content
preg_match_all('/<(h[2-4])[^>]*>(.*?)<\/h[2-4]>/i', $content, $headings);
if (empty($headings[0])) {
return $content; // Return original content if no headings found
}
$toc_html = '<div class="table-ysxin dynamic-toc">';
$toc_html .= '<p><strong>Table of Contents</strong></p>';
$toc_html .= '<ul>';
$updated_content = $content;
foreach ($headings[0] as $index => $full_heading) {
$heading_level = $headings[1][$index]; // h2, h3, h4
$heading_text = strip_tags($headings[2][$index]);
// Generate unique ID from heading text
$heading_id = sanitize_title($heading_text);
// Ensure unique IDs by adding index if needed
$heading_id = $heading_id . '-' . ($index + 1);
// Add ID to the heading in content
$new_heading = str_replace('>', ' id="' . $heading_id . '">', $full_heading);
$updated_content = str_replace($full_heading, $new_heading, $updated_content);
// Add to TOC with proper nesting
$indent_class = '';
if ($heading_level == 'h3') {
$indent_class = ' class="toc-h3"';
} elseif ($heading_level == 'h4') {
$indent_class = ' class="toc-h4"';
}
$toc_html .= '<li' . $indent_class . '><a href="#' . $heading_id . '">' . $heading_text . '</a></li>';
}
$toc_html .= '</ul>';
$toc_html .= '</div>';
// Insert TOC after first paragraph or at the beginning
$paragraphs = explode('</p>', $updated_content, 2);
if (count($paragraphs) > 1) {
$updated_content = $paragraphs[0] . '</p>' . $toc_html . $paragraphs[1];
} else {
$updated_content = $toc_html . $updated_content;
}
return $updated_content;
}
// Hook to automatically add TOC to single posts
add_filter('the_content', 'auto_add_toc_to_content');
function auto_add_toc_to_content($content) {
// Only add TOC to single posts (not pages, archives, etc.)
if (is_single() && in_the_loop() && is_main_query()) {
return generate_dynamic_toc($content);
}
return $content;
}
/**
* Alternative: Shortcode method
* Use [toc] shortcode to manually place TOC anywhere in your content
*/
add_shortcode('toc', 'toc_shortcode');
function toc_shortcode($atts) {
global $post;
$attributes = shortcode_atts(array(
'title' => 'Table of Contents',
'levels' => '2,3,4' // Which heading levels to include
), $atts);
$content = $post->post_content;
$levels = explode(',', $attributes['levels']);
$heading_pattern = '<(h[' . implode('', $levels) . '])[^>]*>(.*?)<\/h[' . implode('', $levels) . ']>';
preg_match_all('/' . $heading_pattern . '/i', $content, $headings);
if (empty($headings[0])) {
return ''; // Return empty if no headings found
}
$toc_html = '<div class="table-ysxin dynamic-toc">';
$toc_html .= '<p><strong>' . esc_html($attributes['title']) . '</strong></p>';
$toc_html .= '<ul>';
foreach ($headings[0] as $index => $full_heading) {
$heading_level = $headings[1][$index];
$heading_text = strip_tags($headings[2][$index]);
$heading_id = sanitize_title($heading_text) . '-' . ($index + 1);
$indent_class = '';
if ($heading_level == 'h3') {
$indent_class = ' class="toc-h3"';
} elseif ($heading_level == 'h4') {
$indent_class = ' class="toc-h4"';
}
$toc_html .= '<li' . $indent_class . '><a href="#' . $heading_id . '">' . esc_html($heading_text) . '</a></li>';
}
$toc_html .= '</ul>';
$toc_html .= '</div>';
return $toc_html;
}
/**
* Method 3: Template function for manual placement
* Call this function in your single.php template
*/
function display_post_toc($post_id = null) {
if (!$post_id) {
global $post;
$post_id = $post->ID;
}
$content = get_post_field('post_content', $post_id);
preg_match_all('/<(h[2-4])[^>]*>(.*?)<\/h[2-4]>/i', $content, $headings);
if (empty($headings[0])) {
return; // Don't display anything if no headings found
}
echo '<div class="table-ysxin dynamic-toc">';
echo '<p><strong>Table of Contents</strong></p>';
echo '<ul>';
foreach ($headings[0] as $index => $full_heading) {
$heading_level = $headings[1][$index];
$heading_text = strip_tags($headings[2][$index]);
$heading_id = sanitize_title($heading_text) . '-' . ($index + 1);
$indent_class = '';
if ($heading_level == 'h3') {
$indent_class = ' class="toc-h3"';
} elseif ($heading_level == 'h4') {
$indent_class = ' class="toc-h4"';
}
echo '<li' . $indent_class . '><a href="#' . $heading_id . '">' . esc_html($heading_text) . '</a></li>';
}
echo '</ul>';
echo '</div>';
}
/**
* CSS for styling the dynamic TOC
* Add this to your theme's style.css or enqueue it properly
*/
function enqueue_toc_styles() {
if (is_single()) {
wp_add_inline_style('your-theme-style', '
.table-ysxin.dynamic-toc {
background: #f9f9f9;
border: 1px solid #ddd;
padding: 20px;
margin: 20px 0;
border-radius: 5px;
}
.table-ysxin.dynamic-toc p {
margin: 0 0 15px 0;
font-size: 16px;
}
.table-ysxin.dynamic-toc ul {
margin: 0;
padding: 0;
list-style: none;
}
.table-ysxin.dynamic-toc li {
margin: 8px 0;
padding-left: 0;
}
.table-ysxin.dynamic-toc li.toc-h3 {
padding-left: 20px;
}
.table-ysxin.dynamic-toc li.toc-h4 {
padding-left: 40px;
}
.table-ysxin.dynamic-toc a {
color: #333;
text-decoration: none;
transition: color 0.3s ease;
}
.table-ysxin.dynamic-toc a:hover {
color: #0073aa;
text-decoration: underline;
}
');
}
}
add_action('wp_enqueue_scripts', 'enqueue_toc_styles');
/**
* JavaScript for smooth scrolling (optional)
*/
function enqueue_toc_scripts() {
if (is_single()) {
wp_add_inline_script('jquery', '
jQuery(document).ready(function($) {
$(".table-ysxin.dynamic-toc a[href^=\'#\']").click(function(e) {
e.preventDefault();
var target = $(this.hash);
if (target.length) {
$("html, body").animate({
scrollTop: target.offset().top - 100
}, 800);
}
});
});
');
}
}
add_action('wp_enqueue_scripts', 'enqueue_toc_scripts');
?>