PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / trunk
Forumax – AI Powered Advanced Community Forum Plugin vtrunk
2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 2.0.0 2.1.0 All 29 releases
bbp-core / includes / features / bbpc_attachments / forms / uploader.php

uploader.php in Forumax – AI Powered Advanced Community Forum Plugin trunk, at includes/features/bbpc_attachments/forms/uploader.php

202 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <fieldset class="bbp-form upload-attachments-form">
2 <div class="frmx-attachments-ui">
3 <div class="frmx-attachments-list" id="frmx-attachments-list"></div>
4
5 <button type="button" class="frmx-add-file-btn" id="frmx-add-file-btn">
6 <span class="frmx-add-icon">
7 <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
8 </span>
9 <?php esc_html_e( 'Add more files', 'forumax' ); ?>
10 </button>
11
12 <div class="frmx-attachment-limit-note">
13 <?php
14 $size = $file_size < 1024 ? $file_size . ' KB' : floor( $file_size / 1024 ) . ' MB';
15 printf( esc_html__( 'Maximum file size allowed is %s.', 'forumax' ), esc_html( $size ) );
16 ?>
17 </div>
18
19 <div id="frmx-hidden-inputs" style="display: none;">
20 <input type="file" name="bbpc_attachment[]" class="frmx-file-input">
21 </div>
22 </div>
23
24 <!-- Keep the legacy container hidden for fallback or original JS hooks -->
25 <div class="bbpc_attachments" style="display:none;"></div>
26 </fieldset>
27
28 <script>
29 document.addEventListener('DOMContentLoaded', function() {
30 const addBtn = document.getElementById('frmx-add-file-btn');
31 const inputsWrap = document.getElementById('frmx-hidden-inputs');
32 const listContainer = document.getElementById('frmx-attachments-list');
33 if ( ! addBtn ) return;
34
35 /* Max-files from bbp config (default 5) */
36 let maxFiles = 5;
37 if ( typeof bbpcAttachmentsInit !== 'undefined' ) {
38 maxFiles = parseInt( bbpcAttachmentsInit.max_files, 10 );
39 }
40
41 /* ---- Helpers ---- */
42
43 /**
44 * Format a byte count into a human-readable string.
45 *
46 * @param {number} bytes
47 * @return {string}
48 */
49 function humanSize( bytes ) {
50 if ( bytes < 1024 ) return bytes + ' B';
51 if ( bytes < 1048576 ) return ( bytes / 1024 ).toFixed( 1 ) + ' KB';
52 return ( bytes / 1048576 ).toFixed( 1 ) + ' MB';
53 }
54
55 /**
56 * Return a colour for the icon based on a rough file-type bucket.
57 *
58 * @param {string} name Filename.
59 * @return {string} CSS colour value.
60 */
61 function iconColor( name ) {
62 const ext = ( name.split( '.' ).pop() || '' ).toLowerCase();
63 const map = {
64 pdf: '#e74c3c',
65 doc: '#2b6cb0', docx: '#2b6cb0',
66 xls: '#276749', xlsx: '#276749',
67 csv: '#276749',
68 ppt: '#c05621', pptx: '#c05621',
69 zip: '#744210', rar: '#744210', '7z': '#744210',
70 jpg: '#6b46c1', jpeg: '#6b46c1', png: '#6b46c1', gif: '#6b46c1', webp: '#6b46c1', svg: '#6b46c1',
71 mp4: '#2c7a7b', mov: '#2c7a7b', avi: '#2c7a7b',
72 mp3: '#553c9a', wav: '#553c9a',
73 };
74 return map[ ext ] || 'var(--bbpc_brand_color)';
75 }
76
77 /** Generic file SVG icon markup. */
78 const fileSvg = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>';
79
80 /** Close (×) SVG for remove button. */
81 const closeSvg = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
82
83 /* ---- Open file picker ---- */
84 addBtn.addEventListener( 'click', function( e ) {
85 e.preventDefault();
86
87 let empty = Array.from( inputsWrap.querySelectorAll( '.frmx-file-input' ) ).find( i => ! i.value );
88 if ( ! empty ) {
89 const total = inputsWrap.querySelectorAll( '.frmx-file-input' ).length;
90 if ( total >= maxFiles ) return;
91 empty = document.createElement( 'input' );
92 empty.type = 'file';
93 empty.name = 'bbpc_attachment[]';
94 empty.className = 'frmx-file-input';
95 inputsWrap.appendChild( empty );
96 }
97 empty.click();
98 } );
99
100 /* ---- File selected ---- */
101 inputsWrap.addEventListener( 'change', function( e ) {
102 if ( e.target && e.target.classList.contains( 'frmx-file-input' ) && e.target.value ) {
103 renderList();
104
105 const total = inputsWrap.querySelectorAll( '.frmx-file-input' ).length;
106 if ( total < maxFiles ) {
107 const next = document.createElement( 'input' );
108 next.type = 'file';
109 next.name = 'bbpc_attachment[]';
110 next.className = 'frmx-file-input';
111 inputsWrap.appendChild( next );
112 }
113 }
114 } );
115
116 /* ---- Remove file ---- */
117 listContainer.addEventListener( 'click', function( e ) {
118 const btn = e.target.closest( '.frmx-remove-file' );
119 if ( ! btn ) return;
120
121 e.preventDefault();
122 const idx = parseInt( btn.dataset.index, 10 );
123 const inp = inputsWrap.querySelectorAll( '.frmx-file-input' )[ idx ];
124 if ( inp ) {
125 inp.remove();
126 renderList();
127
128 const hasEmpty = Array.from( inputsWrap.querySelectorAll( '.frmx-file-input' ) ).some( i => ! i.value );
129 const total = inputsWrap.querySelectorAll( '.frmx-file-input' ).length;
130 if ( ! hasEmpty && total < maxFiles ) {
131 const blank = document.createElement( 'input' );
132 blank.type = 'file';
133 blank.name = 'bbpc_attachment[]';
134 blank.className = 'frmx-file-input';
135 inputsWrap.appendChild( blank );
136 }
137 }
138 } );
139
140 /* ---- Drag-over visual feedback on the add button ---- */
141 addBtn.addEventListener( 'dragover', function( e ) { e.preventDefault(); addBtn.classList.add( 'frmx-drag-over' ); } );
142 addBtn.addEventListener( 'dragleave', function() { addBtn.classList.remove( 'frmx-drag-over' ); } );
143 addBtn.addEventListener( 'drop', function( e ) {
144 e.preventDefault();
145 addBtn.classList.remove( 'frmx-drag-over' );
146
147 const files = e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files : [];
148 if ( ! files.length ) return;
149
150 Array.from( files ).forEach( function( file ) {
151 const total = inputsWrap.querySelectorAll( '.frmx-file-input' ).length;
152 if ( total >= maxFiles ) return;
153
154 /* Build a DataTransfer to assign the file to a new hidden input */
155 const dt = new DataTransfer();
156 dt.items.add( file );
157 const inp = document.createElement( 'input' );
158 inp.type = 'file';
159 inp.name = 'bbpc_attachment[]';
160 inp.className = 'frmx-file-input';
161 inp.files = dt.files;
162 inputsWrap.appendChild( inp );
163 } );
164
165 renderList();
166 } );
167
168 /* ---- Render the uploaded file list ---- */
169 function renderList() {
170 listContainer.innerHTML = '';
171 const inputs = inputsWrap.querySelectorAll( '.frmx-file-input' );
172 let fileCount = 0;
173
174 inputs.forEach( function( input, index ) {
175 if ( ! input.files || ! input.files.length ) return;
176
177 const file = input.files[ 0 ];
178 const color = iconColor( file.name );
179 fileCount++;
180
181 const item = document.createElement( 'div' );
182 item.className = 'frmx-attachment-item';
183
184 item.innerHTML = `
185 <div class="frmx-file-icon-wrapper" style="background:color-mix(in srgb,${color} 12%,transparent);">
186 <span style="color:${color};display:flex;align-items:center;">${fileSvg}</span>
187 </div>
188 <div class="frmx-file-info">
189 <span class="frmx-file-name">${file.name}</span>
190 <span class="frmx-file-size">${humanSize( file.size )}</span>
191 </div>
192 <button type="button" class="frmx-remove-file" data-index="${index}" title="Remove file">${closeSvg}</button>
193 `;
194
195 listContainer.appendChild( item );
196 } );
197
198 addBtn.style.display = ( fileCount >= maxFiles ) ? 'none' : 'flex';
199 }
200 } );
201 </script>
202