PluginProbe
Polylang / 3.8.10
Polylang v3.8.10
3.8.10 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 All 234 releases
← All changes | js/build/nav-menu.js +205 -90 3.0.2 → 3.8.10 View file →
@@ -3,103 +3,218 @@
3 3 *
4 4 * @package Polylang
5 5 */
6 6
7 -jQuery(
8 - function( $ ) {
9 - $( '#update-nav-menu' ).on(
10 - 'click',
11 - function( e ) {
12 - if ( e.target && e.target.className && -1 != e.target.className.indexOf( 'item-edit' ) ) {
13 - $( "input[value='#pll_switcher'][type=text]" ).parent().parent().parent().each(
14 - function(){
15 - var item = $( this ).attr( 'id' ).substring( 19 );
16 - $( this ).children( 'p:not( .field-move )' ).remove(); // remove default fields we don't need
7 +const pllNavMenu = {
8 + /**
9 + * The element wrapping the menu elements.
10 + *
11 + * @member {HTMLElement|null}
12 + */
13 + wrapper: null,
17 14
18 - // item is a number part of id of parent menu item built by WordPress
19 - // pll_data is built server side with i18n strings without HTML and data retrieved from post meta
20 - // the usage of attr method is safe before append call.
21 - h = $( '<input>' ).attr(
22 - {
23 - type: 'hidden',
24 - id: 'edit-menu-item-title-' + item,
25 - name: 'menu-item-title[' + item + ']',
26 - value: pll_data.title
27 - }
28 - );
29 - $( this ).append( h ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
15 + /**
16 + * Init.
17 + */
18 + init: () => {
19 + if ( document.readyState !== 'loading' ) {
20 + pllNavMenu.ready();
21 + } else {
22 + document.addEventListener( 'DOMContentLoaded', pllNavMenu.ready );
23 + }
24 + },
30 25
31 - h = $( '<input>' ).attr(
32 - {
33 - type: 'hidden',
34 - id: 'edit-menu-item-url-' + item,
35 - name: 'menu-item-url[' + item + ']',
36 - value: '#pll_switcher'
37 - }
38 - );
39 - $( this ).append( h ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
26 + /**
27 + * Called when the DOM is ready. Attaches the events to the wrapper.
28 + */
29 + ready: () => {
30 + pllNavMenu.wrapper = document.getElementById( 'menu-to-edit' );
40 31
41 - // a hidden field which exits only if our jQuery code has been executed
42 - h = $( '<input>' ).attr(
43 - {
44 - type: 'hidden',
45 - id: 'edit-menu-item-pll-detect-' + item,
46 - name: 'menu-item-pll-detect[' + item + ']',
47 - value: 1
48 - }
49 - );
50 - $( this ).append( h ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
32 + if ( ! pllNavMenu.wrapper ) {
33 + return;
34 + }
51 35
52 - ids = Array( 'hide_if_no_translation', 'hide_current', 'force_home', 'show_flags', 'show_names', 'dropdown' ); // reverse order
36 + pllNavMenu.wrapper.addEventListener( 'click', pllNavMenu.printMetabox );
37 + pllNavMenu.wrapper.addEventListener( 'change', pllNavMenu.ensureContent );
38 + pllNavMenu.wrapper.addEventListener( 'change', pllNavMenu.showHideRows );
39 + },
53 40
54 - // add the fields
55 - for ( var i = 0, idsLength = ids.length; i < idsLength; i++ ) {
56 - p = $( '<p>' ).attr( 'class', 'description' );
57 - // p is hardcoded just above by using attr method which is safe.
58 - $( this ).prepend( p ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend
59 - // item is a number part of id of parent menu item built by WordPress
60 - // pll_data is built server side with i18n strings without HTML
61 - label = $( '<label>' ).attr( 'for', 'edit-menu-item-' + ids[ i ] + '-' + item ).text( ' ' + pll_data.strings[ ids[ i ] ] );
62 - p.append( label ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
63 - cb = $( '<input>' ).attr(
64 - {
65 - type: 'checkbox',
66 - id: 'edit-menu-item-' + ids[ i ] + '-' + item,
67 - name: 'menu-item-' + ids[ i ] + '[' + item + ']',
68 - value: 1
69 - }
70 - );
71 - if ( ( typeof( pll_data.val[ item ] ) != 'undefined' && pll_data.val[ item ][ ids[ i ] ] == 1 ) || ( typeof( pll_data.val[ item ] ) == 'undefined' && ids[ i ] == 'show_names' ) ) { // show_names as default value
72 - cb.prop( 'checked', true );
73 - }
74 - // See reasons above. Checkbox are totaly hardcoded here with safe value
75 - label.prepend( cb ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend
76 - }
77 - }
78 - );
41 + printMetabox: {
42 + /**
43 + * Event callback that prints our checkboxes in the language switcher.
44 + *
45 + * @param {Event} event The event.
46 + */
47 + handleEvent: ( event ) => {
48 + if ( ! event.target.classList.contains( 'item-edit' ) ) {
49 + // Not clicking on a Edit arrow button.
50 + return;
51 + }
79 52
80 - // disallow unchecking both show names and show flags
81 - $( '.menu-item-data-object-id' ).each(
82 - function() {
83 - var id = $( this ).val();
84 - var options = ['names-', 'flags-'];
85 - $.each(
86 - options,
87 - function( i, v ) {
88 - $( '#edit-menu-item-show_' + v + id ).on(
89 - 'change',
90 - function() {
91 - if ( true != $( this ).prop( 'checked' ) ) {
92 - $( '#edit-menu-item-show_' + options[ 1 - i ] + id ).prop( 'checked', true );
93 - }
94 - }
95 - );
96 - }
97 - );
98 - }
99 - );
53 + const metabox = event.target.closest( '.menu-item' ).querySelector( '.menu-item-settings' );
54 +
55 + if ( ! metabox?.id ) {
56 + // Should not happen.
57 + return;
58 + }
59 +
60 + if ( ! metabox.querySelectorAll( 'input[value="#pll_switcher"][type=text]' ).length ) {
61 + // Not our metabox, or already replaced.
62 + return;
63 + }
64 +
65 + // Remove default fields we don't need.
66 + [ ...metabox.children ].forEach( ( el ) => {
67 + if ( 'P' === el.nodeName && ! el.classList.contains( 'field-move' ) ) {
68 + el.remove();
100 69 }
70 + } );
71 +
72 + const t = pllNavMenu.printMetabox;
73 + const itemId = Number( metabox.id.replace( 'menu-item-settings-', '' ) );
74 +
75 + metabox.append( t.createHiddenInput( 'title', itemId, pll_data.title ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
76 + metabox.append( t.createHiddenInput( 'url', itemId, '#pll_switcher' ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
77 + metabox.append( t.createHiddenInput( 'pll-detect', itemId, 1 ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
78 +
79 + const ids = Array( 'hide_if_no_translation', 'hide_current', 'force_home', 'show_flags', 'show_names', 'dropdown' ); // Reverse order.
80 + const isValDefined = typeof( pll_data.val[ itemId ] ) !== 'undefined';
81 +
82 + ids.forEach( ( optionName ) => {
83 + // Create the checkbox's wrapper.
84 + const inputWrapper = t.createElement( 'p', { class: 'description' } );
85 +
86 + if ( 'hide_current' === optionName && isValDefined && 1 === pll_data.val[ itemId ].dropdown ) {
87 + // Hide the `hide_current` checkbox if `dropdown` is checked.
88 + inputWrapper.classList.add( 'hidden' );
89 + }
90 +
91 + metabox.prepend( inputWrapper ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend
92 +
93 + // Create the checkbox's label.
94 + const inputId = `edit-menu-item-${ optionName }-${ itemId }`;
95 + const label = t.createElement( 'label', { 'for': inputId } );
96 + label.innerText = ` ${ pll_data.strings[ optionName ] }`;
97 +
98 + inputWrapper.append( label ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
99 +
100 + // Create the checkbox.
101 + const cb = t.createElement( 'input', {
102 + type: 'checkbox',
103 + id: inputId,
104 + name: `menu-item-${ optionName }[${ itemId }]`,
105 + value: 1,
106 + } );
107 +
108 + if ( ( isValDefined && 1 === pll_data.val[ itemId ][ optionName ] ) || ( ! isValDefined && 'show_names' === optionName ) ) { // `show_names` as default value.
109 + cb.checked = true;
110 + }
111 +
112 + label.prepend( cb ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend
113 + } );
114 + },
115 +
116 + /**
117 + * Creates and returns a `<input type=hidden"/>` element.
118 + *
119 + * @param {string} id An identifier for this input. It will be part of the final `id` attribute.
120 + * @param {number} itemId The ID of the menu element (post ID).
121 + * @param {string|number} value The input's value.
122 + * @return {HTMLElement} The input element.
123 + */
124 + createHiddenInput: ( id, itemId, value ) => {
125 + return pllNavMenu.printMetabox.createElement( 'input', {
126 + type: 'hidden',
127 + id: `edit-menu-item-${ id }-${ itemId }`,
128 + name: `menu-item-${ id }[${ itemId }]`,
129 + value: value,
130 + } );
131 + },
132 +
133 + /**
134 + * Creates and returns an element.
135 + *
136 + * @param {string} type Element's type.
137 + * @param {Object} atts Element's attributes.
138 + * @return {HTMLElement} The element.
139 + */
140 + createElement: ( type, atts ) => {
141 + const el = document.createElement( type );
142 + for ( const [ key, value ] of Object.entries( atts ) ) {
143 + el.setAttribute( key, value );
101 144 }
102 - );
103 - }
104 -);
145 + return el;
146 + },
147 + },
148 +
149 + ensureContent: {
150 + regExpr: new RegExp( /^edit-menu-item-show_(names|flags)-(\d+)$/ ),
151 +
152 + /**
153 + * Event callback that disallows unchecking both `show_names` and `show_flags`.
154 + *
155 + * @param {Event} event The event.
156 + */
157 + handleEvent: ( event ) => {
158 + if ( ! event.target.id || event.target.checked ) {
159 + // Now checked, nothing to do.
160 + return;
161 + }
162 +
163 + const matches = event.target.id.match( pllNavMenu.ensureContent.regExpr );
164 +
165 + if ( ! matches ) {
166 + // Not the checkbox we want.
167 + return;
168 + }
169 +
170 + // Check the other checkbox.
171 + const [ , type, id ] = matches;
172 + const otherType = 'names' === type ? 'flags' : 'names';
173 + document.getElementById( `edit-menu-item-show_${ otherType }-${ id }` ).checked = true;
174 + },
175 + },
176 +
177 + showHideRows: {
178 + regExpr: new RegExp( /^edit-menu-item-dropdown-(\d+)$/ ),
179 +
180 + /**
181 + * Event callback that shows or hides the `hide_current` checkbox when `dropdown` is checked.
182 + *
183 + * @param {Event} event The event.
184 + */
185 + handleEvent: ( event ) => {
186 + if ( ! event.target.id ) {
187 + // Not the checkbox we want.
188 + return;
189 + }
190 +
191 + const matches = event.target.id.match( pllNavMenu.showHideRows.regExpr );
192 +
193 + if ( ! matches ) {
194 + // Not the checkbox we want.
195 + return;
196 + }
197 +
198 + const hideCb = document.getElementById( `edit-menu-item-hide_current-${ matches[1] }` );
199 +
200 + if ( ! hideCb ) {
201 + // Should not happen.
202 + return;
203 + }
204 +
205 + const description = hideCb.closest( '.description' );
206 +
207 + // Hide or show.
208 + description.classList.toggle( 'hidden', event.target.checked );
209 +
210 + if ( event.target.checked ) {
211 + // Uncheck after hiding.
212 + hideCb.checked = false;
213 + hideCb.dispatchEvent( new Event( 'change' ) );
214 + }
215 + },
216 + },
217 +};
218 +
219 +pllNavMenu.init();
105 220