| 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/classic-editor.js |
| 148 |
/** |
| 149 |
* @package Polylang |
| 150 |
*/ |
| 151 |
|
| 152 |
|
| 153 |
|
| 154 |
|
| 155 |
|
| 156 |
// tag suggest in metabox |
| 157 |
jQuery( |
| 158 |
function ( $ ) { |
| 159 |
$.ajaxPrefilter( |
| 160 |
function ( options, originalOptions, jqXHR ) { |
| 161 |
var lang = $( '.post_lang_choice' ).val(); |
| 162 |
if ( 'string' === typeof options.data && -1 !== options.url.indexOf( 'action=ajax-tag-search' ) && lang ) { |
| 163 |
options.data = 'lang=' + lang + '&' + options.data; |
| 164 |
} |
| 165 |
} |
| 166 |
); |
| 167 |
} |
| 168 |
); |
| 169 |
|
| 170 |
// overrides tagBox.get |
| 171 |
jQuery( |
| 172 |
function ( $ ) { |
| 173 |
// overrides function to add the language |
| 174 |
tagBox.get = function ( id ) { |
| 175 |
var tax = id.substr( id.indexOf( '-' ) + 1 ); |
| 176 |
|
| 177 |
// add the language in the $_POST variable |
| 178 |
var data = { |
| 179 |
action: 'get-tagcloud', |
| 180 |
lang: $( '.post_lang_choice' ).val(), |
| 181 |
tax: tax |
| 182 |
} |
| 183 |
|
| 184 |
$.post( |
| 185 |
ajaxurl, |
| 186 |
data, |
| 187 |
function ( r, stat ) { |
| 188 |
if ( 0 == r || 'success' != stat ) { |
| 189 |
r = wpAjax.broken; |
| 190 |
} |
| 191 |
|
| 192 |
// @see code from WordPress core https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/js/tags-box.js#L291 |
| 193 |
// @see wp_generate_tag_cloud function which generate the escaped HTML https://github.com/WordPress/WordPress/blob/a02b5cc2a8eecb8e076fbb7cf4de7bd2ec8a8eb1/wp-includes/category-template.php#L966-L975 |
| 194 |
r = $( '<div />' ).addClass( 'the-tagcloud' ).attr( 'id', 'tagcloud-' + tax ).html( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html |
| 195 |
$( 'a', r ).on( |
| 196 |
'click', |
| 197 |
function () { |
| 198 |
tagBox.flushTags( $( this ).closest( '.inside' ).children( '.tagsdiv' ), this ); |
| 199 |
return false; |
| 200 |
} |
| 201 |
); |
| 202 |
|
| 203 |
var tagCloud = $( '#tagcloud-' + tax ); |
| 204 |
// add an if else condition to allow modifying the tags outputted when switching the language |
| 205 |
var v = tagCloud.css( 'display' ); |
| 206 |
if ( v ) { |
| 207 |
// See the comment above when r variable is created. |
| 208 |
$( '#tagcloud-' + tax ).replaceWith( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith |
| 209 |
$( '#tagcloud-' + tax ).css( 'display', v ); |
| 210 |
} |
| 211 |
else { |
| 212 |
// See the comment above when r variable is created. |
| 213 |
$( '#' + id ).after( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after |
| 214 |
} |
| 215 |
} |
| 216 |
); |
| 217 |
} |
| 218 |
} |
| 219 |
); |
| 220 |
|
| 221 |
jQuery( |
| 222 |
function ( $ ) { |
| 223 |
// collect taxonomies - code partly copied from WordPress |
| 224 |
var taxonomies = new Array(); |
| 225 |
$( '.categorydiv' ).each( |
| 226 |
function () { |
| 227 |
var this_id = $( this ).attr( 'id' ), taxonomyParts, taxonomy; |
| 228 |
|
| 229 |
taxonomyParts = this_id.split( '-' ); |
| 230 |
taxonomyParts.shift(); |
| 231 |
taxonomy = taxonomyParts.join( '-' ); |
| 232 |
taxonomies.push( taxonomy ); // store the taxonomy for future use |
| 233 |
|
| 234 |
// add our hidden field in the new category form - for each hierarchical taxonomy |
| 235 |
// to set the language when creating a new category |
| 236 |
// html code inserted come from html code itself. |
| 237 |
$( '#' + taxonomy + '-add-submit' ).before( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.before |
| 238 |
$( '<input />' ).attr( 'type', 'hidden' ) |
| 239 |
.attr( 'id', taxonomy + '-lang' ) |
| 240 |
.attr( 'name', 'term_lang_choice' ) |
| 241 |
.attr( 'value', $( '.post_lang_choice' ).val() ) |
| 242 |
); |
| 243 |
} |
| 244 |
); |
| 245 |
|
| 246 |
// Initialize current language to be able to compare if it changes. |
| 247 |
initializeLanguageOldValue(); |
| 248 |
|
| 249 |
// ajax for changing the post's language in the languages metabox |
| 250 |
$( '.post_lang_choice' ).on( |
| 251 |
'change', |
| 252 |
function ( event ) { |
| 253 |
// Initialize the confirmation dialog box. |
| 254 |
const confirmationModal = initializeConfirmationModal(); |
| 255 |
const { dialogContainer: dialog } = confirmationModal; |
| 256 |
let { dialogResult } = confirmationModal; |
| 257 |
// The selected option in the dropdown list. |
| 258 |
const selectedOption = event.target; |
| 259 |
|
| 260 |
if ( $( this ).data( 'old-value' ) !== selectedOption.value && ! isEmptyPost() ) { |
| 261 |
dialog.dialog( 'open' ); |
| 262 |
} else { |
| 263 |
dialogResult = Promise.resolve(); |
| 264 |
} |
| 265 |
|
| 266 |
dialogResult.then( |
| 267 |
() => { |
| 268 |
var data = { |
| 269 |
action: 'post_lang_choice', |
| 270 |
lang: selectedOption.value, |
| 271 |
post_type: $( '#post_type' ).val(), |
| 272 |
taxonomies: taxonomies, |
| 273 |
post_id: $( '#post_ID' ).val(), |
| 274 |
_pll_nonce: $( '#_pll_nonce' ).val() |
| 275 |
} |
| 276 |
|
| 277 |
$.post( |
| 278 |
ajaxurl, |
| 279 |
data, |
| 280 |
function ( response ) { |
| 281 |
// Target a non existing WP HTML id to avoid a conflict with WP ajax requests. |
| 282 |
var res = wpAjax.parseAjaxResponse( response, 'pll-ajax-response' ); |
| 283 |
$.each( |
| 284 |
res.responses, |
| 285 |
function () { |
| 286 |
switch ( this.what ) { |
| 287 |
case 'translations': // translations fields |
| 288 |
// Data is built and come from server side and is well escaped when necessary |
| 289 |
$( '.translations' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html |
| 290 |
initMetaboxAutoComplete(); |
| 291 |
break; |
| 292 |
case 'taxonomy': // categories metabox for posts |
| 293 |
var tax = this.data; |
| 294 |
// @see wp_terms_checklist https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/template.php#L175 |
| 295 |
// @see https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/class-walker-category-checklist.php#L89-L111 |
| 296 |
$( '#' + tax + 'checklist' ).html( this.supplemental.all ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html |
| 297 |
// @see wp_popular_terms_checklist https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/template.php#L236 |
| 298 |
$( '#' + tax + 'checklist-pop' ).html( this.supplemental.populars ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html |
| 299 |
// @see wp_dropdown_categories https://github.com/WordPress/WordPress/blob/5.5.1/wp-includes/category-template.php#L336 |
| 300 |
// which is called by PLL_Admin_Classic_Editor::post_lang_choice to generate supplemental.dropdown |
| 301 |
$( '#new' + tax + '_parent' ).replaceWith( this.supplemental.dropdown ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith |
| 302 |
$( '#' + tax + '-lang' ).val( $( '.post_lang_choice' ).val() ); // hidden field |
| 303 |
break; |
| 304 |
case 'pages': // parent dropdown list for pages |
| 305 |
// @see wp_dropdown_pages https://github.com/WordPress/WordPress/blob/5.2.2/wp-includes/post-template.php#L1186-L1208 |
| 306 |
// @see https://github.com/WordPress/WordPress/blob/5.2.2/wp-includes/class-walker-page-dropdown.php#L88 |
| 307 |
$( '#parent_id' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html |
| 308 |
break; |
| 309 |
case 'flag': // flag in front of the select dropdown |
| 310 |
// Data is built and come from server side and is well escaped when necessary |
| 311 |
$( '.pll-select-flag' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html |
| 312 |
break; |
| 313 |
case 'permalink': // Sample permalink |
| 314 |
var div = $( '#edit-slug-box' ); |
| 315 |
if ( '-1' != this.data && div.children().length ) { |
| 316 |
// @see get_sample_permalink_html https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/post.php#L1425-L1454 |
| 317 |
div.html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html |
| 318 |
} |
| 319 |
break; |
| 320 |
} |
| 321 |
} |
| 322 |
); |
| 323 |
|
| 324 |
// Creates an event once the language has been successfully changed. |
| 325 |
const onPostLangChoice = new CustomEvent( |
| 326 |
"onPostLangChoice", |
| 327 |
{ |
| 328 |
detail: { |
| 329 |
lang: JSON.parse( selectedOption.options[selectedOption.options.selectedIndex].getAttribute( 'data-lang' ) ) |
| 330 |
}, |
| 331 |
} |
| 332 |
); |
| 333 |
document.dispatchEvent( onPostLangChoice ); |
| 334 |
} |
| 335 |
) |
| 336 |
}, |
| 337 |
() => {} // Do nothing when promise is rejected by clicking the Cancel dialog button. |
| 338 |
); |
| 339 |
|
| 340 |
function isEmptyPost() { |
| 341 |
const title = $( 'input#title' ).val(); |
| 342 |
const content = $( 'textarea#content' ).val(); |
| 343 |
const excerpt = $( 'textarea#excerpt' ).val(); |
| 344 |
|
| 345 |
return ! title && ! content && ! excerpt; |
| 346 |
} |
| 347 |
} |
| 348 |
); |
| 349 |
|
| 350 |
// Listen to `onPostLangChoice` to perform actions after the language has been changed. |
| 351 |
document.addEventListener( |
| 352 |
'onPostLangChoice', |
| 353 |
( e ) => { |
| 354 |
// Update the old language with the new one to be able to compare it in the next changing. |
| 355 |
initializeLanguageOldValue(); |
| 356 |
|
| 357 |
// Modifies the language in the tag cloud. |
| 358 |
$( '.tagcloud-link' ).each( |
| 359 |
function () { |
| 360 |
var id = $( this ).attr( 'id' ); |
| 361 |
tagBox.get( id ); |
| 362 |
} |
| 363 |
); |
| 364 |
|
| 365 |
// Modifies the text direction. |
| 366 |
let dir = e.detail.lang.is_rtl ? 'rtl' : 'ltr' |
| 367 |
$( 'body' ).removeClass( 'pll-dir-rtl' ).removeClass( 'pll-dir-ltr' ).addClass( 'pll-dir-' + dir ); |
| 368 |
$( '#content_ifr' ).contents().find( 'html' ).attr( 'lang', e.detail.lang.locale ).attr( 'dir', dir ); |
| 369 |
$( '#content_ifr' ).contents().find( 'body' ).attr( 'dir', dir ); |
| 370 |
|
| 371 |
// Refresh media libraries. |
| 372 |
pll.media.resetAllAttachmentsCollections(); |
| 373 |
} |
| 374 |
); |
| 375 |
|
| 376 |
initMetaboxAutoComplete(); |
| 377 |
} |
| 378 |
); |
| 379 |
|
| 380 |
/** |
| 381 |
* @since 3.0 |
| 382 |
* |
| 383 |
* @namespace pll |
| 384 |
*/ |
| 385 |
var pll = window.pll || {}; |
| 386 |
|
| 387 |
/** |
| 388 |
* @since 3.0 |
| 389 |
* |
| 390 |
* @namespace pll.media |
| 391 |
*/ |
| 392 |
_.extend( pll, { media: {} } ); |
| 393 |
|
| 394 |
/** |
| 395 |
* @since 3.0 |
| 396 |
* |
| 397 |
* @alias pll.media |
| 398 |
* @memberOf pll |
| 399 |
* @namespace |
| 400 |
*/ |
| 401 |
var media = _.extend( |
| 402 |
pll.media, /** @lends pll.media.prototype */ |
| 403 |
{ |
| 404 |
/** |
| 405 |
* TODO: Find a way to delete references to Attachments collections that are not used anywhere else. |
| 406 |
* |
| 407 |
* @type {wp.media.model.Attachments} |
| 408 |
*/ |
| 409 |
attachmentsCollections : [], |
| 410 |
|
| 411 |
/** |
| 412 |
* Imitates { @see wp.media.query } but log all Attachments collections created. |
| 413 |
* |
| 414 |
* @param {Object} [props] |
| 415 |
* @return {wp.media.model.Attachments} |
| 416 |
*/ |
| 417 |
query: function ( props ) { |
| 418 |
var attachments = pll.media.query.delegate( props ); |
| 419 |
|
| 420 |
pll.media.attachmentsCollections.push( attachments ); |
| 421 |
|
| 422 |
return attachments; |
| 423 |
}, |
| 424 |
|
| 425 |
resetAllAttachmentsCollections: function () { |
| 426 |
this.attachmentsCollections.forEach( |
| 427 |
function ( attachmentsCollection ) { |
| 428 |
/** |
| 429 |
* First reset the { @see wp.media.model.Attachments } collection. |
| 430 |
* Then, if it is mirroring a { @see wp.media.model.Query } collection, |
| 431 |
* refresh this one too, so it will fetch new data from the server, |
| 432 |
* and then the wp.media.model.Attachments collection will synchronize with the new data. |
| 433 |
*/ |
| 434 |
attachmentsCollection.reset(); |
| 435 |
if (attachmentsCollection.mirroring) { |
| 436 |
attachmentsCollection.mirroring._hasMore = true; |
| 437 |
attachmentsCollection.mirroring.reset(); |
| 438 |
} |
| 439 |
} |
| 440 |
); |
| 441 |
} |
| 442 |
} |
| 443 |
); |
| 444 |
|
| 445 |
if ( 'undefined' !== typeof wp && 'undefined' !== typeof wp.media ) { |
| 446 |
|
| 447 |
/** |
| 448 |
* @since 3.0 |
| 449 |
* |
| 450 |
* @memberOf pll.media |
| 451 |
*/ |
| 452 |
media.query = _.extend( |
| 453 |
media.query, /** @lends pll.media.query prototype */ |
| 454 |
{ |
| 455 |
/** |
| 456 |
* @type Function References WordPress { @see wp.media.query } constructor |
| 457 |
*/ |
| 458 |
delegate: wp.media.query |
| 459 |
} |
| 460 |
) |
| 461 |
|
| 462 |
// Substitute WordPress media query shortcut with our decorated function. |
| 463 |
wp.media.query = media.query |
| 464 |
|
| 465 |
} |
| 466 |
|
| 467 |
|