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

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

261 lines 8.3 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 /**
8 * Asset registration and enqueueing.
9 *
10 * One stylesheet (noted.css) plus five JS modules:
11 * - api.js — shared REST client (window.notedApi)
12 * - global-panel.js — floating panel (admin + frontend, jQuery)
13 * - editor-sidebar.js — Gutenberg PluginSidebar
14 * - block-notes.js — block-level note attribute + InspectorControls
15 * - classic-metabox.js — classic editor meta box handler
16 *
17 * Every JS consumer depends on the `noted-api` handle so the REST wrapper
18 * lives in exactly one place. Cache-busting versions come from each file's
19 * mtime so a save invalidates the URL automatically.
20 */
21 final class Assets
22 {
23 private const HANDLE_API = 'noted-api';
24 private const HANDLE_ICONS = 'noted-icons';
25
26 /** Public so {@see AdminBar::shouldRender()} can confirm the panel's CSS is present. */
27 public const HANDLE_CSS = 'noted-css';
28
29 private const HANDLE_GLOBAL_PANEL = 'noted-global-panel';
30 private const HANDLE_EDITOR_SIDEBAR = 'noted-editor-sidebar';
31 private const HANDLE_BLOCK_NOTES = 'noted-block-notes';
32 private const HANDLE_CLASSIC_METABOX = 'noted-classic-metabox';
33
34 public function __construct(
35 private Plugin $plugin,
36 private Settings $settings,
37 ) {}
38
39 /**
40 * Register WordPress hooks.
41 */
42 public function register(): void
43 {
44 add_action('admin_enqueue_scripts', [$this, 'enqueueGeneral']);
45 add_action('wp_enqueue_scripts', [$this, 'enqueueGeneral']);
46 add_action('enqueue_block_editor_assets', [$this, 'enqueueBlockEditor']);
47 add_action('admin_enqueue_scripts', [$this, 'enqueueClassicMetabox']);
48 }
49
50 /**
51 * Stylesheet + floating-panel JS for admin (non-block-editor) and frontend.
52 */
53 public function enqueueGeneral(): void
54 {
55 if (! is_user_logged_in()) {
56 return;
57 }
58 if (! $this->settings->userCanView()) {
59 return;
60 }
61
62 $this->registerSharedApi();
63
64 // `buttons` is a core style handle that provides .button / .button-primary
65 // / .button-link / .button-link-delete. Safe to load on the frontend
66 // because its selectors are scoped to those classes.
67 wp_enqueue_style(
68 self::HANDLE_CSS,
69 $this->plugin->assetUrl('css/noted.css'),
70 ['buttons'],
71 $this->assetVersion('css/noted.css')
72 );
73
74 if (! $this->shouldRenderGlobalPanel()) {
75 return;
76 }
77
78 wp_enqueue_style('dashicons');
79
80 wp_enqueue_script(
81 self::HANDLE_GLOBAL_PANEL,
82 $this->plugin->assetUrl('js/global-panel.js'),
83 ['jquery', 'wp-i18n', self::HANDLE_API],
84 $this->assetVersion('js/global-panel.js'),
85 true
86 );
87 $this->setTranslations(self::HANDLE_GLOBAL_PANEL);
88 }
89
90 /**
91 * Block editor: PluginSidebar + block-level notes.
92 */
93 public function enqueueBlockEditor(): void
94 {
95 if (! $this->settings->userCanView()) {
96 return;
97 }
98
99 $this->registerSharedApi();
100 $this->registerSharedIcons();
101
102 if ($this->settings->bool('enable_page_notes', true)) {
103 wp_enqueue_style('dashicons');
104 wp_enqueue_script(
105 self::HANDLE_EDITOR_SIDEBAR,
106 $this->plugin->assetUrl('js/editor-sidebar.js'),
107 [
108 self::HANDLE_API,
109 self::HANDLE_ICONS,
110 'wp-plugins',
111 'wp-editor',
112 'wp-edit-post',
113 'wp-element',
114 'wp-components',
115 'wp-data',
116 'wp-i18n',
117 ],
118 $this->assetVersion('js/editor-sidebar.js'),
119 true
120 );
121 $this->setTranslations(self::HANDLE_EDITOR_SIDEBAR);
122 }
123
124 if ($this->settings->bool('enable_block_notes', true)) {
125 wp_enqueue_script(
126 self::HANDLE_BLOCK_NOTES,
127 $this->plugin->assetUrl('js/block-notes.js'),
128 [
129 self::HANDLE_API,
130 self::HANDLE_ICONS,
131 'wp-blocks',
132 'wp-element',
133 'wp-components',
134 'wp-block-editor',
135 'wp-data',
136 'wp-hooks',
137 'wp-compose',
138 'wp-i18n',
139 ],
140 $this->assetVersion('js/block-notes.js'),
141 true
142 );
143 $this->setTranslations(self::HANDLE_BLOCK_NOTES);
144 }
145 }
146
147 /**
148 * Register the shared icons handle (idempotent).
149 */
150 private function registerSharedIcons(): void
151 {
152 if (wp_script_is(self::HANDLE_ICONS, 'registered')) {
153 return;
154 }
155
156 // `wp-icons` isn't a separate script handle in core, so we ship the
157 // SVG markup from PHP via wp_localize_script. Swap the string in
158 // Plugin::iconSvg() (or via the `noted/icon_svg` filter) to change
159 // the icon everywhere.
160 wp_register_script(
161 self::HANDLE_ICONS,
162 $this->plugin->assetUrl('js/icons.js'),
163 ['wp-element'],
164 $this->assetVersion('js/icons.js'),
165 true
166 );
167
168 wp_localize_script(self::HANDLE_ICONS, 'notedIconsConfig', [
169 'svg' => $this->plugin->iconSvg(),
170 ]);
171 }
172
173 /**
174 * Classic editor meta box JS, post-edit screens only.
175 */
176 public function enqueueClassicMetabox(string $hook): void
177 {
178 if (! in_array($hook, ['post.php', 'post-new.php'], true)) {
179 return;
180 }
181 if (! $this->settings->userCanView()) {
182 return;
183 }
184 if (! $this->settings->bool('enable_page_notes', true)) {
185 return;
186 }
187 if (AdminBar::isBlockEditorScreen()) {
188 return;
189 }
190
191 $this->registerSharedApi();
192
193 wp_enqueue_script(
194 self::HANDLE_CLASSIC_METABOX,
195 $this->plugin->assetUrl('js/classic-metabox.js'),
196 ['wp-i18n', self::HANDLE_API],
197 $this->assetVersion('js/classic-metabox.js'),
198 true
199 );
200 $this->setTranslations(self::HANDLE_CLASSIC_METABOX);
201 }
202
203 /**
204 * Wire up JS translations for a handle, pointing at the plugin's
205 * languages directory so JSON translation files load at runtime.
206 */
207 private function setTranslations(string $handle): void
208 {
209 wp_set_script_translations($handle, 'noted', $this->plugin->languagesDir());
210 }
211
212 /**
213 * Register and localise the shared notedApi handle exactly once.
214 */
215 private function registerSharedApi(): void
216 {
217 if (wp_script_is(self::HANDLE_API, 'registered')) {
218 return;
219 }
220
221 wp_register_script(
222 self::HANDLE_API,
223 $this->plugin->assetUrl('js/api.js'),
224 [],
225 $this->assetVersion('js/api.js'),
226 true
227 );
228
229 wp_localize_script(self::HANDLE_API, 'notedConfig', [
230 'rest_root' => esc_url_raw(rest_url(RestApi::NAMESPACE . '/')),
231 'rest_nonce' => wp_create_nonce('wp_rest'),
232 'current_post_id' => PostType::currentPostId(),
233 'can_edit' => $this->settings->userCanEdit(),
234 'markdown_doc_url' => $this->settings->markdownDocumentationUrl(),
235 'markdown_doc_aria_label' => $this->settings->markdownDocumentationLinkAriaLabel(),
236 'markdown_doc_tooltip' => $this->settings->markdownDocumentationIconTooltip(),
237 ]);
238 }
239
240 /**
241 * True if the floating panel should render — mirrors AdminBar.
242 */
243 private function shouldRenderGlobalPanel(): bool
244 {
245 return (bool) $this->settings->bool('show_global_panel', true);
246 }
247
248 /**
249 * Cache-busting version derived from the asset's mtime. Falls back to
250 * the plugin version constant if the file can't be stat'd.
251 */
252 private function assetVersion(string $relative): string
253 {
254 $path = $this->plugin->pluginDir() . '/assets/' . ltrim($relative, '/');
255 if (! file_exists($path)) {
256 return Plugin::VERSION;
257 }
258 return (string) filemtime($path);
259 }
260 }
261