=== Super Duper Reading Index ===
Contributors: rogerruckstuhl
Tags: anchor links, heading structure, table of contents, structured data, accessibility
Requires at least: 6.4
Tested up to: 7.0
Requires PHP: 8.0
Stable tag: 1.2.3
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Keeps section anchors permanently valid so deep links never break, checks your heading structure and publishes the outline as structured data.

== Description ==

**Super Duper Reading Index** manages the section structure of your posts. Rename a heading and its anchor changes with it, so every bookmark, newsletter link, internal cross-reference and search engine jump link that pointed at the old one silently lands at the top of the page instead - and nobody ever notices. This plugin remembers the anchors each heading has had, keeps serving the retired ones, tells you when a heading structure is broken, and publishes the outline in a form search engines and answer engines can read. The table of contents is the visible output of all that.

🇨🇭🇩🇪🇦🇹 *Hinweis für DACH-Nutzer: Plugin und Support sind auf Deutsch (Schweiz/Deutschland/Österreich) verfügbar.*

= Permanent anchors =

Anchors are derived from heading text, which makes them break every time an editor improves a heading. Nothing warns you, nothing 404s, the link simply stops arriving where it was meant to.

* **Anchor history per heading.** Every save records which anchor each heading produced.
* **Retired anchors keep working.** A former anchor is re-emitted as an empty, invisible element inside its heading, so old links resolve exactly where they always did. It adds no visible markup and no layout.
* **Renames are visible while editing.** The editor sidebar shows which headings were renamed and which anchors they used to have.
* **Deleted headings are reported**, because those anchors genuinely cannot be saved - you get told rather than left guessing.
* **Site wide deep link scan** under *Tools > Reading Index*: finds links pointing at an anchor that no longer exists anywhere. It runs only when you press the button - no background job, no scheduled task.

= Structure check =

A table of contents is only ever as good as the headings underneath it, so the plugin grades them. Each post gets a score from 0 to 100 in the editor sidebar, and *Tools > Reading Index* lists the whole site, worst first.

* Skipped heading levels (an H4 straight after an H2).
* More than one H1, or an H1 in the content where the theme already prints one.
* Empty headings, which produce empty index entries and unusable anchors.
* Duplicated heading text, which forces numbered anchors that break as soon as the order changes.
* Sections that run on past a configurable word limit without a subheading.
* Headings too long to make a usable anchor or a readable index entry.
* Outlines that start at H3 for no reason.

= Machine readable outline =

Every section is published as schema.org `ItemList` data with its own deep link. Search engines use a section list to offer jump links straight into a chapter, and answer engines use it to see how a long document is divided before they quote from it. It is added only where an index is really shown, and never on AMP pages or password protected posts.

= The table of contents =

* **Automatic heading detection** for H2 to H6 - you choose which levels are listed.
* **Anchors are added for you.** Headings that already have an ID keep it, so existing links stay valid.
* **Smooth scrolling** to the selected section, with an adjustable offset for sticky headers.
* **Active section highlighting** while the visitor scrolls.
* **Collapse and expand** with a show/hide button; the visitor's choice can be remembered.
* **Reading time** estimate in the index header, based on an adjustable words-per-minute value.
* **Reading progress bar** at the top or bottom edge of the screen.
* **Sticky sidebar index** that fades in once the inline index has scrolled out of view.
* **Foldable sub-headings** - every entry that has sub-entries gets its own fold-out control.
* **Show more** - long indexes are shortened to a set number of entries with a "show all" button.
* **Gutenberg block, Elementor widget and shortcode** for manual placement.
* **Free position control** for the automatic insertion: above the content, after the first paragraph, before the first heading or below the content.

= Integrations =

* **Elementor** - a "Reading Index" widget with its own title, heading levels, style and marker settings.
* **AMP** - on AMP pages the index switches to a script-free variant: a native fold-out element plus CSS smooth scrolling, valid AMP markup without a single line of JavaScript.
* **Advanced Custom Fields** - headings inside named ACF fields get anchors too and are listed in the index, even though the theme prints those fields outside the post content.

= Design =

Three ready-made presets - Classic (bordered box), Modern card (soft shadow) and Minimal (accent line) - plus full control over accent, background, border, text and link colour, corner radius, font size, width, alignment, maximum height and the list markers (none, bullets, numbers or nested numbers such as 1.1).

= Fine control =

* **Word count limit** - short posts stay without an index.
* **Device target** - show the index on all devices, on desktop only or on mobile only.
* **Alternative headings** - show a shorter label in the index without touching the heading in the text.
* **Exclude headings** by name, with pipe separated patterns and asterisk wildcards.
* **H1 to H6** - every level can be indexed.

= Global or per post =

Every option has a sensible global default. On top of that, each post and page has its own **Reading Index** panel below the editor with overrides for automatic insertion, header label, position, word count limit, device target, alignment, initial state, counters, heading levels, alternative headings, excluded headings, reading time, progress bar and floating index - all for that one post.

