| 1 |
<?php |
| 2 |
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- view template receives variables via extract(); prefixing is impractical. |
| 3 |
|
| 4 |
/** |
| 5 |
* AI Actions split button. |
| 6 |
* |
| 7 |
* Shared by all four surfaces — the classic single-doc headers, the |
| 8 |
* [betterdocs_ai_actions] shortcode, the Gutenberg block and the Elementor |
| 9 |
* widget. Core\AIActions::render() is the only supported way to include it. |
| 10 |
* |
| 11 |
* Built on the WAI-ARIA Disclosure pattern rather than Menu Button: every entry |
| 12 |
* except "Copy page" is a navigation link, and role="menuitem" would override |
| 13 |
* the implicit link role — screen readers would announce "menu item" instead of |
| 14 |
* "link", and the entries would disappear from the links list. |
| 15 |
* |
| 16 |
* @var bool $enable |
| 17 |
* @var array $actions Resolved actions from AIActions::resolve(). |
| 18 |
* @var string $md_url |
| 19 |
* @var string $copy_url |
| 20 |
* @var string $page_url |
| 21 |
* @var int $doc_id |
| 22 |
* @var string $uid |
| 23 |
* @var string $button_label Already resolved by AIActions::render() — the global |
| 24 |
* setting, or a per-instance override from the block, |
| 25 |
* widget or shortcode. May be blank. |
| 26 |
* @var string $widget_type |
| 27 |
* @var string $blockId |
| 28 |
*/ |
| 29 |
|
| 30 |
use WPDeveloper\BetterDocs\Core\AIActions; |
| 31 |
|
| 32 |
if ( ! defined( 'ABSPATH' ) ) { |
| 33 |
exit; |
| 34 |
} |
| 35 |
|
| 36 |
if ( empty( $enable ) || empty( $actions ) ) { |
| 37 |
return; |
| 38 |
} |
| 39 |
|
| 40 |
$primary = null; |
| 41 |
foreach ( $actions as $action ) { |
| 42 |
if ( ! empty( $action['primary'] ) ) { |
| 43 |
$primary = $action; |
| 44 |
break; |
| 45 |
} |
| 46 |
} |
| 47 |
|
| 48 |
$classes = 'betterdocs-ai-actions'; |
| 49 |
if ( ! $primary ) { |
| 50 |
$classes .= ' betterdocs-ai-actions-no-primary'; |
| 51 |
} |
| 52 |
if ( ! empty( $widget_type ) && 'blocks' === $widget_type && ! empty( $blockId ) ) { |
| 53 |
$classes .= ' ' . $blockId; |
| 54 |
} |
| 55 |
|
| 56 |
$menu_id = $uid . '-menu'; |
| 57 |
$toggle_id = $uid . '-toggle'; |
| 58 |
$svg_kses = AIActions::svg_kses(); |
| 59 |
|
| 60 |
// Blank is a legitimate saved value — the setting is a plain text field and a |
| 61 |
// per-instance control can be cleared — so fall back to the primary action's own |
| 62 |
// label rather than shipping an unlabelled button. |
| 63 |
$button_label = isset( $button_label ) ? (string) $button_label : ''; |
| 64 |
if ( '' === trim( $button_label ) ) { |
| 65 |
$button_label = $primary ? $primary['label'] : __( 'AI Actions', 'betterdocs' ); |
| 66 |
} |
| 67 |
?> |
| 68 |
<div class="<?php echo esc_attr( $classes ); ?>" |
| 69 |
data-bd-doc-id="<?php echo esc_attr( $doc_id ); ?>" |
| 70 |
data-bd-md-url="<?php echo esc_url( $md_url ); ?>" |
| 71 |
data-bd-copy-url="<?php echo esc_url( $copy_url ); ?>" |
| 72 |
data-bd-page-url="<?php echo esc_url( $page_url ); ?>"> |
| 73 |
|
| 74 |
<?php if ( $primary ) : ?> |
| 75 |
<button type="button" class="betterdocs-ai-actions-primary" |
| 76 |
data-bd-action="<?php echo esc_attr( $primary['id'] ); ?>" |
| 77 |
data-bd-handler="<?php echo esc_attr( $primary['handler'] ); ?>" |
| 78 |
data-bd-source="<?php echo esc_attr( $primary['source'] ); ?>"> |
| 79 |
<span class="betterdocs-ai-actions-icon betterdocs-ai-actions-icon-idle" aria-hidden="true"> |
| 80 |
<?php echo wp_kses( AIActions::icon( $primary['icon'] ), $svg_kses ); ?> |
| 81 |
</span> |
| 82 |
<?php |
| 83 |
// The copied / failed icons ship with the button and are swapped in by |
| 84 |
// CSS off the wrapper class. That keeps the state change a one-class flip |
| 85 |
// in public/betterdocs.js and keeps every icon in one place — |
| 86 |
// AIActions::icon() — rather than half of them in JavaScript. |
| 87 |
?> |
| 88 |
<span class="betterdocs-ai-actions-icon betterdocs-ai-actions-icon-done" aria-hidden="true"> |
| 89 |
<?php echo wp_kses( AIActions::icon( 'check' ), $svg_kses ); ?> |
| 90 |
</span> |
| 91 |
<span class="betterdocs-ai-actions-icon betterdocs-ai-actions-icon-fail" aria-hidden="true"> |
| 92 |
<?php echo wp_kses( AIActions::icon( 'alert' ), $svg_kses ); ?> |
| 93 |
</span> |
| 94 |
<span class="betterdocs-ai-actions-label"><?php echo esc_html( $button_label ); ?></span> |
| 95 |
</button> |
| 96 |
<?php endif; ?> |
| 97 |
|
| 98 |
<button type="button" class="betterdocs-ai-actions-toggle" |
| 99 |
id="<?php echo esc_attr( $toggle_id ); ?>" |
| 100 |
aria-expanded="false" |
| 101 |
aria-controls="<?php echo esc_attr( $menu_id ); ?>" |
| 102 |
aria-label="<?php esc_attr_e( 'AI actions', 'betterdocs' ); ?>"> |
| 103 |
<span class="betterdocs-ai-actions-caret" aria-hidden="true"> |
| 104 |
<?php echo wp_kses( AIActions::icon( 'caret' ), $svg_kses ); ?> |
| 105 |
</span> |
| 106 |
</button> |
| 107 |
|
| 108 |
<ul class="betterdocs-ai-actions-menu" |
| 109 |
id="<?php echo esc_attr( $menu_id ); ?>" |
| 110 |
aria-labelledby="<?php echo esc_attr( $toggle_id ); ?>" hidden> |
| 111 |
<?php |
| 112 |
$last_group = null; |
| 113 |
foreach ( $actions as $action ) : |
| 114 |
$group = isset( $action['group'] ) ? (string) $action['group'] : ''; |
| 115 |
|
| 116 |
// A rule between the actions that act on this page and the ones that |
| 117 |
// hand it to an assistant. Driven off the group rather than a hard-coded |
| 118 |
// index so a third-party registry entry lands on the right side of it, |
| 119 |
// and never emitted first or for a single group. |
| 120 |
if ( null !== $last_group && $group !== $last_group ) : |
| 121 |
?> |
| 122 |
<li class="betterdocs-ai-actions-separator" role="presentation"></li> |
| 123 |
<?php |
| 124 |
endif; |
| 125 |
$last_group = $group; |
| 126 |
?> |
| 127 |
<li class="betterdocs-ai-actions-row"> |
| 128 |
<?php |
| 129 |
$icon = wp_kses( AIActions::icon( $action['icon'] ), $svg_kses ); |
| 130 |
$is_copy = 'copy' === $action['type']; |
| 131 |
$blank = ! $is_copy && '_blank' === $action['target']; |
| 132 |
?> |
| 133 |
<?php if ( $is_copy ) : ?> |
| 134 |
<button type="button" class="betterdocs-ai-actions-item" |
| 135 |
data-bd-action="<?php echo esc_attr( $action['id'] ); ?>" |
| 136 |
data-bd-handler="<?php echo esc_attr( $action['handler'] ); ?>" |
| 137 |
data-bd-source="<?php echo esc_attr( $action['source'] ); ?>"> |
| 138 |
<?php else : ?> |
| 139 |
<a class="betterdocs-ai-actions-item" |
| 140 |
href="<?php echo esc_url( $action['href'] ); ?>" |
| 141 |
data-bd-action="<?php echo esc_attr( $action['id'] ); ?>" |
| 142 |
<?php echo $blank ? 'target="_blank" rel="noopener noreferrer"' : ''; ?>> |
| 143 |
<?php endif; ?> |
| 144 |
|
| 145 |
<span class="betterdocs-ai-actions-icon" aria-hidden="true"><?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_kses'd above. ?></span> |
| 146 |
<span class="betterdocs-ai-actions-text"> |
| 147 |
<span class="betterdocs-ai-actions-title"><?php echo esc_html( $action['label'] ); ?></span> |
| 148 |
<?php if ( '' !== $action['description'] ) : ?> |
| 149 |
<span class="betterdocs-ai-actions-desc"><?php echo esc_html( $action['description'] ); ?></span> |
| 150 |
<?php endif; ?> |
| 151 |
</span> |
| 152 |
<?php if ( $blank ) : ?> |
| 153 |
<?php |
| 154 |
// No visible arrow: each row already says where it goes, and the |
| 155 |
// glyph was a third column fighting the icon tile for the eye. |
| 156 |
// The hint stays for screen readers, which get no other warning |
| 157 |
// that the link leaves the page. |
| 158 |
?> |
| 159 |
<span class="betterdocs-sr-only"><?php esc_html_e( '(opens in a new tab)', 'betterdocs' ); ?></span> |
| 160 |
<?php endif; ?> |
| 161 |
|
| 162 |
<?php echo $is_copy ? '</button>' : '</a>'; ?> |
| 163 |
</li> |
| 164 |
<?php endforeach; ?> |
| 165 |
</ul> |
| 166 |
|
| 167 |
<span class="betterdocs-ai-actions-status betterdocs-sr-only" role="status" aria-live="polite"></span> |
| 168 |
</div> |
| 169 |
|