PluginProbe
Parse.ly / 3.23.6
Parse.ly v3.23.6
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
wp-parsely / src / js / admin-settings.ts

admin-settings.ts in Parse.ly 3.23.6, at src/js/admin-settings.ts

302 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies
3 */
4 import { escapeHTML } from '@wordpress/escape-html';
5 import { __, sprintf } from '@wordpress/i18n';
6
7 /**
8 * Internal dependencies
9 */
10 import {
11 ContentHelperError,
12 ContentHelperErrorCode,
13 } from '../content-helper/common/content-helper-error';
14 import { CheckAuthProvider } from '../content-helper/common/providers/check-auth-provider';
15
16 document.addEventListener( 'DOMContentLoaded', (): void => {
17 displayContentHelperSectionMessages();
18 addContentHelperTabEventHandlers();
19 setActiveTab();
20 window.addEventListener( 'hashchange', setActiveTab );
21 document.querySelector( '.media-single-image button.browse' )?.addEventListener( 'click', selectImage );
22 } );
23
24 function setActiveTab(): void {
25 const activeTab = location.hash !== '' ? location.hash.substring( 1 ) : 'basic-section';
26
27 document.querySelectorAll( '.nav-tab' )?.forEach( ( t: Element ): void => {
28 if ( t.classList.contains( activeTab + '-tab' ) ) {
29 t.classList.add( 'nav-tab-active' );
30 } else {
31 t.classList.remove( 'nav-tab-active' );
32 }
33 } );
34
35 document.querySelectorAll( '.tab-content' )?.forEach( ( t: Element ): void => {
36 if ( t.classList.contains( activeTab ) ) {
37 t.setAttribute( 'style', 'display: initial' );
38 } else {
39 t.setAttribute( 'style', 'display: none' );
40 }
41 } );
42
43 const form = document.querySelector( 'form[name="parsely"]' );
44 if ( form ) {
45 form.removeAttribute( 'hidden' );
46 form.setAttribute( 'action', `options.php#${ activeTab }` );
47 }
48 }
49
50 function selectImage( event: Event ) {
51 const optionName = ( event.target as HTMLButtonElement ).dataset.option;
52
53 const imageFrame = window.wp.media( {
54 multiple: false,
55 library: {
56 type: 'image',
57 },
58 } );
59
60 imageFrame.on( 'select', function() {
61 const url = imageFrame.state().get( 'selection' ).first().toJSON().url;
62 const inputSelector: string = '#media-single-image-' + optionName + ' input.file-path';
63
64 const inputElement: HTMLInputElement | null = document.querySelector( inputSelector );
65 if ( inputElement ) {
66 inputElement.value = url;
67 }
68 } );
69
70 imageFrame.open();
71 }
72
73 /**
74 * Adds the necessary event handlers to the Content Intelligence tab.
75 *
76 * @since 3.16.0
77 */
78 function addContentHelperTabEventHandlers(): void {
79 // Selector for the checkbox that enables/disables all AI features.
80 const aiFeaturesCheckbox = document.querySelector<HTMLInputElement>(
81 'input#content_helper_ai_features_enabled'
82 );
83
84 // Selector for the checkboxes that enable/disable individual AI features.
85 const featureCheckboxes = document.querySelectorAll<HTMLInputElement>(
86 'input#content_helper_smart_linking_enabled, ' +
87 'input#content_helper_title_suggestions_enabled, ' +
88 'input#content_helper_excerpt_suggestions_enabled, ' +
89 'input#content_helper_traffic_boost_enabled'
90 );
91
92 // Selector for all fieldsets in the Content Intelligence section.
93 const fieldsets = document.querySelectorAll(
94 'div.content-intelligence-section fieldset'
95 );
96
97 // Event handlers.
98 enableAllFormFieldsOnSubmit();
99 updateAllFeatureSections(); // Must run on load to update the UI.
100
101 aiFeaturesCheckbox?.addEventListener( 'change', (): void => {
102 updateAllFeatureSections();
103 } );
104
105 featureCheckboxes.forEach( ( checkbox ): void => {
106 checkbox.addEventListener( 'change', (): void => {
107 updateFeatureSection( checkbox );
108 } );
109 } );
110
111 /**
112 * Turns on/off all AI feature sections in the UI.
113 *
114 * @since 3.16.0
115 */
116 function updateAllFeatureSections(): void {
117 if ( ! aiFeaturesCheckbox ) {
118 return;
119 }
120
121 if ( aiFeaturesCheckbox.checked ) {
122 // Enable all applicable fieldsets.
123 fieldsets.forEach( ( fieldset: Element ): void => {
124 setDisabled( fieldset, false );
125
126 featureCheckboxes.forEach( ( checkbox ): void => {
127 updateFeatureSection( checkbox );
128 } );
129 } );
130 } else {
131 // Disable all fieldsets.
132 fieldsets.forEach( ( fieldset: Element ): void => {
133 if ( ! fieldset.querySelector( `#${ aiFeaturesCheckbox.id }` ) ) {
134 setDisabled( fieldset );
135 }
136 } );
137
138 // Disable "Enabled" labels.
139 document.querySelectorAll( 'label.prevent-disable' )
140 .forEach( ( label: Element ): void => {
141 setPreventDisable( label, false );
142 } );
143 }
144 }
145
146 /**
147 * Turns on/off a specific feature section in the UI.
148 *
149 * @since 3.16.0
150 *
151 * @param {HTMLInputElement} checkbox The checkbox controlling the feature.
152 */
153 function updateFeatureSection( checkbox: HTMLInputElement ): void {
154 const userRolesFieldset = checkbox
155 .closest( 'fieldset' )?.nextSibling?.nextSibling as HTMLElement;
156
157 if ( checkbox.checked ) {
158 setDisabled( [ checkbox, userRolesFieldset ], false );
159 } else {
160 setDisabled( userRolesFieldset );
161
162 // Keep enabled styling on "Enabled" labels for visibility.
163 const enabledLabel = checkbox.parentElement as HTMLLabelElement;
164 setPreventDisable( enabledLabel );
165 }
166 }
167
168 /**
169 * Allows or prevents an element from being shown as disabled.
170 *
171 * This is done by injecting a class into the element, which is used in CSS.
172 *
173 * @since 3.16.0
174 *
175 * @param {Element} element The target element.
176 * @param {boolean} status true to prevent disabled style, false to allow.
177 */
178 function setPreventDisable( element: Element, status: boolean = true ): void {
179 if ( status ) {
180 element.classList.add( 'prevent-disable' );
181 } else {
182 element.classList.remove( 'prevent-disable' );
183 }
184 }
185
186 /**
187 * Sets the disabled attribute on an element.
188 *
189 * @since 3.16.0
190 *
191 * @param {Element | Element[]} element The target element.
192 * @param {boolean} status true to disable, false to enable.
193 */
194 function setDisabled( element: Element | Element[], status: boolean = true ): void {
195 if ( ! Array.isArray( element ) ) {
196 element = [ element ];
197 }
198
199 element.forEach( ( el: Element ): void => {
200 if ( status ) {
201 el.setAttribute( 'disabled', 'disabled' );
202 } else {
203 el.removeAttribute( 'disabled' );
204 }
205 } );
206 }
207
208 /**
209 * Allows the form to post its whole data.
210 *
211 * When elements are disabled, they are not included when submitting the
212 * form, resulting in missing data.
213 *
214 * This workaround removes any disabled attribute so the form can submit its
215 * whole data. It also prevents styling changes while the data is being
216 * submitted, in order to avoid visual glitches.
217 *
218 * @since 3.16.0
219 */
220 function enableAllFormFieldsOnSubmit(): void {
221 document.querySelector( '.wp-admin form[name="parsely"]' )
222 ?.addEventListener( 'submit', (): void => {
223 const baseSelector = '.wp-admin .content-intelligence-section fieldset';
224
225 document.querySelectorAll( `${ baseSelector }[disabled]` )
226 .forEach( ( fieldset: Element ): void => {
227 // Style the whole setting table row as disabled.
228 fieldset.parentElement?.parentElement?.classList.add(
229 'disabled-before-posting'
230 );
231
232 // Avoid disabled checkbox styling changes.
233 fieldset.querySelectorAll(
234 `${ baseSelector } label input[type="checkbox"]`
235 ).forEach( ( input: Element ): void => {
236 input.classList.add( 'disabled' );
237 } );
238
239 fieldset.removeAttribute( 'disabled' );
240 } );
241 } );
242 }
243 }
244
245 /**
246 * Displays any messages needed under the Content Intelligence section.
247 *
248 * @since 3.19.0
249 */
250 async function displayContentHelperSectionMessages(): Promise<void> {
251 let message = null;
252 let authResponse = null;
253
254 try {
255 const [ apiAuth, trafficBoostAuth ] = await Promise.all( [
256 CheckAuthProvider.getInstance().getAuthorizationResponse(
257 { auth_scope: 'suggestions_api' }
258 ),
259 CheckAuthProvider.getInstance().getAuthorizationResponse(
260 { auth_scope: 'traffic_boost' }
261 ),
262 ] );
263
264 authResponse = {
265 api: apiAuth,
266 traffic_boost: trafficBoostAuth,
267 };
268 } catch ( err: unknown ) {
269 console.error( err ); // eslint-disable-line no-console
270
271 if ( err instanceof ContentHelperError ) {
272 if ( ContentHelperErrorCode.PluginSettingsApiSecretNotSet === err.code ) {
273 message = sprintf( '<p><strong>%s</strong></p>', escapeHTML( __( 'All Content Intelligence AI functionality is disabled because an API Secret has not been set.', 'wp-parsely' ) ) );
274 }
275 }
276 } finally {
277 if ( authResponse ) {
278 if ( 200 !== authResponse.api.code ) {
279 const requestAccessLink = sprintf( '<a href="%1$s" target="_blank" rel="noopener">%2$s</a>', 'https://wpvip.com/content-helper/#content-helper-form', __( 'Request access here', 'wp-parsely' ) );
280 /* translators: %s: Link to request access to Content Intelligence AI functionality. */
281 const messageWithAccessLink = sprintf( escapeHTML( __( 'All Content Intelligence AI functionality is disabled for this website. %s.', 'wp-parsely' ) ), requestAccessLink );
282 message = sprintf( '<p><strong>%s</strong></p>', messageWithAccessLink );
283 } else if ( 200 === authResponse.api.code && 200 !== authResponse.traffic_boost.code ) {
284 const contactSupportLink = sprintf( '<a href="%1$s">%2$s</a>', 'mailto:support@parsely.com', 'support@parsely.com' );
285 /* translators: %s: Link to request access to Content Intelligence AI functionality. */
286 const messageWithAccessLink = sprintf( escapeHTML( __( 'Engagement Boost functionality is disabled for this website. To enable it, contact %s.', 'wp-parsely' ) ), contactSupportLink );
287 message = sprintf( '<p><strong>%s</strong></p>', messageWithAccessLink );
288 }
289 }
290
291 if ( message ) {
292 const div = document.createElement( 'div' );
293 div.className = 'content-helper-message notice notice-error';
294 div.innerHTML = message;
295 const contentHelperSection = document.querySelector( '.content-intelligence-section' );
296 if ( contentHelperSection ) {
297 contentHelperSection.insertBefore( div, contentHelperSection.firstChild );
298 }
299 }
300 }
301 }
302