Skip to content

Conversation

@iftakharul-islam
Copy link
Member

@iftakharul-islam iftakharul-islam commented Oct 8, 2025

Summary by CodeRabbit

  • New Features

    • Added a dynamic Table of Contents block with editor UI, preview, server-rendered output, front-end behavior (scroll‑spy, smooth scrolling, mobile collapsible) and comprehensive styling options.
    • Enhanced Docs Grid: new styling controls, hover effects, improved pagination and section toggle behavior.
  • Refactor

    • Streamlined block registration to conditionally register multiple blocks.
  • Chores

    • Updated build tooling/dev scripts versions.

@iftakharul-islam iftakharul-islam self-assigned this Oct 8, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 8, 2025

Warning

Rate limit exceeded

@iftakharul-islam has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 0120b90 and 3dc23be.

⛔ Files ignored due to path filters (12)
  • assets/build/5f4138884ca3828fa2ff.svg is excluded by !**/*.svg
  • assets/build/fonts/wedocs.0e4fd4b5.ttf is excluded by !**/*.ttf
  • assets/build/fonts/wedocs.29ec52e7.woff is excluded by !**/*.woff
  • assets/build/images/avatar-four.5b164b46.jpg is excluded by !**/*.jpg
  • assets/build/images/avatar-one.d65f1350.jpg is excluded by !**/*.jpg
  • assets/build/images/avatar-three.af590ffb.jpg is excluded by !**/*.jpg
  • assets/build/images/avatar-two.15349c81.jpg is excluded by !**/*.jpg
  • assets/build/images/pro-badge.aa003c10.png is excluded by !**/*.png
  • assets/build/images/slider-1.b05d3c91.jpg is excluded by !**/*.jpg
  • assets/build/images/slider-2.f7822c31.jpg is excluded by !**/*.jpg
  • assets/build/images/slider-3.ab3b7f94.jpg is excluded by !**/*.jpg
  • assets/build/images/slider-4.501d8d95.jpg is excluded by !**/*.jpg
📒 Files selected for processing (16)
  • assets/build/block.asset.php
  • assets/build/block.js
  • assets/build/blocks/DocsGrid/block.json
  • assets/build/blocks/DocsGrid/render.php
  • assets/build/frontend.asset.php
  • assets/build/frontend.css
  • assets/build/frontend.js
  • assets/build/index.asset.php
  • assets/build/index.css
  • assets/build/index.js
  • assets/build/print.asset.php
  • assets/build/print.css
  • assets/build/print.js
  • assets/build/store.asset.php
  • assets/build/store.js
  • assets/build/style-block.css

Walkthrough

Adds a new dynamic Table of Contents block (editor, frontend, styles, server render), refactors DocsGrid PHP rendering with guarded functions and inline styles/JS, updates block registration to conditionally register blocks from block.json, tweaks webpack entry invocation, updates devDependencies in package.json, and disables a template_include filter in includes/Frontend.php.

Changes

