PluginProbe
Polylang / 3.6.7
Polylang v3.6.7
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / js / build / block-editor.js

block-editor.js in Polylang 3.6.7, at js/build/block-editor.js

406 lines 12.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /******/ "use strict";
2
3 ;// ./js/src/lib/confirmation-modal.js
4 /**
5 * @package Polylang
6 */
7
8 const languagesList = jQuery( '.post_lang_choice' );
9
10 // Dialog box for alerting the user about a risky changing.
11 const initializeConfirmationModal = () => {
12 // We can't use underscore or lodash in this common code because it depends of the context classic or block editor.
13 // Classic editor underscore is loaded, Block editor lodash is loaded.
14 const { __ } = wp.i18n;
15
16 // Create dialog container.
17 const dialogContainer = jQuery(
18 '<div/>',
19 {
20 id: 'pll-dialog',
21 style: 'display:none;'
22 }
23 ).text( __( 'Are you sure you want to change the language of the current content?', 'polylang' ) );
24
25 // Put it after languages list dropdown.
26 // PHPCS ignore dialogContainer is a new safe HTML code generated above.
27 languagesList.after( dialogContainer ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after
28
29 const dialogResult = new Promise(
30 ( confirm, cancel ) => {
31 const confirmDialog = ( what ) => { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent
32 switch ( what ) { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent
33 case 'yes':
34 // Confirm the new language.
35 languagesList.data( 'old-value', languagesList.children( ':selected' ).first().val() );
36 confirm();
37 break;
38 case 'no':
39 // Revert to the old language.
40 languagesList.val( languagesList.data( 'old-value' ) );
41 cancel( 'Cancel' );
42 break;
43 }
44 dialogContainer.dialog( 'close' ); // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent
45 } // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent
46
47 // Initialize dialog box in the case a language is selected but not added in the list.
48 const dialogOptions = {
49 autoOpen: false,
50 modal: true,
51 draggable: false,
52 resizable: false,
53 title: __( 'Change language', 'polylang' ),
54 minWidth: 600,
55 maxWidth: '100%',
56 open: function ( event, ui ) {
57 // Change dialog box position for rtl language
58 if ( jQuery( 'body' ).hasClass( 'rtl' ) ) {
59 jQuery( this ).parent().css(
60 {
61 right: jQuery( this ).parent().css( 'left' ),
62 left: 'auto'
63 }
64 );
65 }
66 },
67 close: function ( event, ui ) {
68 // When we're closing the dialog box we need to cancel the language change as we click on Cancel button.
69 confirmDialog( 'no' );
70 },
71 buttons: [
72 {
73 text: __( 'OK', 'polylang' ),
74 click: function ( event ) {
75 confirmDialog( 'yes' );
76 }
77 },
78 {
79 text: __( 'Cancel', 'polylang' ),
80 click: function ( event ) {
81 confirmDialog( 'no' );
82 }
83 }
84 ]
85 };
86
87 if ( jQuery.ui.version >= '1.12.0' ) {
88 Object.assign( dialogOptions, { classes: { 'ui-dialog': 'pll-confirmation-modal' } } );
89 } else {
90 Object.assign( dialogOptions, { dialogClass: 'pll-confirmation-modal' } ); // jQuery UI 1.11.4 - WP < 5.6
91 }
92
93 dialogContainer.dialog( dialogOptions );
94 }
95 );
96 return { dialogContainer, dialogResult };
97 }
98
99 const initializeLanguageOldValue = () => {
100 // Keep the old language value to be able to compare to the new one and revert to it if necessary.
101 languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() );
102 };
103
104 ;// ./js/src/lib/metabox-autocomplete.js
105 /**
106 * @package Polylang
107 */
108
109 // Translations autocomplete input box.
110 function initMetaboxAutoComplete() {
111 jQuery('.tr_lang').each(
112 function () {
113 var tr_lang = jQuery(this).attr('id').substring(8);
114 var td = jQuery(this).parent().parent().siblings('.pll-edit-column');
115
116 jQuery(this).autocomplete(
117 {
118 minLength: 0,
119 source: ajaxurl + '?action=pll_posts_not_translated' +
120 '&post_language=' + jQuery('.post_lang_choice').val() +
121 '&translation_language=' + tr_lang +
122 '&post_type=' + jQuery('#post_type').val() +
123 '&_pll_nonce=' + jQuery('#_pll_nonce').val(),
124 select: function (event, ui) {
125 jQuery('#htr_lang_' + tr_lang).val(ui.item.id);
126 // ui.item.link is built and come from server side and is well escaped when necessary
127 td.html(ui.item.link); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
128 },
129 }
130 );
131
132 // when the input box is emptied
133 jQuery(this).on(
134 'blur',
135 function () {
136 if ( ! jQuery(this).val() ) {
137 jQuery('#htr_lang_' + tr_lang).val(0);
138 // Value is retrieved from HTML already generated server side
139 td.html(td.siblings('.hidden').children().clone()); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html
140 }
141 }
142 );
143 }
144 );
145 }
146
147 ;// ./js/src/lib/filter-path-middleware.js
148 /**
149 * @package Polylang
150 */
151
152 /**
153 * Filters requests for translatable entities.
154 * This logic is shared across all Polylang plugins.
155 *
156 * @since 3.5
157 *
158 * @param {APIFetchOptions} options
159 * @param {Array} filteredRoutes
160 * @param {CallableFunction} filter
161 * @returns {APIFetchOptions}
162 */
163 const filterPathMiddleware = ( options, filteredRoutes, filter ) => {
164 const cleanPath = options.path.split( '?' )[0].replace(/^\/+|\/+$/g, ''); // Get path without query parameters and trim '/'.
165
166 return Object.values( filteredRoutes ).find( ( path ) => cleanPath === path ) ? filter( options ) : options;
167 }
168
169 /* harmony default export */ const filter_path_middleware = (filterPathMiddleware);
170
171 ;// ./js/src/block-editor.js
172 /**
173 * @package Polylang
174 */
175
176
177
178
179
180
181
182 /**
183 * Filter REST API requests to add the language in the request
184 *
185 * @since 2.5
186 */
187 wp.apiFetch.use(
188 function ( options, next ) {
189 /*
190 * If options.url is defined, this is not a REST request but a direct call to post.php for legacy metaboxes.
191 * If `filteredRoutes` is not defined, return early.
192 */
193 if ( 'undefined' !== typeof options.url || 'undefined' === typeof pllFilteredRoutes ) {
194 return next( options );
195 }
196
197 return next( filter_path_middleware( options, pllFilteredRoutes, addLanguageParameter ) );
198 }
199 );
200
201 /**
202 * Gets the language of the currently edited post, fallback to default language if none is found.
203 *
204 * @since 2.5
205 *
206 * @return {Element.value}
207 */
208 function getCurrentLanguage() {
209 const lang = document.querySelector( '[name=post_lang_choice]' );
210
211 if ( null === lang ) {
212 return pllDefaultLanguage;
213 }
214
215 return lang.value;
216 }
217
218 /**
219 * Adds language parameter according to the current one (query string for GET, body for PUT and POST).
220 *
221 * @since 3.5
222 *
223 * @param {APIFetchOptions} options
224 * @returns {APIFetchOptions}
225 */
226 function addLanguageParameter( options ) {
227 if ( 'undefined' === typeof options.data || null === options.data ) {
228 // GET
229 options.path += ( ( options.path.indexOf( '?' ) >= 0 ) ? '&lang=' : '?lang=' ) + getCurrentLanguage();
230 } else {
231 // PUT, POST
232 options.data.lang = getCurrentLanguage();
233 }
234
235 return options;
236 }
237
238 /**
239 * Handles internals of the metabox:
240 * Language select, autocomplete input field.
241 *
242 * @since 1.5
243 *
244 * Save post after lang choice is done and redirect to the same page for refreshing all the data.
245 *
246 * @since 2.5
247 *
248 * Link post saving after refreshing the metabox.
249 *
250 * @since 3.0
251 */
252 jQuery(
253 function ( $ ) {
254 // Initialize current language to be able to compare if it changes.
255 initializeLanguageOldValue();
256
257
258 // Ajax for changing the post's language in the languages metabox
259 $( '.post_lang_choice' ).on(
260 'change',
261 function ( event ) {
262 const { select, dispatch, subscribe } = wp.data;
263 const emptyPost = isEmptyPost();
264 const { addQueryArgs } = wp.url;
265
266 // Initialize the confirmation dialog box.
267 const confirmationModal = initializeConfirmationModal();
268 const { dialogContainer : dialog } = confirmationModal;
269 let { dialogResult } = confirmationModal;
270 const selectedOption = event.target; // The selected option in the dropdown list.
271
272 // Specific case for empty posts.
273 // Place at the beginning because window.location change triggers automatically page reloading.
274 if ( location.pathname.match( /post-new.php/gi ) && emptyPost ) {
275 reloadPageForEmptyPost( selectedOption.value );
276 }
277
278 // Otherwise send an ajax request to refresh the legacy metabox and set the post language with the new language.
279 // It needs a confirmation of the user before changing the language.
280 // Need to wait the ajax response before triggering the block editor post save action.
281 if ( $( this ).data( 'old-value' ) !== selectedOption.value && ! emptyPost ) {
282 dialog.dialog( 'open' );
283 } else {
284 // Update the old language with the new one to be able to compare it in the next change.
285 // Because the page isn't reloaded in this case.
286 initializeLanguageOldValue();
287 dialogResult = Promise.resolve();
288 }
289
290 dialogResult.then(
291 () => {
292 let data = { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent
293 action: 'post_lang_choice',
294 lang: selectedOption.value,
295 post_type: $( '#post_type' ).val(),
296 post_id: $( '#post_ID' ).val(),
297 _pll_nonce: $( '#_pll_nonce' ).val()
298 }
299
300 // Update post language in database as soon as possible.
301 // Because, in addition of the block editor save process, the legacy metabox uses a post.php process to update the language and is too late compared to the page reload.
302 $.post(
303 ajaxurl,
304 data,
305 function () {
306 blockEditorSavePostAndReloadPage();
307 }
308 );
309 },
310 () => {} // Do nothing when promise is rejected by clicking the Cancel dialog button.
311 );
312
313 function isEmptyPost() {
314 const editor = select( 'core/editor' );
315
316 return ! editor.getEditedPostAttribute( 'title' )?.trim() && ! editor.getEditedPostContent() && ! editor.getEditedPostAttribute( 'excerpt' )?.trim();
317 }
318
319 /**
320 * Reload the block editor page for empty posts.
321 *
322 * @param {string} lang The target language code.
323 */
324 function reloadPageForEmptyPost( lang ) {
325 // Change the new_lang parameter with the new language value for reloading the page
326 // WPCS location.search is never written in the page, just used to reload page with the right value of new_lang
327 // new_lang input is controlled server side in PHP. The value come from the dropdown list of language returned and escaped server side.
328 // Notice that window.location changing triggers automatically page reloading.
329 if ( -1 != location.search.indexOf( 'new_lang' ) ) {
330 // use regexp non capturing group to replace new_lang parameter no matter where it is and capture other parameters which can be behind it
331 window.location.search = window.location.search.replace( /(?:new_lang=[^&]*)(&)?(.*)/, 'new_lang=' + lang + '$1$2' ); // phpcs:ignore WordPressVIPMinimum.JS.Window.location, WordPressVIPMinimum.JS.Window.VarAssignment
332 } else {
333 window.location.search = window.location.search + ( ( -1 != window.location.search.indexOf( '?' ) ) ? '&' : '?' ) + 'new_lang=' + lang; // phpcs:ignore WordPressVIPMinimum.JS.Window.location, WordPressVIPMinimum.JS.Window.VarAssignment
334 }
335 };
336
337 /**
338 * Triggers block editor post save and reload the block editor page when everything is ok.
339 */
340 function blockEditorSavePostAndReloadPage() {
341
342 let unsubscribe = null;
343 const previousPost = select( 'core/editor').getCurrentPost();
344
345 // Listen if the savePost is completely done by subscribing to its events.
346 const savePostIsDone = new Promise(
347 function ( resolve, reject ) {
348 unsubscribe = subscribe(
349 function () {
350 const post = select( 'core/editor').getCurrentPost();
351 const { id, status, type } = post;
352 const error = select( 'core' )
353 .getLastEntitySaveError(
354 'postType',
355 type,
356 id
357 );
358
359 if ( error ) {
360 reject();
361 }
362
363 if ( previousPost.modified !== post.modified ) {
364
365 if ( location.pathname.match( /post-new.php/gi ) && status !== 'auto-draft' && id ) {
366 window.history.replaceState(
367 { id },
368 'Post ' + id,
369 addQueryArgs( 'post.php', { post: id, action: 'edit' } )
370 );
371 }
372 resolve();
373 }
374 }
375 );
376 }
377 );
378
379 // Triggers the post save.
380 dispatch( 'core/editor' ).savePost();
381
382 // Process
383 savePostIsDone.then(
384 function () {
385 // If the post is well saved, we can reload the page
386 window.location.reload();
387 },
388 function () {
389 // If the post save failed
390 unsubscribe();
391 }
392 ).catch(
393 function () {
394 // If an exception is thrown
395 unsubscribe();
396 }
397 );
398 };
399 }
400 );
401
402 initMetaboxAutoComplete();
403 }
404 );
405
406