PluginProbe
Noted! / trunk
Noted! vtrunk
2.0.2 2.0.1 trunk 1.0 2.0
noted / includes / AdminBar.php

AdminBar.php in Noted! trunk, at includes/AdminBar.php

173 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Noted;
6
7 use WP_Admin_Bar;
8
9 /**
10 * The admin-bar Noted! button + the floating side panel HTML.
11 *
12 * Suppressed on block-editor screens — the {@see EditorSidebar} (JS-side
13 * PluginSidebar) is the in-Gutenberg surface, and we don't want both UIs
14 * fighting for the same screen real estate.
15 */
16 final class AdminBar
17 {
18 public function __construct(private Settings $settings)
19 {
20 }
21
22 /**
23 * Register WordPress hooks.
24 */
25 public function register(): void
26 {
27 add_action('admin_bar_menu', [$this, 'addBarIcon'], 100);
28 add_action('admin_footer', [$this, 'renderFloatingPanel']);
29 add_action('wp_footer', [$this, 'renderFloatingPanel']);
30 }
31
32 /**
33 * Add the "Noted!" node to the WordPress admin bar.
34 */
35 public function addBarIcon(WP_Admin_Bar $bar): void
36 {
37 if (! $this->shouldRender()) {
38 return;
39 }
40 $bar->add_node([
41 'id' => 'noted',
42 'title' => 'Noted!',
43 'href' => '#',
44 'meta' => ['class' => 'noted-icon'],
45 ]);
46 }
47
48 /**
49 * Output the floating panel HTML in the footer.
50 */
51 public function renderFloatingPanel(): void
52 {
53 if (! $this->shouldRender()) {
54 return;
55 }
56
57 $postId = PostType::currentPostId();
58
59 // Rendered `hidden`; global-panel.js removes the attribute on init.
60 // Nothing can open the panel without that JS anyway, so tying
61 // visibility to it means a stray render (a document that emitted this
62 // markup without the assets) stays invisible instead of dumping form
63 // fields into the page.
64 ?>
65 <div
66 id="noted-panel"
67 class="noted-panel wp-admin-styling"
68 hidden
69 role="dialog"
70 aria-modal="true"
71 aria-labelledby="noted-panel-title"
72 tabindex="-1"
73 data-current-post="<?php echo esc_attr((string) $postId); ?>"
74 >
75 <button id="noted-close" class="noted-close-button" aria-label="<?php esc_attr_e('Close notes', 'noted'); ?>">&times;</button>
76 <div class="noted-content">
77 <h2 id="noted-panel-title"><?php esc_html_e('Notes', 'noted'); ?></h2>
78 <?php if ($postId) : ?>
79 <div class="noted-tabs" role="tablist">
80 <button type="button" class="noted-tab is-active" data-tab="global" role="tab"><?php esc_html_e('Global', 'noted'); ?></button>
81 <button type="button" class="noted-tab" data-tab="post" role="tab"><?php esc_html_e('This Post', 'noted'); ?></button>
82 </div>
83 <?php endif; ?>
84 <form id="noted-form">
85 <p class="noted-field">
86 <label for="noted-title"><?php esc_html_e('Title', 'noted'); ?></label>
87 <input type="text" id="noted-title" name="title" class="widefat">
88 </p>
89 <p class="noted-field">
90 <label for="noted-description" class="noted-description-label">
91 <?php esc_html_e('Description', 'noted'); ?>
92 <a
93 class="noted-markdown-doc-link"
94 href="<?php echo esc_url($this->settings->markdownDocumentationUrl()); ?>"
95 target="_blank"
96 rel="noopener noreferrer"
97 aria-label="<?php echo esc_attr($this->settings->markdownDocumentationLinkAriaLabel()); ?>"
98 data-tooltip="<?php echo esc_attr($this->settings->markdownDocumentationIconTooltip()); ?>"
99 >
100 <span class="dashicons dashicons-info" aria-hidden="true"></span>
101 </a>
102 </label>
103 <textarea id="noted-description" name="description" class="widefat" rows="4"></textarea>
104 </p>
105 <button type="button" id="noted-add" class="button button-primary"><?php esc_html_e('Add Note', 'noted'); ?></button>
106 </form>
107 <div id="noted-list" class="noted-list-container"></div>
108 </div>
109 </div>
110 <?php
111 }
112
113 /**
114 * True if the floating panel should appear on the current screen.
115 *
116 * The panel is intentionally shown on block-editor screens too — the
117 * PluginSidebar and the floating panel coexist and stay in sync via
118 * the `notedApi.subscribe` event channel.
119 */
120 public function shouldRender(): bool
121 {
122 if (! $this->settings->userCanView()) {
123 return false;
124 }
125 if (! $this->settings->bool('show_global_panel', true)) {
126 return false;
127 }
128 if (! self::isDocumentRequest()) {
129 return false;
130 }
131
132 // The panel is inert — and visually broken — without its stylesheet
133 // and JS. Plugins that print a bare preview document and call
134 // wp_footer() directly (MetaSlider's block preview iframe) never run
135 // the enqueue pipeline, so this catches them generically instead of
136 // endpoint by endpoint. Footer assets print at priority 20, after
137 // this hook, so 'enqueued' is the expected state here.
138 return wp_style_is(Assets::HANDLE_CSS, 'enqueued')
139 || wp_style_is(Assets::HANDLE_CSS, 'done');
140 }
141
142 /**
143 * False for requests that can reach a footer hook without being a page a
144 * human is looking at — REST, AJAX, cron, XML-RPC, feeds and oEmbed
145 * iframes.
146 */
147 private static function isDocumentRequest(): bool
148 {
149 if (defined('REST_REQUEST') && REST_REQUEST) {
150 return false;
151 }
152 if (wp_doing_ajax() || wp_doing_cron()) {
153 return false;
154 }
155 if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) {
156 return false;
157 }
158 return ! is_feed() && ! is_embed();
159 }
160
161 /**
162 * Detect whether the current request is an admin block-editor screen.
163 */
164 public static function isBlockEditorScreen(): bool
165 {
166 if (! is_admin() || ! function_exists('get_current_screen')) {
167 return false;
168 }
169 $screen = get_current_screen();
170 return $screen && method_exists($screen, 'is_block_editor') && $screen->is_block_editor();
171 }
172 }
173