44 * Plugin Name: SimpleTOC - Table of Contents Block
55 * Plugin URI: https://marc.tv/simpletoc-wordpress-inhaltsverzeichnis-plugin-gutenberg/
66 * Description: SEO-friendly Table of Contents Gutenberg block. No JavaScript and no CSS means faster loading.
7- * Version: 6.9.2
7+ * Version: 6.9.1
88 * Author: Marc Tönsing
99 * Author URI: https://toensing.com
1010 * Text Domain: simpletoc
@@ -127,6 +127,68 @@ function register_simpletoc_block()
127127 return $ toc_plugins ;
128128});
129129
130+ /**
131+ * Class to manage headline IDs.
132+ *
133+ * Ensures a unique anchor for each headline.
134+ */
135+ class SimpleTOC_Headline_Ids {
136+ /**
137+ * Array of headlines and their counts.
138+ * @var array
139+ */
140+ private $ headlines = array ();
141+
142+ /**
143+ * Add a headline to the array
144+ * @param string $headline_slug The slug of the headline
145+ */
146+ private function add_headline ( $ headline_slug ) {
147+ if ( empty ( $ headline_slug ) ) {
148+ return ;
149+ }
150+
151+ if ( ! isset ( $ this ->headlines [ $ headline_slug ] ) ) {
152+ $ this ->headlines [ $ headline_slug ] = 1 ;
153+ } else {
154+ $ this ->headlines [ $ headline_slug ] = $ this ->get_headline_count ( $ headline_slug ) + 1 ;
155+ }
156+ if ( $ this ->headlines [ $ headline_slug ] > 1 ) {
157+ $ new_headline_slug = $ headline_slug . '- ' . $ this ->headlines [ $ headline_slug ];
158+ if ( isset ( $ this ->headlines [ $ new_headline_slug ] ) ) {
159+ $ new_headline_slug = $ this ->add_headline ( $ new_headline_slug );
160+ }
161+ $ new_headline_count = $ this ->get_headline_count ( $ new_headline_slug );
162+ if ( 0 === $ new_headline_count ) {
163+ $ new_headline_count = 1 ;
164+ }
165+ $ this ->headlines [ $ new_headline_slug ] = $ new_headline_count ;
166+ return $ new_headline_slug ;
167+ }
168+ return $ headline_slug ;
169+ }
170+
171+ /**
172+ * Get the anchor for a headline
173+ * @param string $headline The headline
174+ * @return string The anchor for the headline
175+ */
176+ public function get_headline_anchor ( $ headline ) {
177+ if ( empty ( $ headline ) ) {
178+ return '' ;
179+ }
180+
181+ $ headline_slug = simpletoc_sanitize_string ( $ headline );
182+
183+ $ headline_slug = $ this ->add_headline ( $ headline_slug );
184+ return $ headline_slug ;
185+ }
186+
187+ private function get_headline_count ( $ headline_slug = '' ) {
188+ return $ this ->headlines [ $ headline_slug ] ?? 0 ;
189+ }
190+ }
191+
130192/**
131193* Adds IDs to the headings of the provided post content using a recursive block structure.
132194* @param string $content The content to add IDs to
@@ -159,18 +221,22 @@ function add_ids_to_blocks_recursive($blocks)
159221 'generateblocks/text ' ,
160222 'generateblocks/headline ' ,
161223 );
162-
224+
163225 /**
164226 * Filter to add supported blocks for IDs.
165227 *
166228 * @param array $supported_blocks The array of supported blocks.
167229 */
168230 $ supported_blocks = apply_filters ( 'simpletoc_supported_blocks_for_ids ' , $ supported_blocks );
169231
232+ // Need two separate instances so that IDs aren't double coubnted.
233+ $ inner_html_id_instance = new SimpleTOC_Headline_Ids ();
234+ $ inner_content_id_instance = new SimpleTOC_Headline_Ids ();
235+
170236 foreach ($ blocks as &$ block ) {
171237 if (isset ($ block ['blockName ' ]) && in_array ( $ block ['blockName ' ], $ supported_blocks ) && isset ($ block ['innerHTML ' ]) && isset ($ block ['innerContent ' ]) && isset ($ block ['innerContent ' ][0 ])) {
172- $ block ['innerHTML ' ] = add_anchor_attribute ($ block ['innerHTML ' ]);
173- $ block ['innerContent ' ][0 ] = add_anchor_attribute ($ block ['innerContent ' ][0 ]);
238+ $ block ['innerHTML ' ] = add_anchor_attribute ( $ block ['innerHTML ' ], $ inner_html_id_instance );
239+ $ block ['innerContent ' ][0 ] = add_anchor_attribute ( $ block ['innerContent ' ][0 ], $ inner_content_id_instance );
174240 } elseif (isset ($ block ['attrs ' ]['ref ' ])) {
175241 // search in reusable blocks (this is not finished because I ran out of ideas.)
176242 // $reusable_block_id = $block['attrs']['ref'];
@@ -396,9 +462,10 @@ function simpletoc_plugin_meta($links, $file)
396462/**
397463* Adds an ID attribute to all Heading tags in the provided HTML.
398464* @param string $html The HTML content to modify
465+ * @param SimpleTOC_Headline_Ids $headline_class_instance The instance of the SimpleTOC_Headline_Ids class
399466* @return string The modified HTML content with ID attributes added to the Heading tags
400467*/
401- function add_anchor_attribute ($ html )
468+ function add_anchor_attribute ($ html, $ headline_class_instance = null )
402469{
403470
404471 // remove non-breaking space entites from input HTML
@@ -425,7 +492,7 @@ function add_anchor_attribute($html)
425492 }
426493 // Set id attribute
427494 $ heading_text = trim (strip_tags ($ html ));
428- $ anchor = simpletoc_sanitize_string ($ heading_text );
495+ $ anchor = $ headline_class_instance -> get_headline_anchor ($ heading_text );
429496 $ tag ->setAttribute ("id " , $ anchor );
430497 }
431498
@@ -456,17 +523,15 @@ function generate_toc($headings, $attributes)
456523 list ($ min_depth , $ initial_depth ) = find_min_depth ($ headings , $ attributes );
457524
458525 $ item_count = 0 ;
459-
526+ $ headline_ids = new SimpleTOC_Headline_Ids ();
460527 foreach ($ headings as $ line => $ headline ) {
461528 $ this_depth = (int )$ headings [$ line ][2 ];
462529 $ next_depth = isset ($ headings [$ line + 1 ][2 ]) ? (int )$ headings [$ line + 1 ][2 ] : '' ;
463530 $ exclude_headline = should_exclude_headline ($ headline , $ attributes , $ this_depth );
464531 $ title = trim (strip_tags ($ headline ));
465532 $ customId = extract_id ($ headline );
466- $ link = $ customId ? $ customId : simpletoc_sanitize_string ($ title );
467-
533+ $ link = $ customId ? $ customId : $ headline_ids ->get_headline_anchor ($ title );
468534 if (!$ exclude_headline ) {
469-
470535 $ item_count ++;
471536 open_list ($ list , $ list_type , $ min_depth , $ this_depth );
472537 $ page = get_page_number_from_headline ($ headline );
0 commit comments