/**
* Zeevou Container-Based Table of Contents Shortcode
*
* A highly customizable, container-scoped table of contents generator
* with full styling control and professional UX design.
*
* Usage:
*
* @version 1.0.0
* @author Zeevou Development Team
*/
if (!function_exists(‘zeevou_container_toc_shortcode’)) {
function zeevou_container_toc_shortcode($atts) {
// Parse shortcode attributes with defaults
$atts = shortcode_atts([
// Container targeting
‘container’ => ”, // CSS selector for container (e.g., “#main-content”, “.article-body”)
‘container_id’ => ”, // Alternative: just provide ID without #
‘container_class’ => ”, // Alternative: just provide class without .
// Heading configuration
‘heading_levels’ => ‘2,3,4,5,6’, // Which heading levels to include
‘min_headings’ => ‘3’, // Minimum headings required to show TOC
‘exclude_selector’ => ”, // CSS selector for headings to exclude
// Display settings
‘title’ => ‘Table of Contents’, // TOC title
‘show_title’ => ‘yes’, // Show/hide title
‘collapsible’ => ‘yes’, // Make TOC collapsible
‘initially_collapsed’ => ‘no’, // Start collapsed?
‘smooth_scroll’ => ‘yes’, // Enable smooth scrolling
‘scroll_offset’ => ’80’, // Offset for fixed headers (px)
// Numbering
‘numbering’ => ‘decimal’, // decimal, none, roman, alpha
‘hierarchical’ => ‘yes’, // Hierarchical numbering (1.1, 1.2, etc.)
‘number_size’ => ‘0.75rem’, // Font size for numbers
‘number_weight’ => ‘600’, // Font weight for numbers
‘number_color’ => ”, // Number color (defaults to link_color if empty)
// Styling – Container
‘background’ => ‘#f9fafb’, // Background color
‘border_color’ => ‘#e5e7eb’, // Border color
‘border_width’ => ‘2px’, // Border width
‘border_radius’ => ‘8px’, // Border radius
‘padding’ => ‘1.5rem’, // Internal padding
‘max_width’ => ‘100%’, // Maximum width
‘shadow’ => ‘yes’, // Box shadow
// Styling – Title
‘title_color’ => ‘#111827’, // Title text color
‘title_size’ => ‘1.25rem’, // Title font size
‘title_weight’ => ‘700’, // Title font weight
‘title_align’ => ‘left’, // Title alignment
// Styling – Links
‘link_color’ => ‘#4b5563’, // Link color
‘link_hover_color’ => ‘#7c3aed’, // Link hover color
‘link_active_color’ => ‘#7c3aed’, // Active link color
‘link_size’ => ‘0.875rem’, // Link font size
‘link_weight’ => ‘400’, // Link font weight
// Styling – List
‘list_style’ => ‘none’, // none, disc, circle, square
‘indent_size’ => ‘1.5rem’, // Indentation for nested items
‘item_spacing’ => ‘0.5rem’, // Space between items
// Advanced
‘animate’ => ‘yes’, // Animate scroll and interactions
‘highlight_active’ => ‘yes’, // Highlight currently visible section
‘font_family’ => ‘inherit’, // Font family
‘custom_class’ => ”, // Additional CSS class
], $atts, ‘zeevou_toc’);
// Determine container selector
$container_selector = ”;
if (!empty($atts[‘container’])) {
$container_selector = trim($atts[‘container’]);
} elseif (!empty($atts[‘container_id’])) {
// Sanitize ID: remove # if user added it, then re-add
$clean_id = trim(str_replace(‘#’, ”, $atts[‘container_id’]));
$container_selector = ‘#’ . $clean_id;
} elseif (!empty($atts[‘container_class’])) {
// Sanitize class: remove . if user added it, then re-add
$clean_class = trim(str_replace(‘.’, ”, $atts[‘container_class’]));
$container_selector = ‘.’ . $clean_class;
}
// If no container specified, use entire page content
if (empty($container_selector)) {
$container_selector = ‘article, .entry-content, .post-content, main’;
}
// Generate unique ID for this TOC instance
$unique_id = ‘zeevou-toc-‘ . uniqid();
// Parse heading levels and validate
$heading_levels = array_map(‘trim’, explode(‘,’, $atts[‘heading_levels’]));
$heading_levels = array_filter($heading_levels, function($level) {
return is_numeric($level) && $level >= 1 && $level <= 6;
});
// Re-index array to avoid gaps in keys
$heading_levels = array_values($heading_levels);
// Default to h2-h6 if no valid levels
if (empty($heading_levels)) {
$heading_levels = ['2', '3', '4', '5', '6'];
}
$heading_selectors = array_map(function($level) {
return 'h' . intval($level);
}, $heading_levels);
$heading_selector_string = implode(', ', $heading_selectors);
// Generate list style type
$list_style_type = 'none';
if ($atts['list_style'] !== 'none') {
$list_style_type = $atts['list_style'];
}
// Generate numbering counter style
$counter_style = 'decimal';
switch ($atts['numbering']) {
case 'roman':
$counter_style = 'lower-roman';
break;
case 'alpha':
$counter_style = 'lower-alpha';
break;
case 'none':
$counter_style = 'none';
break;
default:
$counter_style = 'decimal';
}
ob_start();
?>