= Languages =

The plugin is fully internationalised. Translations are contributed and delivered through translate.wordpress.org, so every locale is kept up to date without shipping catalogues inside the plugin.

= Accessibility =

The index is a labelled navigation landmark, the show/hide button carries the correct ARIA state, the current section is marked with `aria-current`, keyboard focus moves to the target heading, and the `prefers-reduced-motion` setting is respected.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/` or install it through **Plugins > Add New**.
2. Activate the plugin.
3. Open **Settings > Reading Index** and choose where the index should appear.

By default the index is added to blog posts that contain at least three headings.

== Frequently Asked Questions ==

= What exactly happens when I rename a heading? =

The next save notices that the heading changed position or text, records the anchor it used to have, and from then on that old anchor is emitted again as an empty element inside the heading. Any link to it keeps arriving at the same section. The editor sidebar lists every heading this has happened to.

= Does the alias change how my page looks? =

No. It is an empty inline element inside the heading with no content, no box and no styling. It cannot shift text, wrap a line differently or alter spacing.

= What if I delete a heading instead of renaming it? =

Then its anchor cannot be saved - there is nothing left to point at. The plugin says so rather than quietly redirecting the link to an unrelated section: the editor sidebar marks the anchor as orphaned, and the report under **Tools > Reading Index** lists every link that still points at it, so you can repoint them.

= Does the deep link scan slow my site down? =

No. It only runs when you press the button on the report screen. There is no cron job, no background task and nothing happens on a visitor's page load.

= Does the plugin send anything to an external service? =

No. Everything - the anchor history, the structure check, the link scan and the structured data - is computed and stored on your own site.

= How do I place the index manually? =

Use the **Reading Index** block in the editor, the **Reading Index** widget in Elementor, or the shortcode `[sdrein_reading_index]` (alias `[sdrein_toc]`). When one of them is present, the automatic insertion is skipped for that post.

= Which ACF fields are indexed? =

Only the ones you name under **Settings > Reading Index > Integrations**, comma separated. The field values are read early, their headings get anchors, and the anchored markup is handed back to the theme when it prints the field - so it works no matter whether the theme outputs the field before or after the content.

= How do I exclude a single heading? =

Give the heading the additional CSS class `sdrein-exclude`. The heading then stays out of the index.

= Can I change the settings for just one post? =

Yes. Every post and page has a **Reading Index** panel below the editor. Everything left on "Use global setting" or left empty follows the plugin settings.

= How do the alternative and excluded headings work? =

Alternative headings take one `Original|Replacement` pair per line and only change the label inside the index, case sensitive. Excluded headings are separated by a pipe, an asterisk stands for any text and matching ignores upper and lower case: `Recipe*|About the author|*sponsored*`.

= The floating index does not show up. =

It is switched off by default, needs a minimum screen width (1200 pixels by default) and only fades in once the inline index has scrolled out of view. All three values can be changed under **Settings > Reading Index > Extras**.

= Does it work with page builders? =

The index is built from the rendered content, so any builder that outputs regular heading tags works. If a builder renders its headings outside the standard content filter, the stylesheet may not be loaded automatically - the filter `sdrein_load_assets` lets you force it.

== Screenshots ==

1. The index in a blog post, Classic preset with nested numbers.
2. Settings page - general options.
3. Settings page - design options with colour pickers.
4. Per post overrides in the editor sidebar.

== Changelog ==

= 1.2.3 - 12.08.2026 =

* New: **Permanent anchors.** The anchors of every heading are recorded on save and retired ones keep being served, so links made before a heading was renamed still arrive at the right section.
* New: **Structure check.** A score from 0 to 100 per post plus findings for skipped heading levels, several H1 headings, empty or duplicated headings, overlong sections and unusable heading lengths. Shown in the editor sidebar and site wide under Tools > Reading Index.
* New: **Deep link scan.** Finds links pointing at an anchor that no longer exists. Runs on demand only, never in the background.
* New: **Machine readable outline.** Sections are published as schema.org ItemList data with their own deep links, for search engines and answer engines.
* New: Structure tab in the settings for anchor tracking, the section length limit and the structured data output.
* Changed: The shortcode is now `[sdrein_reading_index]`. The former `[reading_index]` was too generic a name to claim globally - a shortcode tag is a site wide identifier, and it also became an unprefixed `shortcode_atts_` filter. The alias `[sdrein_toc]` is unchanged.
* Changed: Translations are no longer bundled. They are contributed and delivered through translate.wordpress.org instead.
* Fixed: The uninstall routine missed nine of the per-post option keys and left them in the database.
* Fixed: The structure meta box left its container element unclosed on posts that had no anchor history yet, which broke the layout of the editor sidebar.

== Upgrade Notice ==

= 1.2.3 =

Adds permanent section anchors so renaming a heading no longer breaks existing deep links, a heading structure check with a site wide report, and a machine readable section outline. The shortcode is now [sdrein_reading_index]; the alias [sdrein_toc] is unchanged.