1var batcheditInterface = (function () { 2 function debounce(callback, timeout) { 3 var timeoutId = null; 4 5 var wrapper = function() { 6 var self = this; 7 var args = arguments; 8 9 if (timeoutId) { 10 clearTimeout(timeoutId); 11 } 12 13 timeoutId = setTimeout(function () { 14 callback.apply(self, args); 15 16 timeoutId = null; 17 }, timeout); 18 }; 19 20 return wrapper; 21 } 22 23 function escapeId(id) { 24 return id.replace(/([:.])/g, '\\$1'); 25 } 26 27 function observeStyleMutations($element, callback) { 28 return new MutationObserver(debounce(callback, 500)).observe($element.get(0), { 29 attributes: true, attributeFilter: ['style'] 30 }); 31 } 32 33 function getLang(id) { 34 if (id in batcheditLang) { 35 return batcheditLang[id]; 36 } 37 38 return 'undefined'; 39 } 40 41 function updateConfig(id, value) { 42 var config = Cookies.getJSON('BatchEditConfig'); 43 44 if (typeof config != 'undefined') { 45 config[id] = value; 46 47 Cookies.set('BatchEditConfig', config, { path: '', sameSite: 'Strict' }); 48 } 49 } 50 51 function initializeTooltip() { 52 jQuery.widget.bridge('uitooltip', jQuery.ui.tooltip); 53 jQuery('#batchedit').uitooltip({ 54 tooltipClass: 'be-tooltip', 55 show: {delay: 1000}, 56 track: true 57 }); 58 } 59 60 function initializeApplyCheckboxes() { 61 jQuery('#be-applyall').click(function() { 62 jQuery('.be-file input').prop('checked', this.checked); 63 }); 64 65 jQuery('.be-file .be-stats input').click(function() { 66 jQuery('.be-match input[id^=' + escapeId(this.id) + ']').prop('checked', this.checked); 67 }); 68 69 // When all single matches of a file have been checked, mark the appropriate file box as checked, too. 70 jQuery('.be-match input').click(function() { 71 var pageId = escapeId(this.id.substr(0, this.id.indexOf('#'))); 72 var pageMatches = jQuery('.be-match input[id^=' + pageId + ']').get(); 73 74 jQuery('#' + pageId).prop('checked', pageMatches.reduce(function(checked, input) { 75 return checked && input.checked; 76 }, true)); 77 }); 78 79 // Consolidate the list of all checked match ids into single hidden input field as 80 // a JSON-encoded array string. This avoids problems for huge replacement sets 81 // exceeding `max_input_vars` in `php.ini`. 82 jQuery('#batchedit form').submit(function() { 83 // Consolidate checked matches into a single string variable to be posted to the backend. 84 jQuery('input[name=apply]').val(JSON.stringify(jQuery('.be-match input:checked').map(function() { 85 return this.id; 86 }).get())); 87 }); 88 } 89 90 function initializeTotalStatsFloater() { 91 var $anchor = jQuery('#be-totalstats'); 92 var $floater = $anchor.children('div'); 93 94 var updateFloater = function() { 95 if ($anchor.length == 0) { 96 return; 97 } 98 99 if (window.pageYOffset > $anchor.offset().top) { 100 $floater.addClass('be-floater').width($anchor.css('width')); 101 } 102 else { 103 $floater.removeClass('be-floater'); 104 } 105 }; 106 107 jQuery(window).scroll(updateFloater); 108 109 updateFloater(); 110 } 111 112 function initializeSearchMode() { 113 var $searchMode = jQuery('input[name=searchmode]'); 114 var $advancedRegexp = jQuery('input[name=advregexp]'); 115 var $searchInputs = jQuery('#be-searchedit, #be-searcharea'); 116 var $replaceInputs = jQuery('#be-replaceedit, #be-replacearea'); 117 118 function updatePlaceholders(searchMode, advancedRegexp) { 119 var replaceMode = searchMode; 120 121 if (searchMode == 'regexp' && advancedRegexp) { 122 searchMode = 'advregexp'; 123 } 124 125 $searchInputs.prop('placeholder', getLang('hnt_' + searchMode + 'search')); 126 $replaceInputs.prop('placeholder', getLang('hnt_' + replaceMode + 'replace')); 127 } 128 129 $searchMode.click(function() { 130 updatePlaceholders(this.value, $advancedRegexp.prop('checked')); 131 updateConfig('searchmode', this.value); 132 }); 133 134 $advancedRegexp.click(function() { 135 updatePlaceholders($searchMode.filter(':checked').val(), this.checked); 136 updateConfig('advregexp', this.checked); 137 }); 138 } 139 140 function initializeMatchCase() { 141 jQuery('input[name=matchcase]').click(function() { 142 updateConfig('matchcase', this.checked); 143 }); 144 } 145 146 function initializeMultiline() { 147 var $multiline = jQuery('input[name=multiline]'); 148 var $searchEdit = jQuery('#be-searchedit'); 149 var $searchArea = jQuery('#be-searcharea'); 150 var $replaceEdit = jQuery('#be-replaceedit'); 151 var $replaceArea = jQuery('#be-replacearea'); 152 153 $multiline.click(function() { 154 if (this.checked) { 155 $searchEdit.hide(); 156 $replaceEdit.hide(); 157 $searchArea.val($searchEdit.val()).show(); 158 $replaceArea.val($replaceEdit.val()).show(); 159 } 160 else { 161 $searchArea.hide(); 162 $replaceArea.hide(); 163 $searchEdit.val($searchArea.val().replace(/\n/g, '\\n')).show(); 164 $replaceEdit.val($replaceArea.val().replace(/\n/g, '\\n')).show(); 165 } 166 167 updateConfig('multiline', this.checked); 168 }); 169 170 observeStyleMutations($searchArea, function() { 171 // Avoid using $searchArea.outerHeight() as it will have to modify display style 172 // of the element when it's hidden in order to get its height. This style mutation 173 // will trigger the observer again, causing an infintite loop. 174 if ($searchArea.get(0).offsetHeight > 0) { 175 updateConfig('searchheight', $searchArea.get(0).offsetHeight); 176 } 177 }); 178 179 observeStyleMutations($replaceArea, function() { 180 if ($replaceArea.get(0).offsetHeight > 0) { 181 updateConfig('replaceheight', $replaceArea.get(0).offsetHeight); 182 } 183 }); 184 185 jQuery('#batchedit form').submit(function() { 186 if (!$multiline.prop('checked')) { 187 $searchArea.val($searchEdit.val()); 188 $replaceArea.val($replaceEdit.val()); 189 } 190 }); 191 } 192 193 function initializeAdvancedOptions() { 194 $document = jQuery(document); 195 $options = jQuery('#be-extoptions'); 196 197 function onClickOutside(event) { 198 if (!$options.get(0).contains(event.target)) { 199 console.log('outside'); 200 close(); 201 } 202 } 203 204 function open() { 205 $options.show(); 206 $document.on('click', onClickOutside); 207 } 208 209 function close() { 210 $options.hide(); 211 $document.off('click', onClickOutside); 212 } 213 214 jQuery('a[href="javascript:openAdvancedOptions();"]').click(function() { 215 open(); 216 217 return false; 218 }); 219 220 jQuery('a[href="javascript:closeAdvancedOptions();"]').click(function() { 221 close(); 222 223 return false; 224 }); 225 } 226 227 function initializeMatchContext() { 228 var $contextChars = jQuery('input[name=ctxchars]'); 229 var $contextLines = jQuery('input[name=ctxlines]'); 230 231 jQuery('input[name=matchctx]').click(function() { 232 $contextChars.prop('disabled', !this.checked); 233 $contextLines.prop('disabled', !this.checked); 234 updateConfig('matchctx', this.checked); 235 }); 236 237 $contextChars.change(function() { 238 updateConfig('ctxchars', this.value); 239 }); 240 241 $contextLines.change(function() { 242 updateConfig('ctxlines', this.value); 243 }); 244 } 245 246 function initializeSearchLimit() { 247 var $searchMax = jQuery('input[name=searchmax]'); 248 249 jQuery('input[name=searchlimit]').click(function() { 250 $searchMax.prop('disabled', !this.checked); 251 updateConfig('searchlimit', this.checked); 252 }); 253 254 $searchMax.change(function() { 255 updateConfig('searchmax', this.value); 256 }); 257 } 258 259 function initializeKeepMarks() { 260 var $markPolicy = jQuery('select[name=markpolicy]'); 261 262 jQuery('input[name=keepmarks]').click(function() { 263 $markPolicy.prop('disabled', !this.checked); 264 updateConfig('keepmarks', this.checked); 265 }); 266 267 $markPolicy.change(function() { 268 updateConfig('markpolicy', this.value); 269 }); 270 } 271 272 function initializeApplyTemplatePatterns() { 273 jQuery('input[name=tplpatterns]').click(function() { 274 updateConfig('tplpatterns', this.checked); 275 }); 276 } 277 278 function initializeCheckSummary() { 279 jQuery('input[name=checksummary]').click(function() { 280 updateConfig('checksummary', this.checked); 281 }); 282 } 283 284 function startProgressMonitor() { 285 var hidden = true; 286 var $progress = jQuery('#be-progress'); 287 288 function updateProgress(data) { 289 $progress.text(data.operation).width((data.progress / 10) + "%"); 290 } 291 292 function checkProgress() { 293 setTimeout(function () { 294 batcheditServer.checkProgress(onProgressUpdate); 295 }, 500); 296 } 297 298 function onProgressUpdate(data) { 299 if (hidden && data.progress < 400) { 300 jQuery('#be-progressbar').css('display', 'flex'); 301 jQuery('input[name^=cmd').prop('disabled', true); 302 303 hidden = false; 304 } 305 306 if (!hidden) { 307 updateProgress(data); 308 checkProgress(); 309 } 310 } 311 312 checkProgress(); 313 } 314 315 function initializePreview() { 316 jQuery('input[name=cmd\\[preview\\]]').click(function() { 317 startProgressMonitor(); 318 }); 319 } 320 321 function initializeApply() { 322 jQuery('input[name=cmd\\[apply\\]]').click(function() { 323 var proceed = true; 324 325 if (jQuery('input[name=checksummary]').prop('checked') && 326 jQuery('input[name=summary]').val().replace(/\s+/, '') == '') { 327 proceed = confirm(getLang('war_nosummary')); 328 } 329 330 if (proceed) { 331 startProgressMonitor(); 332 } 333 334 return proceed; 335 }); 336 } 337 338 function initializeCancel() { 339 jQuery('input[name=cancel]').click(function() { 340 batcheditServer.cancelOperation(); 341 }); 342 } 343 344 function initialize() { 345 initializeTooltip(); 346 initializeApplyCheckboxes(); 347 initializeTotalStatsFloater(); 348 initializeSearchMode(); 349 initializeMatchCase(); 350 initializeMultiline(); 351 initializeAdvancedOptions(); 352 initializeMatchContext(); 353 initializeSearchLimit(); 354 initializeKeepMarks(); 355 initializeApplyTemplatePatterns(); 356 initializeCheckSummary(); 357 initializePreview(); 358 initializeApply(); 359 initializeCancel(); 360 } 361 362 return { 363 initialize : initialize 364 } 365})(); 366 367jQuery(function () { 368 batcheditInterface.initialize(); 369}); 370