PluginProbe ʕ •ᴥ•ʔ
Advanced Ads – Ad Manager & AdSense / 2.0.25
Advanced Ads – Ad Manager & AdSense v2.0.25
2.0.25 2.0.24 2.0.23 2.0.22 2.0.21 1.38.0 1.39.0 1.39.1 1.39.2 1.39.3 1.39.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.40.0 1.40.1 1.40.2 1.41.0 1.42.0 1.42.1 1.42.2 1.43.0 1.44.0 1.44.1 1.45.0 1.45.1 1.46.0 1.47.0 1.47.1 1.47.2 1.47.3 1.47.4 1.47.5 1.48.0 1.48.1 1.49.0 1.5.0 1.5.0.1 1.5.1 1.5.2 1.5.2.1 1.5.4 1.5.4.1 1.5.5 1.50.0 1.51.0 1.51.1 1.51.2 1.51.3 1.52.0 1.52.1 1.52.2 1.52.3 1.52.4 1.53.0 1.53.1 1.53.2 1.54.0 1.54.1 1.55.0 1.56.0 1.56.1 1.56.2 1.56.3 1.56.4 1.6 1.6.1 1.6.10 1.6.10.1 1.6.10.2 1.6.11 1.6.11.1 1.6.12 1.6.13 1.6.14 1.6.15 1.6.16 1.6.17 1.6.17.1 1.6.17.2 1.6.2 1.6.2.1 1.6.3 1.6.4 1.6.4.1 1.6.5 1.6.6 1.6.6.1 1.6.7 1.6.7.1 1.6.8 1.6.8.1 1.6.8.2 1.6.8.3 1.6.9 1.6.9.1 1.6.9.2 1.6.9.3 1.6.9.4 1.7 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.1.1 1.7.1.2 1.7.1.3 1.7.1.4 1.7.1.5 1.7.10 trunk 1.7.11 1.0.1 1.7.12 1.0.2 1.7.13 1.0.3 1.7.14 1.1.0 1.7.15 1.1.1 1.7.16 1.1.2 1.7.17 1.1.3 1.7.18 1.10 1.7.19 1.10.1 1.7.2 1.10.10 1.7.2.1 1.10.11 1.7.20 1.10.12 1.7.21 1.10.2 1.7.22 1.10.3 1.7.23 1.10.4 1.7.24 1.10.5 1.7.25 1.10.6 1.7.3 1.10.7 1.7.4 1.10.8 1.7.4.1 1.10.9 1.7.4.2 1.11 1.7.4.3 1.11.1 1.7.4.4 1.11.2 1.7.4.5 1.12 1.7.5 1.13 1.7.5.1 1.13.1 1.7.6 1.13.2 1.7.7 1.13.3 1.7.8 1.13.4 1.7.9 1.13.5 1.7.9.1 1.13.6 1.7.9.2 1.13.7 1.7.9.3 1.13.8 1.8 1.14 1.8.1 1.14.1 1.8.10 1.14.10 1.8.11 1.14.11 1.8.12 1.14.2 1.8.13 1.14.3 1.8.14 1.14.4 1.8.15 1.14.5 1.8.16 1.14.6 1.8.17 1.14.7 1.8.18 1.14.8 1.8.19 1.14.9 1.8.2 1.15 1.8.20 1.16 1.8.21 1.16.1 1.8.22 1.17 1.8.23 1.17.1 1.8.24 1.17.10 1.8.25 1.17.10-rc.1 1.8.26 1.17.11 1.8.27 1.17.12 1.8.28 1.17.12-rc.1 1.8.29 1.17.2 1.8.3 1.17.3 1.8.30 1.17.4 1.8.4 1.17.5 1.8.5 1.17.6 1.8.6 1.17.7 1.8.7 1.17.8 1.8.8 1.17.9 1.8.9 1.17.9-beta.1 1.9 1.18.0 2.0.0 1.19.0 2.0.1 1.19.1 2.0.10 1.2 2.0.11 1.2.1 2.0.12 1.2.2 2.0.13 1.2.3 2.0.14 1.2.4 2.0.15 1.2.5 2.0.16 1.2.6 2.0.17 1.2.7 2.0.18 1.20.0 2.0.19 1.20.0-rc.1 2.0.2 1.20.0-rc.2 2.0.20 1.20.1 2.0.3 1.20.2 2.0.4 1.20.3 2.0.5 1.21.0 2.0.6 1.21.1 2.0.7 1.22.0 2.0.8 1.22.1 2.0.9 1.22.2 1.23.0 1.23.1 1.23.2 1.24.0 1.24.1 1.24.2 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.29.0 1.29.1 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.30.0 1.30.1 1.30.2 1.30.2-rc.1 1.30.3 1.30.4 1.30.4-rc.1 1.30.5 1.31.0 1.31.1 1.32.0 1.32.0-rc.1 1.33.0 1.33.1 1.33.2 1.34.0 1.35.0 1.35.1 1.36.0 1.36.1 1.36.2 1.36.3 1.37.0 1.37.1 1.37.2
advanced-ads / src / admin / partials / file-uploader.js
advanced-ads / src / admin / partials Last commit date
file-uploader.js 3 months ago header-tabs.css 3 months ago search-reulsts.js 3 months ago tabs.css 3 months ago tabs.js 3 months ago tailwind.css 3 months ago
file-uploader.js
340 lines
1 /* eslint-disable no-alert */
2 class FileUploader {
3 /**
4 * @param {HTMLElement} wrap Wrap element
5 * @param {HTMLInputElement} inputEl File input element (carries data-* config)
6 */
7 constructor( wrap, inputEl ) {
8 this.wrap = wrap;
9 this.input = inputEl;
10 this.ui = {
11 dropZone: wrap.querySelector( '.dropzone' ),
12 };
13
14 // ── Read limits from data-attributes on the input ────────────
15 this.maxSizeMB = parseFloat( inputEl.dataset.maxSizeMb ) || 5;
16 this.maxFiles = parseInt( inputEl.dataset.maxFiles ) || 10;
17 this.allowedExt = (
18 inputEl.dataset.allowedExt || 'jpg,jpeg,png,gif,svg,pdf,docx,txt'
19 )
20 .split( ',' )
21 .map( ( e ) => e.trim().toLowerCase() );
22 // ─────────────────────────────────────────────────────────────
23
24 this.maxSizeBytes = this.maxSizeMB * 1024 * 1024;
25
26 // Internal state
27 this.selectedFiles = [];
28
29 // Setup
30 this.input.accept = this.allowedExt.map( ( e ) => '.' + e ).join( ',' );
31 this._createRegions();
32 this._renderHints();
33 this._bindEvents();
34 }
35
36 reset() {
37 this.selectedFiles = [];
38 this._renderSelected();
39 }
40
41 // ── Event binding ─────────────────────────────────────────────
42
43 _bindEvents() {
44 // Native file picker
45 this.input.addEventListener( 'change', ( e ) => {
46 this._processFiles( e.target.files );
47 this.input.value = ''; // reset so same file can be re-selected
48 } );
49
50 // Drag & drop
51 this.ui.dropZone.addEventListener( 'dragover', ( e ) => {
52 e.preventDefault();
53 if ( ! this.ui.dropZone.classList.contains( 'limit-reached' ) ) {
54 this.ui.dropZone.classList.add( 'dragover' );
55 }
56 } );
57 this.ui.dropZone.addEventListener( 'dragleave', () =>
58 this.ui.dropZone.classList.remove( 'dragover' )
59 );
60 this.ui.dropZone.addEventListener( 'drop', ( e ) => {
61 e.preventDefault();
62 this.ui.dropZone.classList.remove( 'dragover' );
63 this._processFiles( e.dataTransfer.files );
64 } );
65 }
66
67 // ── UI Rendering ──────────────────────────────────────────────
68
69 _createRegions() {
70 // Count badge
71 const countBadge = document.createElement( 'div' );
72 countBadge.className = 'file-count-badge';
73 countBadge.style.display = 'none';
74 this.wrap.appendChild( countBadge );
75 this.ui.countBadge = countBadge;
76
77 // Error box
78 const errorBox = document.createElement( 'div' );
79 errorBox.className = 'error-box';
80 errorBox.style.display = 'none';
81 this.wrap.appendChild( errorBox );
82 this.ui.errorBox = errorBox;
83
84 // Error box title
85 const errorBoxTitle = document.createElement( 'p' );
86 errorBoxTitle.className = 'error-box-title';
87 errorBoxTitle.innerHTML = 'Some files were rejected:';
88 errorBox.appendChild( errorBoxTitle );
89
90 // Error list
91 const errorList = document.createElement( 'ul' );
92 errorList.className = 'error-list';
93 errorBox.appendChild( errorList );
94 this.ui.errorList = errorList;
95
96 // Selected container
97 const selectedContainer = document.createElement( 'div' );
98 selectedContainer.className = 'selected-files-container';
99 this.wrap.appendChild( selectedContainer );
100 this.ui.selectedContainer = selectedContainer;
101 }
102
103 _renderHints() {
104 // append div with class upload-hints
105 const hints = document.createElement( 'div' );
106 hints.className = 'upload-hints';
107 hints.innerHTML =
108 `<div><strong>Allowed:</strong> ${ this.allowedExt
109 .map( ( e ) => '.' + e )
110 .join( ', ' ) }</div>` +
111 `<span><strong>Max size:</strong> ${ this.maxSizeMB } MB per file</span>` +
112 `<span><strong>Max files:</strong> ${ this.maxFiles }</span>`;
113
114 this.ui.dropZone.appendChild( hints );
115 }
116
117 _updateCountBadge() {
118 const total = this.selectedFiles.length;
119 const badge = this.ui.countBadge;
120
121 if ( total === 0 ) {
122 badge.style.display = 'none';
123 return;
124 }
125
126 badge.style.display = 'inline-block';
127 badge.textContent = `${ total } / ${ this.maxFiles } file${
128 total !== 1 ? 's' : ''
129 } selected`;
130 badge.classList.toggle( 'at-limit', total >= this.maxFiles );
131
132 // Visually lock drop zone at limit
133 this.ui.dropZone.classList.toggle(
134 'limit-reached',
135 total >= this.maxFiles
136 );
137 }
138
139 _showErrors( rejections ) {
140 const { errorBox, errorList } = this.ui;
141 if ( rejections.length === 0 ) {
142 errorBox.style.display = 'none';
143 return;
144 }
145 errorList.innerHTML = rejections
146 .map(
147 ( r ) =>
148 `<li><strong>${ r.name }</strong>: ${ r.reasons.join(
149 '; '
150 ) }</li>`
151 )
152 .join( '' );
153 errorBox.style.display = 'block';
154 }
155
156 _createFileItem( data ) {
157 const { id, filename, fileimage, datetime, filesize } = data;
158
159 const box = document.createElement( 'div' );
160 box.className = 'file-item';
161 box.dataset.id = id;
162
163 // Thumbnail or generic icon
164 const imgDiv = document.createElement( 'div' );
165 imgDiv.className = 'file-image';
166 if ( this._isImage( filename ) ) {
167 const img = document.createElement( 'img' );
168 img.src = fileimage;
169 img.alt = '';
170 imgDiv.appendChild( img );
171 } else {
172 imgDiv.innerHTML = '<i class="far fa-file-alt"></i>';
173 }
174
175 // File metadata
176 const detail = document.createElement( 'div' );
177 detail.className = 'file-detail';
178 detail.innerHTML = `
179 <h6>${ filename }</h6>
180 <p>
181 <span>Size: ${ filesize }</span>
182 <span style="margin-left:10px">Modified: ${ datetime }</span>
183 </p>
184 `;
185
186 // Action buttons
187 const actions = document.createElement( 'div' );
188 actions.className = 'file-actions';
189
190 const deleteBtn = document.createElement( 'button' );
191 deleteBtn.className = 'file-action-btn';
192 deleteBtn.textContent = 'Delete';
193 deleteBtn.addEventListener( 'click', () => this._deleteSelected( id ) );
194 actions.appendChild( deleteBtn );
195 detail.appendChild( actions );
196 box.appendChild( imgDiv );
197 box.appendChild( detail );
198 return box;
199 }
200
201 _renderSelected() {
202 this.ui.selectedContainer.innerHTML = '';
203 this.selectedFiles.forEach( ( f ) =>
204 this.ui.selectedContainer.appendChild( this._createFileItem( f ) )
205 );
206 this._updateCountBadge();
207 }
208
209 // ── State mutations ───────────────────────────────────────────
210
211 _deleteSelected( id ) {
212 if (
213 globalThis.confirm( 'Are you sure you want to delete this file?' )
214 ) {
215 this.selectedFiles = this.selectedFiles.filter(
216 ( f ) => f.id !== id
217 );
218 this._renderSelected();
219 }
220 }
221
222 // ── Core: process a FileList ──────────────────────────────────
223
224 _processFiles( fileList ) {
225 const rejections = [];
226 const slots = this.maxFiles - this.selectedFiles.length;
227
228 // Already at limit before even looking at new files
229 if ( slots <= 0 ) {
230 this._showErrors( [
231 {
232 name: 'Selection blocked',
233 reasons: [
234 `you have already reached the limit of ${ this.maxFiles } files`,
235 ],
236 },
237 ] );
238 return;
239 }
240
241 let accepted = 0;
242
243 for ( const file of fileList ) {
244 // Enforce count limit across this batch
245 if ( accepted >= slots ) {
246 rejections.push( {
247 name: file.name,
248 reasons: [ `max file limit of ${ this.maxFiles } reached` ],
249 } );
250 continue;
251 }
252
253 // Validate extension & size
254 const errors = this._validateFile( file );
255 if ( errors.length > 0 ) {
256 rejections.push( { name: file.name, reasons: errors } );
257 continue;
258 }
259
260 // All good — read as data URL
261 const reader = new globalThis.FileReader();
262 reader.onloadend = () => {
263 this.selectedFiles.push( {
264 id: this._generateId(),
265 filename: file.name,
266 filetype: file.type,
267 fileimage: reader.result,
268 datetime: file.lastModified
269 ? new Date( file.lastModified ).toLocaleString(
270 'en-IN'
271 )
272 : 'Unknown',
273 filesize: this._formatSize( file.size ),
274 } );
275 this._renderSelected();
276 };
277 reader.readAsDataURL( file );
278 accepted++;
279 }
280
281 this._showErrors( rejections );
282 }
283
284 // ── Utilities ─────────────────────────────────────────────────
285
286 _generateId() {
287 return (
288 Math.random().toString( 36 ).slice( 2, 9 ) +
289 Date.now().toString( 36 )
290 );
291 }
292
293 _formatSize( bytes, decimals = 2 ) {
294 if ( bytes === 0 ) {
295 return '0 Bytes';
296 }
297 const k = 1024;
298 const sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB' ];
299 const i = Math.floor( Math.log( bytes ) / Math.log( k ) );
300 return (
301 parseFloat(
302 ( bytes / Math.pow( k, i ) ).toFixed( Math.max( 0, decimals ) )
303 ) +
304 ' ' +
305 sizes[ i ]
306 );
307 }
308
309 _isImage( filename ) {
310 return /\.(jpg|jpeg|png|gif|svg)$/i.test( filename );
311 }
312
313 _getExt( filename ) {
314 return filename.split( '.' ).pop().toLowerCase();
315 }
316
317 // ── Validation ────────────────────────────────────────────────
318
319 _validateFile( file ) {
320 const errors = [];
321 const ext = this._getExt( file.name );
322
323 if ( ! this.allowedExt.includes( ext ) ) {
324 errors.push( `".${ ext }" is not an allowed extension` );
325 }
326
327 if ( file.size > this.maxSizeBytes ) {
328 errors.push(
329 `exceeds max size of ${
330 this.maxSizeMB
331 } MB (is ${ this._formatSize( file.size ) })`
332 );
333 }
334
335 return errors;
336 }
337 }
338
339 export { FileUploader };
340