=== JuZ Rich Text Extender ===
Contributors:      juzed
Tags:              rich text, gutenberg, inline formats, toolbar
Requires at least: 6.1
Tested up to:      6.9
Requires PHP:      7.0
Stable tag:        1.0.0
License:           GPL-2.0+
License URI:       http://www.gnu.org/licenses/gpl-2.0.txt

Extends the Gutenberg block editor toolbar with custom inline format buttons, with smart nesting validation based on HTML spec rules.

== Description ==

JuZ Rich Text Extender allows developers to register custom inline format buttons in the Gutenberg (block editor) toolbar. Each button applies a specific HTML inline tag with an optional CSS class to the selected text.

The plugin includes a built-in nesting validation system: buttons are automatically disabled when their use would produce invalid HTML nesting (e.g. `<a>` inside `<a>`). When a format is already active, its button remains enabled so it can always be toggled off.

**Key features:**

* Register custom inline formats (tag + CSS class) via a WordPress filter
* `tagName` defaults to `span`, which is strongly recommended for compatibility
* Smart nesting validation against the full active tag stack (for non-span tags)
* Buttons are visually disabled when nesting is not allowed
* Exhaustive inline tag ruleset (`span`, `a`, `strong`, `em`, `mark`, `code`, `u`, `s`, `i`, `b`, `q`, `cite`, `abbr`, `small`, `sub`, `sup`)
* Inline tag rules are filterable for full customisation by themes or other plugins

**For developers:**

Formats and inline tag rules are both exposed via WordPress filters, making the plugin fully extensible without modifying core files.

== Installation ==

1. Upload the `juz-rich-text-extender` folder to the `/wp-content/plugins/` directory.
2. Activate the plugin through the **Plugins** menu in WordPress.
3. Use the `juz_rich_text_extender/formats` filter to register your custom inline formats.

== Frequently Asked Questions ==

= How do I register a custom format? =

Use the `juz_rich_text_extender/formats` filter to add your formats. Each format must be an associative array with the following keys:

* `name` - unique identifier (used as the format type slug)
* `title` - label shown in the toolbar tooltip
* `tagName` - HTML inline tag to apply. Optional, defaults to `span`. See the caution note below.
* `className` - CSS class applied to the tag
* `icon` - a Dashicon slug or SVG string

Example:

`
add_filter( 'juz_rich_text_extender/formats', function( $formats ) {
    $formats[] = [
        'name'      => 'highlight',
        'title'     => __( 'Highlight', 'your-text-domain' ),
        'className' => 'my-highlight',
        'icon'      => 'editor-textcolor',
    ];
    return $formats;
} );
`

**CAUTION:** `tagName` is optional and defaults to `span`, which is strongly recommended. Using any other tag may produce invalid HTML nesting or conflict with Gutenberg's internal formats (e.g. `core/link` for `<a>` tags). If you do specify a custom tag, the nesting validation system will still protect against invalid HTML where possible, but you may encounter editor limitations. See "How do I apply a custom class to a link?" for more details.

= How do I customise the inline tag nesting rules? =

Use the `juz_rich_text_extender/inline_tags` filter. Each tag entry has three keys: `canSelfNest` (bool), `canNestIn` (array of tag names), and `canContain` (array of tag names).

Example:

`
add_filter( 'juz_rich_text_extender/inline_tags', function( $tags ) {
    // Allow <kbd> as a custom inline tag
    $tags['kbd'] = [
        'canSelfNest' => false,
        'canNestIn'   => [ 'p', 'li', 'span', 'strong', 'em' ],
        'canContain'  => [],
    ];
    return $tags;
} );
`

= Why is a button greyed out in the toolbar? =

The nesting validation system detected that applying this format at the current cursor position would produce invalid HTML. For example, clicking `<a>` while already inside an `<a>` tag is forbidden by the HTML specification, so the button is disabled. This validation only applies to formats using a non-span `tagName`.

= Can I override nesting rules for an existing tag? =

Yes. Use the `juz_rich_text_extender/inline_tags` filter and modify the entry for the tag you want to change before returning the array.

= How do I apply a custom class to a link? =

Gutenberg manages `<a>` tags internally via its own `core/link` format, which makes it unreliable to apply a custom `<a>`-based format on top of an existing link. The recommended approach is to leave `tagName` unset (defaults to `span`) and target it in CSS using the `:has()` selector:

`
add_filter( 'juz_rich_text_extender/formats', function( $formats ) {
    $formats[] = [
        'name'      => 'button',
        'title'     => __( 'Button', 'your-text-domain' ),
        'className' => 'btn',
        'icon'      => 'button',
    ];
    return $formats;
} );
`

Then in your CSS:

`
a:has(> .btn) {
    display: inline-block;
    padding: 0.5em 1em;
    background: #0073aa;
    color: #fff;
    border-radius: 4px;
    text-decoration: none;
}
`

This approach produces valid HTML (`<a href="..."><span class="btn">text</span></a>`), survives the HTML ↔ visual editor round-trip without issues, and is supported by all modern browsers.

== Screenshots ==

1. Custom inline format buttons added to the Gutenberg toolbar.
2. A button is automatically disabled when its nesting would be invalid.

== Changelog ==

= 1.0.0 =
* Initial release.
* Custom inline format registration via filter.
* tagName defaults to span for maximum compatibility.
* Smart nesting validation on the full active tag stack for non-span tags.
* Exhaustive inline tag ruleset covering all standard HTML inline elements.
* Filterable inline tag rules for themes and plugins.

== Upgrade Notice ==

= 1.0.0 =
Initial release — no upgrade steps required.