Cohort / File(s) Summary of changes
Dependencies
package.json
Added @wordpress/block-editor@^14.5.0 and upgraded @wordpress/scripts from ^25.0.0 to ^30.25.0.
DocsGrid render refactor
src/blocks/DocsGrid/render.php
Wrapped functions with if (!function_exists(...)) guards, moved rendering into a returnable guarded function and echoed the caller, reorganized pagination/query/content flow, added dynamic inline CSS and JS (DOMContentLoaded toggle), preserved output buffering.
New Table of Contents block
src/blocks/TableOfContents/block.json, src/blocks/TableOfContents/edit.js, src/blocks/TableOfContents/index.js, src/blocks/TableOfContents/render.php, src/blocks/TableOfContents/view.js, src/blocks/TableOfContents/style.scss, src/blocks/TableOfContents/editor.scss, src/blocks/TableOfContents/save.js
Introduced a dynamic TOC block: manifest with attributes and assets, editor UI (Edit component, Inspector controls, mock headings), editor & frontend styles, frontend view script (TOC generation, scroll spy, smooth scrolling, mobile collapse), and server-side render helpers to extract and render headings safely.
Block registration flow
wedocs.php
Replaced direct require/register for a single block with a loop that conditionally calls register_block_type for a list of blocks when each block.json exists; removed explicit require_once-based DocsGrid registration.
Blocks index import
src/blocks/index.js
Added import/registration of the new TableOfContents block alongside existing blocks.
Build configuration
webpack.config.js
Changed entry composition to call defaultConfig.entry() instead of spreading defaultConfig.entry.
Frontend hook disabled
includes/Frontend.php
Commented out/disables the add_filter('template_include', ...) template loader hook.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Extra attention recommended:

  • src/blocks/DocsGrid/render.php — confirm function guard names, echo/return behavior, inline CSS/JS sanitization, and edge-case rendering paths.
  • src/blocks/TableOfContents/* — review server-side heading extraction for correctness and XSS/ID uniqueness, and frontend view script scroll/spy performance and resilience.
  • webpack.config.js — verify build behavior when using defaultConfig.entry() in this project's webpack configuration.

Possibly related PRs

  • Add docs grid #203 — overlapping changes touching src/blocks/DocsGrid/render.php and related DocsGrid additions; likely code-level related.

Suggested labels

Needs Review

Poem

I nibble code by moonlit burrow light,
A TOC sprouts leaves and blocks take flight.
DocsGrid learned to dress with style and JS flair,
Webpack called its entry — handle with care.
I thump my foot — the patch hops bright! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The PR title "Configured wedocs new package system" is vague and does not accurately reflect the primary changes in the pull request. While the changeset does include minor dependency updates in package.json and configuration changes, the most significant work involves implementing a new TableOfContents Gutenberg block (with complete editor, render, styling, and frontend JavaScript components) and refactoring the block registration system in wedocs.php to conditionally register multiple blocks. The term "package system" is ambiguous—it could refer to NPM package management, WordPress block architecture, or plugin architecture—and fails to convey that the main objective is adding a major new block feature. The title does not highlight the primary architectural or feature changes, making it misleading about the true scope and intent of the pull request. Consider revising the title to more accurately reflect the main changes, such as "Add TableOfContents block with dynamic rendering and refactor block registration" or "Implement dynamic TableOfContents Gutenberg block with updated block system." This would clearly communicate the primary objective of the PR and help team members understand the scope when reviewing commit history.
Docstring Coverage ⚠️ Warning Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0434ddd and cc74d76.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • package.json (1 hunks)
  • src/blocks/DocsGrid/render.php (1 hunks)
  • webpack.config.js (1 hunks)
  • wedocs.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/blocks/DocsGrid/render.php (2)
src/blocks/DocsGrid/edit.js (1)
  • attributes (20-48)
src/blocks/DocsGrid/save.js (1)
  • attributes (4-16)
🪛 PHPMD (2.15.0)
src/blocks/DocsGrid/render.php

182-182: Avoid unused local variables such as '$total_pages'. (undefined)

(UnusedLocalVariable)


227-227: Avoid unused local variables such as '$total_articles'. (undefined)

(UnusedLocalVariable)

Comment on lines +321 to 324
return ob_get_clean();
}
echo render_wedocs_docs_grid($attributes);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Return the rendered markup instead of echoing

When a block.json uses the render file, WordPress expects that file to return the HTML string. Echoing here causes the metadata loader to return 1, which results in broken front-end output. Replace the echo with a return so the block renders correctly.

-    echo render_wedocs_docs_grid($attributes);
+    return render_wedocs_docs_grid($attributes);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return ob_get_clean();
}
echo render_wedocs_docs_grid($attributes);
}
return ob_get_clean();
}
return render_wedocs_docs_grid($attributes);
}
🤖 Prompt for AI Agents
In src/blocks/DocsGrid/render.php around lines 321 to 324, the file currently
echoes the rendered markup which causes WordPress to return 1 from the render
file; replace the final echo call with a return so the function returns the HTML
string (i.e., change echo render_wedocs_docs_grid($attributes); to return
render_wedocs_docs_grid($attributes);) and ensure no additional output is sent
after the return.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
wedocs.php (2)

176-188: Apply WordPress coding standards for spacing around control structures.

The control structures don't follow WordPress coding standards for spacing. WordPress requires spaces after opening parentheses and before closing parentheses.

Apply this diff to align with WordPress coding standards:

 public function register_blocks() {

     $block_lists = [
         WEDOCS_PATH . '/assets/build/blocks/DocsGrid',
     ];

-    foreach ($block_lists as $block) {
-        if (file_exists($block . '/block.json')) {
+    foreach ( $block_lists as $block ) {
+        if ( file_exists( $block . '/block.json' ) ) {
             // Register the DocsGrid block
-            register_block_type($block);
+            register_block_type( $block );
         }
     }
 }

184-184: Update comment to reflect generic block registration.

The comment specifically mentions "DocsGrid block" but this code is inside a loop that can register multiple block types. Consider making the comment more generic to match the actual functionality.

Apply this diff:

-            // Register the DocsGrid block
+            // Register the block
             register_block_type($block);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc74d76 and 64d463f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • package.json (1 hunks)
  • wedocs.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/blocks/TableOfContents/render.php (1)

74-95: Inline style building is fine; consider null-coalescing to avoid empty style attr.

Current get_block_wrapper_attributes handles null, but you can skip style when no styles exist.

-    'style' => !empty($inline_styles) ? implode('; ', $inline_styles) : null
+    // Only set 'style' when we actually have styles.
+    ...( ! empty( $inline_styles ) ? [ 'style' => implode( '; ', $inline_styles ) ] : [] )
src/blocks/TableOfContents/style.scss (1)

58-76: Add visible focus styles for keyboard navigation.

Improve accessibility by styling focus-visible on links.

 a {
   text-decoration: none;
   color: var(--toc-link-color, inherit);
   display: inline-block;
   transition: color 0.2s ease;
+  &:focus-visible {
+    outline: 2px solid currentColor;
+    outline-offset: 2px;
+  }
   &:hover {
     color: var(--toc-link-hover-color, currentColor);
     text-decoration: underline;
   }

Optionally honor reduced motion:

html.smooth-scroll {
-  scroll-behavior: smooth;
+  scroll-behavior: smooth;
+  @media (prefers-reduced-motion: reduce) {
+    scroll-behavior: auto;
+  }
}

Also applies to: 138-141

src/blocks/TableOfContents/edit.js (1)

279-381: Avoid __experimental APIs where stable equivalents exist; mirror final ID strategy in preview.

  • Prefer stable ToolsPanel components when your target WP version allows, to reduce churn.
  • For consistency, preview numbering/indent is fine; optionally use the same slugify ID strategy used in view/SSR to keep anchors consistent in editor previews.

Please confirm your minimum WP version. If ≥ 6.3, you can replace __experimentalToolsPanel and related with their stable counterparts.

Also applies to: 128-144

wedocs.php (1)

176-189: Apply the suggested refactor to improve maintainability and handle incomplete builds.

The verification reveals that TableOfContents is not yet built in assets/build/blocks/ (only DocsGrid exists), while the hard-coded array tries to register both. The file_exists() guard masks this incomplete build. The suggested refactor addresses this:

  • Using glob() auto-discovers only built blocks, preventing drift from hard-coded lists
  • Adding function_exists('register_block_type') guards against older WordPress versions
  • Future blocks will be automatically registered without code changes

The review comment suggestions are sound and should be applied.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64d463f and 0120b90.

📒 Files selected for processing (11)
  • includes/Frontend.php (1 hunks)
  • src/blocks/TableOfContents/block.json (1 hunks)
  • src/blocks/TableOfContents/edit.js (1 hunks)
  • src/blocks/TableOfContents/editor.scss (1 hunks)
  • src/blocks/TableOfContents/index.js (1 hunks)
  • src/blocks/TableOfContents/render.php (1 hunks)
  • src/blocks/TableOfContents/save.js (1 hunks)
  • src/blocks/TableOfContents/style.scss (1 hunks)
  • src/blocks/TableOfContents/view.js (1 hunks)
  • src/blocks/index.js (1 hunks)
  • wedocs.php (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • includes/Frontend.php
  • src/blocks/TableOfContents/save.js
  • src/blocks/index.js
🧰 Additional context used
🧬 Code graph analysis (3)
src/blocks/TableOfContents/render.php (2)
src/blocks/TableOfContents/edit.js (2)
  • attributes (55-70)
  • headings (72-72)
src/blocks/TableOfContents/view.js (13)
  • content (159-159)
  • headings (55-55)
  • headings (174-177)
  • level (104-104)
  • text (105-105)
  • id (106-106)
  • id (175-175)
  • html (95-95)
  • counters (100-100)
  • i (113-113)
  • i (129-129)
  • indent (122-122)
  • numbers (128-128)
src/blocks/TableOfContents/index.js (1)
src/blocks/TableOfContents/edit.js (1)
  • Edit (54-409)
src/blocks/TableOfContents/edit.js (1)
src/blocks/TableOfContents/view.js (14)
  • headings (55-55)
  • headings (174-177)
  • supportedHeadings (42-42)
  • showHierarchy (43-43)
  • showNumbering (44-44)
  • counters (100-100)
  • i (113-113)
  • i (129-129)
  • indent (122-122)
  • numberText (125-125)
  • level (104-104)
  • tocTitle (12-12)
  • collapsibleOnMobile (16-16)
  • smoothScroll (15-15)
🔇 Additional comments (5)
src/blocks/TableOfContents/editor.scss (1)

6-27: LGTM for editor styles.

Scoped, minimal, and safe for editor. No blocking issues.

src/blocks/TableOfContents/index.js (1)

29-34: Registration pattern is correct.

Dynamic block wired via block.json + Edit; no save() needed. Good.

src/blocks/TableOfContents/block.json (3)

106-106: Verify textdomain matches project convention.

Line 106 specifies textdomain as "dynamic-table-of-contents-block-wp", but the AI summary indicates it should be "wedocs". Ensure this matches your project's localization and textdomain configuration to avoid translation/i18n issues.


84-92: Verify color attribute defaults are handled safely in frontend code.

The color attributes (titleColor, linkColor, linkHoverColor) lack default values (lines 85, 88, 91). Confirm that the frontend rendering code, view script, and render.php handler gracefully manage undefined or null values for these attributes to prevent styling breakage.


107-111: Verify asset file paths and naming match build output.

Asset paths reference index.css, style-index.css, index.js, view.js, and render.php. Confirm these file names align with your webpack/build configuration to ensure assets are correctly located and bundled.

Comment on lines +121 to +174
if ( ! function_exists( 'wedocs_extract_headings_from_content_safe' ) ) {
function wedocs_extract_headings_from_content_safe($content, $supported_headings) {
$headings = [];

if (empty($content) || !is_array($supported_headings)) {
return $headings;
}

// Extract only content within entry-content div for weDocs pages
// This prevents the TOC from picking up headings from headers, footers, sidebars, etc.
if (preg_match('/<div[^>]*class=["\'][^"\']*entry-content[^"\']*["\'][^>]*>(.*?)<\/div>/is', $content, $entry_match)) {
$content = $entry_match[1];
}

// Convert h-tags to lowercase for pattern matching
$supported_tags = array_map('strtolower', $supported_headings);
$pattern = '/<(' . implode('|', $supported_tags) . ')(?:[^>]*(?:\s+id=["\']([^"\'][^"\']*)["\'\'])?[^>]*)>(.*?)<\/\1>/i';

if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
$used_ids = [];

foreach ($matches as $match) {
if (count($match) < 4) continue;

$tag = strtolower($match[1]);
$level = (int) substr($tag, 1);
$existing_id = isset($match[2]) && !empty(trim($match[2])) ? trim($match[2]) : '';
$text = wp_strip_all_tags($match[3]);
$text = trim($text);

if (empty($text)) continue;

$id = !empty($existing_id) ? $existing_id : sanitize_title($text);

// Ensure unique IDs
$original_id = $id;
$counter = 1;
while (in_array($id, $used_ids)) {
$id = $original_id . '-' . $counter;
$counter++;
}
$used_ids[] = $id;

$headings[] = [
'level' => $level,
'text' => $text,
'id' => $id
];
}
}

return $headings;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix SSR/CSR ID generation mismatch; expand counters to cover H6.

  • Server derives IDs from a slug of the text; frontend uses “toc-heading-{index}”. If JS fails, anchors won’t resolve. Align JS to use the same slug algorithm and uniqueness policy.
  • Expand $counters to include level 6 and reset loop to 6 to avoid notices if H6 ever appears.

Apply on PHP side (robust counters + small regex cleanup):

-        $pattern = '/<(' . implode('|', $supported_tags) . ')(?:[^>]*(?:\s+id=["\']([^"\'][^"\']*)["\'])?[^>]*)>(.*?)<\/\1>/i';
+        $pattern = '/<(' . implode('|', $supported_tags) . ')(?:[^>]*(?:\s+id=["\']([^"\']*)["\'])?[^>]*)>(.*?)<\/\1>/i';
-        $counters = [1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0];
+        $counters = [1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];
...
-                    for ($i = $level + 1; $i <= 5; $i++) {
+                    for ($i = $level + 1; $i <= 6; $i++) {
                         $counters[$i] = 0;
                     }

Then update view.js to compute the same slug and assign IDs before generating the list (see inline diff in that file’s comment). This makes SSR anchors work without JS and avoids duplicate/mismatched IDs. Based on learnings

Also applies to: 176-249

🤖 Prompt for AI Agents
In src/blocks/TableOfContents/render.php around lines 121-174, the PHP TOC
slug/ID generation and heading-level handling are incomplete: extend the
counters to cover H1–H6 and ensure the uniqueness loop resets/checks up to level
6 (avoid notices if an H6 appears); simplify/clean the regex to reliably capture
optional id attributes and inner text (keep case-insensitive matching and
entry-content extraction), and preserve the sanitize_title-based slug plus the
incrementing uniqueness suffix logic. Then update view.js to compute the exact
same slug algorithm and uniqueness policy client-side (replicate sanitize_title
behavior and the same incremental "-n" suffix strategy), assign those IDs to
headings before building the TOC list so SSR anchors match CSR IDs and anchors
work even if JS runs or fails.

Comment on lines +15 to +17
const smoothScroll = tocBlock.classList.contains('smooth-scroll') || tocBlock.dataset.smoothScroll === 'true';
const collapsibleOnMobile = tocBlock.classList.contains('collapsible-mobile') || window.innerWidth <= 768;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Front-end behavior bugs: collapsible flag, smoothScroll toggle, ID strategy, and escaping.

  • Collapsible is forced on small screens even when disabled.
  • smoothScroll=false still intercepts and animates.
  • IDs must be assigned before list generation and follow the same slug/uniqueness as PHP.
  • Escape text before injecting HTML; expand counters to H6.

Apply:

-    const smoothScroll = tocBlock.classList.contains('smooth-scroll') || tocBlock.dataset.smoothScroll === 'true';
-    const collapsibleOnMobile = tocBlock.classList.contains('collapsible-mobile') || window.innerWidth <= 768;
+    const smoothScroll = tocBlock.classList.contains('smooth-scroll') || tocBlock.dataset.smoothScroll === 'true';
+    const collapsibleOnMobile = tocBlock.classList.contains('collapsible-mobile');

@@ function generateTOC(tocBlock) {
-    // Generate TOC HTML
-    const tocHTML = generateTOCHTML(headings, supportedHeadings, showHierarchy, showNumbering);
-    const tocContent = tocBlock.querySelector('.toc-content');
-    tocContent.innerHTML = tocHTML;
-
-    // Add IDs to headings if they don't have them
-    headings.forEach(function(heading, index) {
-        if (!heading.id) {
-            heading.id = 'toc-heading-' + index;
-        }
-    });
+    // Assign IDs first (slugify + ensure uniqueness) so anchors work even without JS re-rendering.
+    const used = new Set(Array.from(headings).map(h => h.id).filter(Boolean));
+    headings.forEach((heading) => {
+        if (!heading.id) {
+            const base = slugify(heading.textContent || '');
+            let id = base || 'section';
+            let n = 1;
+            while (used.has(id)) { id = `${base}-${n++}`; }
+            heading.id = id;
+            used.add(id);
+        }
+    });
+
+    // Generate TOC HTML
+    const tocHTML = generateTOCHTML(headings, showHierarchy, showNumbering);
+    const tocContent = tocBlock.querySelector('.toc-content');
+    tocContent.innerHTML = tocHTML;
- function generateTOCHTML(headings, supportedHeadings, showHierarchy, showNumbering) {
+ function generateTOCHTML(headings, showHierarchy, showNumbering) {
     let html = '<ul class="toc-list';
@@
-    const counters = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
+    const counters = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
     let globalCounter = 1;
@@
-        const text = heading.textContent.trim();
-        const id = heading.id || 'toc-heading-' + Array.from(headings).indexOf(heading);
+        const text = (heading.textContent || '').trim();
+        const id = heading.id;
@@
-                for (let i = level + 1; i <= 5; i++) {
+                for (let i = level + 1; i <= 6; i++) {
                     counters[i] = 0;
                 }
             } else {
                 counters[level] = globalCounter++;
             }
         }
@@
-        html += '<li style="margin-left: ' + indent + 'px">';
-        html += '<a href="#' + id + '" class="toc-link">';
+        html += '<li style="margin-left: ' + indent + 'px">';
+        html += '<a href="#' + encodeURIComponent(id) + '" class="toc-link">';
         if (showNumbering) {
             html += '<span class="toc-number">' + numberText + '</span>';
         }
-        html += text;
+        html += escapeHtml(text);
         html += '</a></li>';
 function setupSmoothScrolling(tocBlock, smoothScroll) {
     const links = tocBlock.querySelectorAll('.toc-link');
 
     links.forEach(function(link) {
         link.addEventListener('click', function(e) {
-            e.preventDefault();
+            if (!smoothScroll) {
+                // Let the browser jump to the anchor normally.
+                return;
+            }
+            e.preventDefault();
             const targetId = link.getAttribute('href').substring(1);
             const targetElement = document.getElementById(targetId);
 
             if (targetElement) {
-                const offsetTop = targetElement.offsetTop - 80; // Account for sticky headers
+                const rect = targetElement.getBoundingClientRect();
+                const offsetTop = window.pageYOffset + rect.top - 80; // Account for sticky headers
                 if (smoothScroll && 'scrollBehavior' in document.documentElement.style) {
                     window.scrollTo({
                         top: offsetTop,
                         behavior: 'smooth'
                     });
                 } else {
                     // Fallback smooth scroll
                     animateScrollTo(offsetTop, 600);
                 }
             }
         });
     });
 }
+
+// Helpers
+function slugify(text) {
+    return String(text)
+        .toLowerCase()
+        .trim()
+        .replace(/&/g, '-and-')
+        .replace(/['"]/g, '')
+        .replace(/[^a-z0-9]+/g, '-')
+        .replace(/^-+|-+$/g, '');
+}
+function escapeHtml(s) {
+    return String(s)
+        .replace(/&/g, '&amp;')
+        .replace(/</g, '&lt;')
+        .replace(/>/g, '&gt;')
+        .replace(/"/g, '&quot;')
+        .replace(/'/g, '&#039;');
+}

This aligns IDs with PHP, respects the collapsible toggle, fixes smoothScroll off behavior, and hardens HTML injection.

Also applies to: 63-74, 94-151, 220-244

🤖 Prompt for AI Agents
In src/blocks/TableOfContents/view.js around lines 15-17 (and also apply similar
fixes to ranges 63-74, 94-151, 220-244): the code forces collapsible behavior on
small screens, ignores smoothScroll when explicitly false, assigns IDs after
list generation, and injects unescaped HTML; fix by (1) respect the collapsible
flag by combining the class check with the user setting rather than forcing true
for window.innerWidth <= 768 (only enable collapsible when the class/data flag
is set, optionally add responsive behavior as a separate feature), (2) change
smoothScroll logic so smoothScroll is true only when the class/data attribute
explicitly indicates true (do not default to true), (3) compute and assign
stable, unique IDs for headings before building the TOC list using the same
slugging/uniqueness algorithm as the PHP backend so IDs match, (4) escape
heading text and attributes before injecting into innerHTML or, better, create
DOM nodes/textContent to avoid HTML injection, and (5) expand the heading
counter logic to include H1–H6; apply these same corrections to the other
indicated line ranges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants