|
2 | 2 | * ActivityPub Moderation Admin JavaScript |
3 | 3 | */ |
4 | 4 |
|
5 | | -(function( $ ) { |
| 5 | +/* global activitypubModerationL10n, jQuery */ |
| 6 | + |
| 7 | +/** |
| 8 | + * @param {Object} $ - jQuery |
| 9 | + * @param {Object} wp - WordPress global object |
| 10 | + * @param {Object} wp.i18n - Internationalization functions |
| 11 | + * @param {Object} wp.a11y - Accessibility functions |
| 12 | + * @param {Object} wp.ajax - AJAX functions |
| 13 | + */ |
| 14 | +(function( $, wp ) { |
6 | 15 | 'use strict'; |
7 | 16 |
|
| 17 | + var __ = wp.i18n.__; |
| 18 | + var _n = wp.i18n._n; |
| 19 | + var sprintf = wp.i18n.sprintf; |
| 20 | + |
| 21 | + /** |
| 22 | + * Helper function to show a message using wp.a11y and alert |
| 23 | + * |
| 24 | + * @param {string} message - The message to display |
| 25 | + */ |
| 26 | + function showMessage( message ) { |
| 27 | + if ( wp.a11y && wp.a11y.speak ) { |
| 28 | + wp.a11y.speak( message, 'assertive' ); |
| 29 | + } |
| 30 | + alert( message ); |
| 31 | + } |
| 32 | + |
8 | 33 | /** |
9 | 34 | * Helper function to validate domain format |
| 35 | + * |
| 36 | + * @param {string} domain - The domain to validate |
| 37 | + * @return {boolean} Whether the domain is valid |
10 | 38 | */ |
11 | 39 | function isValidDomain( domain ) { |
12 | 40 | // Basic domain validation - must contain at least one dot and valid characters |
|
16 | 44 |
|
17 | 45 | /** |
18 | 46 | * Helper function to check if a term already exists in the UI |
| 47 | + * |
| 48 | + * @param {string} type - The type of block (domain or keyword) |
| 49 | + * @param {string} value - The value to check |
| 50 | + * @param {string} context - The context (user or site) |
| 51 | + * @param {number|null} userId - The user ID (for user context) |
| 52 | + * @return {boolean} Whether the term is already blocked |
19 | 53 | */ |
20 | 54 | function isTermAlreadyBlocked( type, value, context, userId ) { |
21 | 55 | var selector; |
|
27 | 61 | return $( selector ).length > 0; |
28 | 62 | } |
29 | 63 |
|
| 64 | + /** |
| 65 | + * Validate a blocked term value |
| 66 | + * |
| 67 | + * @param {string} type - The type of block (domain or keyword) |
| 68 | + * @param {string} value - The value to validate |
| 69 | + * @param {string} context - The context (user or site) |
| 70 | + * @param {number|null} userId - The user ID (for user context) |
| 71 | + * @return {boolean} Whether the value is valid |
| 72 | + */ |
| 73 | + function validateBlockedTerm( type, value, context, userId ) { |
| 74 | + if ( ! value ) { |
| 75 | + showMessage( __( 'Please enter a value to block.', 'activitypub' ) ); |
| 76 | + return false; |
| 77 | + } |
| 78 | + |
| 79 | + if ( type === 'domain' && ! isValidDomain( value ) ) { |
| 80 | + showMessage( __( 'Please enter a valid domain (e.g., example.com).', 'activitypub' ) ); |
| 81 | + return false; |
| 82 | + } |
| 83 | + |
| 84 | + if ( isTermAlreadyBlocked( type, value, context, userId ) ) { |
| 85 | + showMessage( __( 'This term is already blocked.', 'activitypub' ) ); |
| 86 | + return false; |
| 87 | + } |
| 88 | + |
| 89 | + return true; |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * Create a table row for a blocked term. |
| 94 | + * |
| 95 | + * @param {string} type - The type of block (domain or keyword) |
| 96 | + * @param {string} value - The blocked value |
| 97 | + * @param {string} context - The context (user or site) |
| 98 | + * @return {jQuery} The constructed table row |
| 99 | + */ |
| 100 | + function createBlockedTermRow( type, value, context ) { |
| 101 | + var $button = $( '<button>', { |
| 102 | + type: 'button', |
| 103 | + 'class': 'button button-small remove-' + context + '-block-btn', |
| 104 | + 'data-type': type, |
| 105 | + 'data-value': value, |
| 106 | + text: __( 'Remove', 'activitypub' ) |
| 107 | + } ); |
| 108 | + |
| 109 | + return $( '<tr>' ).append( $( '<td>' ).text( value ), $( '<td>' ).append( $button ) ); |
| 110 | + } |
| 111 | + |
30 | 112 | /** |
31 | 113 | * Helper function to add a blocked term to the UI |
32 | 114 | */ |
33 | 115 | function addBlockedTermToUI( type, value, context, userId ) { |
| 116 | + var table; |
| 117 | + |
34 | 118 | if ( context === 'user' ) { |
35 | 119 | // For user moderation, add to the appropriate table |
36 | 120 | var container = $( '.activitypub-user-block-list[data-user-id="' + userId + '"]' ); |
37 | 121 |
|
38 | | - var table = container.find( '.activitypub-blocked-' + type ); |
| 122 | + table = container.find( '.activitypub-blocked-' + type ); |
39 | 123 | if ( table.length === 0 ) { |
40 | 124 | table = $( '<table class="widefat striped activitypub-blocked-' + type + '" role="presentation" style="max-width: 500px; margin: 15px 0;"><tbody></tbody></table>' ); |
41 | 125 | container.find( '#new_user_' + type ).closest( '.add-user-block-form' ).before( table ); |
42 | 126 | } |
43 | | - table.append( '<tr><td>' + value + '</td><td style="width: 80px;"><button type="button" class="button button-small remove-user-block-btn" data-type="' + type + '" data-value="' + value + '">Remove</button></td></tr>' ); |
| 127 | + table.find( 'tbody' ).append( createBlockedTermRow( type, value, context ) ); |
44 | 128 | } else if ( context === 'site' ) { |
45 | | - // For site moderation, add to the appropriate table |
46 | | - var container = $( '#new_site_' + type ).closest( '.activitypub-site-block-list' ); |
47 | | - var table = container.find( '.activitypub-site-blocked-' + type ); |
| 129 | + // For site moderation, add to the table inside the details element |
| 130 | + var details = $( '.activitypub-site-block-details[data-type="' + type + '"]' ); |
| 131 | + table = details.find( '.activitypub-site-blocked-' + type ); |
| 132 | + |
48 | 133 | if ( table.length === 0 ) { |
49 | | - table = $( '<table class="widefat striped activitypub-site-blocked-' + type + '" role="presentation" style="max-width: 500px; margin: 15px 0;"><tbody></tbody></table>' ); |
50 | | - container.find( '.add-site-block-form' ).before( table ); |
| 134 | + // Create table inside the details element (after summary) |
| 135 | + table = $( '<table class="widefat striped activitypub-site-blocked-' + type + '" role="presentation"><tbody></tbody></table>' ); |
| 136 | + details.find( 'summary' ).after( table ); |
51 | 137 | } |
52 | | - table.append( '<tr><td>' + value + '</td><td style="width: 80px;"><button type="button" class="button button-small remove-site-block-btn" data-type="' + type + '" data-value="' + value + '">Remove</button></td></tr>' ); |
| 138 | + |
| 139 | + table.find( 'tbody' ).append( createBlockedTermRow( type, value, context ) ); |
| 140 | + |
| 141 | + updateSiteBlockSummary( type ); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + /** |
| 146 | + * Helper function to update the site block summary count |
| 147 | + */ |
| 148 | + function updateSiteBlockSummary( type ) { |
| 149 | + var details = $( '.activitypub-site-block-details[data-type="' + type + '"]' ); |
| 150 | + var table = details.find( '.activitypub-site-blocked-' + type ); |
| 151 | + var count = table.find( 'tbody tr' ).length || table.find( 'tr' ).length; |
| 152 | + var summary = details.find( 'summary' ); |
| 153 | + |
| 154 | + if ( count === 0 ) { |
| 155 | + // Empty state |
| 156 | + var emptyText = type === 'domain' |
| 157 | + ? __( 'No blocked domains', 'activitypub' ) |
| 158 | + : __( 'No blocked keywords', 'activitypub' ); |
| 159 | + summary.text( emptyText ); |
| 160 | + details.attr( 'open', '' ); |
| 161 | + table.remove(); |
| 162 | + } else { |
| 163 | + // Has items - use _n for proper pluralization |
| 164 | + var text = type === 'domain' |
| 165 | + ? _n( '%s blocked domain', '%s blocked domains', count, 'activitypub' ) |
| 166 | + : _n( '%s blocked keyword', '%s blocked keywords', count, 'activitypub' ); |
| 167 | + summary.text( sprintf( text, count ) ); |
53 | 168 | } |
54 | 169 | } |
55 | 170 |
|
|
63 | 178 |
|
64 | 179 | if ( button.length > 0 ) { |
65 | 180 | // Remove the parent table row |
66 | | - var parent = button.closest( 'tr' ); |
67 | | - var container = parent.closest( 'table' ); |
68 | | - parent.remove(); |
| 181 | + button.closest( 'tr' ).remove(); |
69 | 182 |
|
70 | | - // If the container is now empty, remove it |
71 | | - if ( container.find( 'tr' ).length === 0 ) { |
72 | | - container.remove(); |
| 183 | + // Update the summary count for site blocks |
| 184 | + if ( context === 'site' ) { |
| 185 | + updateSiteBlockSummary( type ); |
73 | 186 | } |
74 | 187 | } |
75 | 188 | } |
|
94 | 207 | var input = $( '#new_user_' + type ); |
95 | 208 | var value = input.val().trim(); |
96 | 209 |
|
97 | | - if ( ! value ) { |
98 | | - // Use wp.a11y.speak for better accessibility. |
99 | | - if ( wp.a11y && wp.a11y.speak ) { |
100 | | - wp.a11y.speak( activitypubModerationL10n.enterValue, 'assertive' ); |
101 | | - } else { |
102 | | - alert( activitypubModerationL10n.enterValue ); |
103 | | - } |
104 | | - return; |
105 | | - } |
106 | | - |
107 | | - // Validate domain format if this is a domain block |
108 | | - if ( type === 'domain' && ! isValidDomain( value ) ) { |
109 | | - var message = activitypubModerationL10n.invalidDomain || 'Please enter a valid domain (e.g., example.com).'; |
110 | | - if ( wp.a11y && wp.a11y.speak ) { |
111 | | - wp.a11y.speak( message, 'assertive' ); |
112 | | - } |
113 | | - alert( message ); |
114 | | - return; |
115 | | - } |
116 | | - |
117 | | - // Check if the term is already blocked |
118 | | - if ( isTermAlreadyBlocked( type, value, 'user', userId ) ) { |
119 | | - var message = activitypubModerationL10n.alreadyBlocked || 'This term is already blocked.'; |
120 | | - if ( wp.a11y && wp.a11y.speak ) { |
121 | | - wp.a11y.speak( message, 'assertive' ); |
122 | | - } |
123 | | - alert( message ); |
| 210 | + if ( ! validateBlockedTerm( type, value, 'user', userId ) ) { |
124 | 211 | return; |
125 | 212 | } |
126 | 213 |
|
|
136 | 223 | input.val( '' ); |
137 | 224 | addBlockedTermToUI( type, value, 'user', userId ); |
138 | 225 | }).fail( function( response ) { |
139 | | - var message = response && response.message ? response.message : activitypubModerationL10n.addBlockFailed; |
140 | | - if ( wp.a11y && wp.a11y.speak ) { |
141 | | - wp.a11y.speak( message, 'assertive' ); |
142 | | - } else { |
143 | | - alert( message ); |
144 | | - } |
| 226 | + var message = response && response.message ? response.message : __( 'Failed to add block.', 'activitypub' ); |
| 227 | + showMessage( message ); |
145 | 228 | }); |
146 | 229 | } |
147 | 230 |
|
|
157 | 240 | }).done( function() { |
158 | 241 | removeBlockedTermFromUI( type, value, 'user' ); |
159 | 242 | }).fail( function( response ) { |
160 | | - var message = response && response.message ? response.message : activitypubModerationL10n.removeBlockFailed; |
161 | | - if ( wp.a11y && wp.a11y.speak ) { |
162 | | - wp.a11y.speak( message, 'assertive' ); |
163 | | - } else { |
164 | | - alert( message ); |
165 | | - } |
| 243 | + var message = response && response.message ? response.message : __( 'Failed to remove block.', 'activitypub' ); |
| 244 | + showMessage( message ); |
166 | 245 | }); |
167 | 246 | } |
168 | 247 |
|
|
204 | 283 | var input = $( '#new_site_' + type ); |
205 | 284 | var value = input.val().trim(); |
206 | 285 |
|
207 | | - if ( ! value ) { |
208 | | - if ( wp.a11y && wp.a11y.speak ) { |
209 | | - wp.a11y.speak( activitypubModerationL10n.enterValue, 'assertive' ); |
210 | | - } else { |
211 | | - alert( activitypubModerationL10n.enterValue ); |
212 | | - } |
213 | | - return; |
214 | | - } |
215 | | - |
216 | | - // Validate domain format if this is a domain block |
217 | | - if ( type === 'domain' && ! isValidDomain( value ) ) { |
218 | | - var message = activitypubModerationL10n.invalidDomain || 'Please enter a valid domain (e.g., example.com).'; |
219 | | - if ( wp.a11y && wp.a11y.speak ) { |
220 | | - wp.a11y.speak( message, 'assertive' ); |
221 | | - } |
222 | | - alert( message ); |
223 | | - return; |
224 | | - } |
225 | | - |
226 | | - // Check if the term is already blocked |
227 | | - if ( isTermAlreadyBlocked( type, value, 'site' ) ) { |
228 | | - var message = activitypubModerationL10n.alreadyBlocked || 'This term is already blocked.'; |
229 | | - if ( wp.a11y && wp.a11y.speak ) { |
230 | | - wp.a11y.speak( message, 'assertive' ); |
231 | | - } |
232 | | - alert( message ); |
| 286 | + if ( ! validateBlockedTerm( type, value, 'site', null ) ) { |
233 | 287 | return; |
234 | 288 | } |
235 | 289 |
|
|
244 | 298 | input.val( '' ); |
245 | 299 | addBlockedTermToUI( type, value, 'site' ); |
246 | 300 | }).fail( function( response ) { |
247 | | - var message = response && response.message ? response.message : activitypubModerationL10n.addBlockFailed; |
248 | | - if ( wp.a11y && wp.a11y.speak ) { |
249 | | - wp.a11y.speak( message, 'assertive' ); |
250 | | - } else { |
251 | | - alert( message ); |
252 | | - } |
| 301 | + var message = response && response.message ? response.message : __( 'Failed to add block.', 'activitypub' ); |
| 302 | + showMessage( message ); |
253 | 303 | }); |
254 | 304 | } |
255 | 305 |
|
|
264 | 314 | }).done( function() { |
265 | 315 | removeBlockedTermFromUI( type, value, 'site' ); |
266 | 316 | }).fail( function( response ) { |
267 | | - var message = response && response.message ? response.message : activitypubModerationL10n.removeBlockFailed; |
268 | | - if ( wp.a11y && wp.a11y.speak ) { |
269 | | - wp.a11y.speak( message, 'assertive' ); |
270 | | - } else { |
271 | | - alert( message ); |
272 | | - } |
| 317 | + var message = response && response.message ? response.message : __( 'Failed to remove block.', 'activitypub' ); |
| 318 | + showMessage( message ); |
273 | 319 | }); |
274 | 320 | } |
275 | 321 |
|
|
302 | 348 | // Initialize when document is ready. |
303 | 349 | $( document ).ready( init ); |
304 | 350 |
|
305 | | -})( jQuery ); |
| 351 | +})( jQuery, wp ); |
0 commit comments