mbkbucket 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -123,6 +123,16 @@
123
123
  font-size: 0.9rem;
124
124
  }
125
125
 
126
+ .stats-bucket-badge {
127
+ background: rgba(255, 255, 255, 0.2);
128
+ padding: 0.35rem 0.75rem;
129
+ border-radius: 0.5rem;
130
+ font-size: 0.85rem;
131
+ font-weight: 600;
132
+ color: white;
133
+ letter-spacing: 0.02em;
134
+ }
135
+
126
136
  /* Form styling - Modern approach */
127
137
  .form-control {
128
138
  border: 1px solid var(--gray-light-color-ml);
@@ -511,13 +521,14 @@
511
521
  }
512
522
 
513
523
  .folder-row .file-icon {
514
- background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 50%, rgba(245, 158, 11, 0.15) 100%);
515
- color: #f59e0b;
516
- box-shadow: 0 2px 8px rgba(245, 158, 11, 0.15);
524
+ background: linear-gradient(160deg, #fff5db 0%, #ffe7b2 100%);
525
+ color: #b45309;
526
+ border: 1px solid rgba(180, 83, 9, 0.2);
527
+ box-shadow: 0 3px 10px rgba(180, 83, 9, 0.14);
517
528
  }
518
529
 
519
530
  .folder-row:hover .file-icon {
520
- box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
531
+ box-shadow: 0 8px 18px rgba(180, 83, 9, 0.24);
521
532
  }
522
533
 
523
534
  .breadcrumb-nav {
@@ -561,22 +572,28 @@
561
572
  }
562
573
 
563
574
  .file-icon {
564
- width: 40px;
565
- height: 40px;
566
- background: linear-gradient(135deg, var(--primary-light-color-ml) 0%, rgba(13, 148, 136, 0.08) 100%);
567
- border-radius: 10px;
575
+ width: 42px;
576
+ height: 42px;
577
+ background: linear-gradient(160deg, #ecfdf8 0%, #d9f7ef 100%);
578
+ border: 1px solid rgba(13, 148, 136, 0.18);
579
+ border-radius: 12px;
568
580
  display: flex;
569
581
  align-items: center;
570
582
  justify-content: center;
571
583
  margin-right: 0.9rem;
572
- color: var(--primary-color-ml);
573
- font-size: 1.25rem;
584
+ color: #0f766e;
585
+ font-size: 1.15rem;
574
586
  flex-shrink: 0;
575
- transition: transform 0.25s ease, box-shadow 0.25s ease;
587
+ box-shadow: 0 3px 10px rgba(13, 148, 136, 0.12);
588
+ transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
589
+ }
590
+
591
+ .file-icon i {
592
+ transition: transform 0.22s ease;
576
593
  }
577
594
 
578
595
  .file-row:hover .file-icon {
579
- box-shadow: 0 2px 8px rgba(13, 148, 136, 0.2);
596
+ box-shadow: 0 8px 18px rgba(13, 148, 136, 0.2);
580
597
  }
581
598
 
582
599
  .file-info {
@@ -653,53 +670,58 @@
653
670
 
654
671
  /* File type specific styling - Enhanced gradients */
655
672
  .file-icon.image-file {
656
- background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 50%, rgba(245, 158, 11, 0.15) 100%);
657
- color: #f59e0b;
658
- box-shadow: 0 2px 8px rgba(245, 158, 11, 0.15);
673
+ background: linear-gradient(160deg, #fff6e3 0%, #ffe7bf 100%);
674
+ color: #b45309;
675
+ border-color: rgba(180, 83, 9, 0.22);
676
+ box-shadow: 0 3px 10px rgba(180, 83, 9, 0.16);
659
677
  }
660
678
 
661
679
  .file-icon.video-file {
662
- background: linear-gradient(135deg, #ef4444 0%, #f87171 50%, rgba(239, 68, 68, 0.15) 100%);
663
- color: #ef4444;
664
- box-shadow: 0 2px 8px rgba(239, 68, 68, 0.15);
680
+ background: linear-gradient(160deg, #ffe9e7 0%, #ffd1cc 100%);
681
+ color: #b91c1c;
682
+ border-color: rgba(185, 28, 28, 0.22);
683
+ box-shadow: 0 3px 10px rgba(185, 28, 28, 0.16);
665
684
  }
666
685
 
667
686
  .file-icon.audio-file {
668
- background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 50%, rgba(139, 92, 246, 0.15) 100%);
669
- color: #8b5cf6;
670
- box-shadow: 0 2px 8px rgba(139, 92, 246, 0.15);
687
+ background: linear-gradient(160deg, #e6efff 0%, #d6e5ff 100%);
688
+ color: #1d4ed8;
689
+ border-color: rgba(29, 78, 216, 0.22);
690
+ box-shadow: 0 3px 10px rgba(29, 78, 216, 0.16);
671
691
  }
672
692
 
673
693
  .file-icon.document-file {
674
- background: linear-gradient(135deg, #06b6d4 0%, #22d3ee 50%, rgba(6, 182, 212, 0.15) 100%);
675
- color: #06b6d4;
676
- box-shadow: 0 2px 8px rgba(6, 182, 212, 0.15);
694
+ background: linear-gradient(160deg, #e6faf9 0%, #c8f0ed 100%);
695
+ color: #0f766e;
696
+ border-color: rgba(15, 118, 110, 0.22);
697
+ box-shadow: 0 3px 10px rgba(15, 118, 110, 0.16);
677
698
  }
678
699
 
679
700
  .file-icon.code-file {
680
- background: linear-gradient(135deg, #10b981 0%, #34d399 50%, rgba(16, 185, 129, 0.15) 100%);
681
- color: #10b981;
682
- box-shadow: 0 2px 8px rgba(16, 185, 129, 0.15);
701
+ background: linear-gradient(160deg, #e9fbe9 0%, #d5f4d5 100%);
702
+ color: #166534;
703
+ border-color: rgba(22, 101, 52, 0.22);
704
+ box-shadow: 0 3px 10px rgba(22, 101, 52, 0.16);
683
705
  }
684
706
 
685
707
  .file-row:hover .file-icon.image-file {
686
- box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
708
+ box-shadow: 0 8px 18px rgba(180, 83, 9, 0.24);
687
709
  }
688
710
 
689
711
  .file-row:hover .file-icon.video-file {
690
- box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
712
+ box-shadow: 0 8px 18px rgba(185, 28, 28, 0.24);
691
713
  }
692
714
 
693
715
  .file-row:hover .file-icon.audio-file {
694
- box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
716
+ box-shadow: 0 8px 18px rgba(29, 78, 216, 0.24);
695
717
  }
696
718
 
697
719
  .file-row:hover .file-icon.document-file {
698
- box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3);
720
+ box-shadow: 0 8px 18px rgba(15, 118, 110, 0.24);
699
721
  }
700
722
 
701
723
  .file-row:hover .file-icon.code-file {
702
- box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
724
+ box-shadow: 0 8px 18px rgba(22, 101, 52, 0.24);
703
725
  }
704
726
 
705
727
  /* Empty state - Modern design */
@@ -741,6 +763,96 @@
741
763
  color: var(--gray-color-ml);
742
764
  }
743
765
 
766
+ .empty-state-illustration {
767
+ margin-bottom: 1.5rem;
768
+ position: relative;
769
+ display: inline-block;
770
+ }
771
+
772
+ .empty-state-search-icon {
773
+ font-size: 2rem;
774
+ color: var(--primary-color-ml);
775
+ position: absolute;
776
+ bottom: -5px;
777
+ right: -10px;
778
+ background: white;
779
+ border-radius: 50%;
780
+ padding: 5px;
781
+ }
782
+
783
+ .empty-state-description {
784
+ max-width: 400px;
785
+ margin: 0 auto 1.5rem;
786
+ color: var(--text-muted-color-ml);
787
+ }
788
+
789
+ .empty-state-actions {
790
+ display: flex;
791
+ gap: 0.75rem;
792
+ justify-content: center;
793
+ flex-wrap: wrap;
794
+ }
795
+
796
+ .file-path-display {
797
+ font-size: 0.75rem;
798
+ color: var(--gray-color-ml);
799
+ margin-bottom: 2px;
800
+ }
801
+
802
+ .sort-icon {
803
+ margin-left: 0.5rem;
804
+ }
805
+
806
+ .sort-icon-inactive {
807
+ color: var(--gray-color-ml);
808
+ opacity: 0.3;
809
+ font-size: 0.8rem;
810
+ }
811
+
812
+ .sort-icon-active {
813
+ color: var(--primary-color-ml);
814
+ font-size: 0.9rem;
815
+ }
816
+
817
+ .bulk-actions-bar {
818
+ display: none;
819
+ padding: 0.5rem;
820
+ border-bottom: 1px solid var(--gray-light-color-ml);
821
+ background: var(--light-color-ml);
822
+ }
823
+
824
+ .toast-close-btn {
825
+ background: none;
826
+ border: none;
827
+ color: white;
828
+ cursor: pointer;
829
+ opacity: 0.8;
830
+ font-size: 1.2rem;
831
+ }
832
+
833
+ .upload-item-meta-row {
834
+ display: flex;
835
+ align-items: center;
836
+ gap: 0.75rem;
837
+ min-width: 0;
838
+ width: 100%;
839
+ margin-bottom: 5px;
840
+ }
841
+
842
+ .upload-item-size-label {
843
+ white-space: nowrap;
844
+ color: var(--text-muted-color-ml);
845
+ font-size: 0.875rem;
846
+ margin-left: auto;
847
+ }
848
+
849
+ .upload-item-status-row {
850
+ display: flex;
851
+ justify-content: space-between;
852
+ align-items: center;
853
+ gap: 0.5rem;
854
+ }
855
+
744
856
  /* Modal styling - Modern design */
745
857
  .modal {
746
858
  position: fixed;
@@ -8,9 +8,35 @@ let currentViewMode = 'folder'; // 'folder' or 'flat'
8
8
  let pageFiles = [];
9
9
  let pageFolders = [];
10
10
  let currentNextToken = null;
11
+ let currentApiPrefix = '';
11
12
  const bucketAppName = document.getElementById('bucketAppData')?.dataset.appName || '';
12
13
  // Bucket is configured centrally on the server; client should not select a bucket.
13
14
 
15
+ function stripApiPrefixFromPath(path, apiPrefix = '') {
16
+ const normalizedPath = String(path || '').replace(/^\/+/, '');
17
+ const normalizedApiPrefix = String(apiPrefix || '').replace(/^\/+/, '').replace(/\/+$/, '');
18
+
19
+ if (!normalizedApiPrefix) return normalizedPath;
20
+ if (normalizedPath === normalizedApiPrefix || normalizedPath === `${normalizedApiPrefix}/`) return '';
21
+ if (normalizedPath.startsWith(`${normalizedApiPrefix}/`)) {
22
+ return normalizedPath.slice(normalizedApiPrefix.length + 1);
23
+ }
24
+
25
+ return normalizedPath;
26
+ }
27
+
28
+ function toApiFolderPath(prefix = '', apiPrefix = '') {
29
+ const uiPrefix = String(prefix || '').replace(/^\/+/, '');
30
+ const normalizedApiPrefix = String(apiPrefix || '').replace(/^\/+/, '').replace(/\/+$/, '');
31
+
32
+ if (!uiPrefix) return '';
33
+ if (!normalizedApiPrefix) return uiPrefix;
34
+ if (uiPrefix === normalizedApiPrefix || uiPrefix.startsWith(`${normalizedApiPrefix}/`)) {
35
+ return uiPrefix;
36
+ }
37
+ return `${normalizedApiPrefix}/${uiPrefix}`;
38
+ }
39
+
14
40
  // Show alert message (Toast)
15
41
  function showAlert(message, type = 'success') {
16
42
  const container = document.getElementById('toast-container');
@@ -51,7 +77,7 @@ function showAlert(message, type = 'success') {
51
77
  toast.innerHTML = `
52
78
  <i class="fas ${isSuccess ? 'fa-check-circle' : 'fa-exclamation-circle'}" style="font-size: 1.25rem;"></i>
53
79
  <div style="flex: 1;">${message}</div>
54
- <button onclick="this.parentElement.remove()" style="background:none; border:none; color:white; cursor:pointer; opacity:0.8; font-size:1.2rem;">&times;</button>
80
+ <button onclick="this.parentElement.remove()" class="toast-close-btn">&times;</button>
55
81
  `;
56
82
 
57
83
  container.appendChild(toast);
@@ -82,8 +108,9 @@ async function loadFiles(page = 1, prefix = '', search = '', sort = currentSort,
82
108
  try {
83
109
  const parts = [];
84
110
  if (prefix) {
111
+ const folderPathForUrl = toApiFolderPath(prefix, currentApiPrefix);
85
112
  // preserve slashes in folder param for readability: encode then restore slashes
86
- const folderEncoded = encodeURIComponent(prefix).replace(/%2F/g, '/');
113
+ const folderEncoded = encodeURIComponent(folderPathForUrl).replace(/%2F/g, '/');
87
114
  parts.push(`folder=${folderEncoded}`);
88
115
  }
89
116
  if (page && page > 1) parts.push(`page=${page}`);
@@ -139,6 +166,7 @@ async function loadFiles(page = 1, prefix = '', search = '', sort = currentSort,
139
166
 
140
167
  // Store next token
141
168
  currentNextToken = data.nextContinuationToken || null;
169
+ currentApiPrefix = data.prefix || '';
142
170
 
143
171
  // Update stats
144
172
  if (data.totalFiles === -1) {
@@ -151,7 +179,7 @@ async function loadFiles(page = 1, prefix = '', search = '', sort = currentSort,
151
179
  filterInfo.textContent = `Search results for: "${search}"`;
152
180
  fileListTitle.innerHTML = `Search Results <span class="text-muted">for: </span><span class="badge bg-info">${search}</span>`;
153
181
  } else {
154
- const label = prefix || bucketAppName || 'Root';
182
+ const label = prefix || bucketAppName || 'app';
155
183
  filterInfo.textContent = `Filtered by folder: ${label}`;
156
184
  fileListTitle.innerHTML = `Files <span class="text-muted">in: </span><span class="badge bg-info">${label}</span>`;
157
185
  }
@@ -176,12 +204,16 @@ async function loadFiles(page = 1, prefix = '', search = '', sort = currentSort,
176
204
  pageFiles = data.files || [];
177
205
 
178
206
  if (data.mode === 'optimized') {
179
- pageFolders = data.folders || [];
207
+ pageFolders = (data.folders || []).map(folder => stripApiPrefixFromPath(folder, currentApiPrefix)).filter(Boolean);
180
208
  } else {
181
- pageFolders = search ? [] : extractFoldersAtCurrentLevel(data.files, prefix);
209
+ pageFolders = search
210
+ ? []
211
+ : extractFoldersAtCurrentLevel(data.files, currentApiPrefix || prefix)
212
+ .map(folder => stripApiPrefixFromPath(folder, currentApiPrefix))
213
+ .filter(Boolean);
182
214
  }
183
215
 
184
- renderFilesTable(pageFiles, pageFolders, prefix);
216
+ renderFilesTable(pageFiles, pageFolders, prefix, currentApiPrefix);
185
217
  renderPagination(data);
186
218
  paginationContainer.style.display = 'block';
187
219
  }
@@ -230,7 +262,7 @@ function toggleSort(field) {
230
262
  return 0;
231
263
  });
232
264
 
233
- renderFilesTable(pageFiles, pageFolders, currentPrefix);
265
+ renderFilesTable(pageFiles, pageFolders, currentPrefix, currentApiPrefix);
234
266
  }
235
267
  }
236
268
 
@@ -299,7 +331,7 @@ function changeViewMode(mode) {
299
331
  }
300
332
 
301
333
  // Re-render with existing data
302
- renderFilesTable(pageFiles, pageFolders, currentPrefix);
334
+ renderFilesTable(pageFiles, pageFolders, currentPrefix, currentApiPrefix);
303
335
  }
304
336
 
305
337
  // Extract folders that match a search query from file paths
@@ -331,7 +363,7 @@ function extractMatchingFolders(files, search) {
331
363
  }
332
364
 
333
365
  // Render files table
334
- function renderFilesTable(files, folders = [], prefix = '') {
366
+ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
335
367
  // Determine which files/folders to show based on view mode
336
368
  let displayFiles = [];
337
369
  let displayFolders = [];
@@ -369,7 +401,8 @@ function renderFilesTable(files, folders = [], prefix = '') {
369
401
  // Skip folder marker objects (keys ending with /)
370
402
  if (file.Key.endsWith('/')) return false;
371
403
 
372
- const relativePath = prefix ? file.Key.substring(prefix.length + (prefix.endsWith('/') ? 0 : 1)) : file.Key;
404
+ const basePrefix = apiPrefix || prefix;
405
+ const relativePath = basePrefix ? file.Key.substring(basePrefix.length + (basePrefix.endsWith('/') ? 0 : 1)) : file.Key;
373
406
  // File is at current level if it has no '/' in relative path
374
407
  return !relativePath.includes('/');
375
408
  });
@@ -386,7 +419,7 @@ function renderFilesTable(files, folders = [], prefix = '') {
386
419
  <div class="breadcrumb-nav">
387
420
  <div class="breadcrumb-item">
388
421
  <i class="fas fa-home"></i>
389
- <span class="breadcrumb-link" onclick="loadFiles(1, '')" title="Go to root">Root</span>
422
+ <span class="breadcrumb-link" onclick="loadFiles(1, '')" title="Go to ${bucketAppName || 'app'}">${bucketAppName || 'app'}</span>
390
423
  </div>
391
424
  `;
392
425
 
@@ -482,7 +515,7 @@ function renderFilesTable(files, folders = [], prefix = '') {
482
515
 
483
516
  // If in flat mode, we might want to show the folder path more prominently
484
517
  const pathDisplay = (currentViewMode === 'flat' && folderPath && folderPath !== prefix + (prefix ? '/' : ''))
485
- ? `<div style="font-size: 0.75rem; color: var(--gray-color-ml); margin-bottom:2px;">
518
+ ? `<div class="file-path-display">
486
519
  <i class="fas fa-folder" style="margin-right:4px; opacity:0.5;"></i> ${folderPath}
487
520
  </div>`
488
521
  : '';
@@ -535,24 +568,25 @@ function renderFilesTable(files, folders = [], prefix = '') {
535
568
 
536
569
  if (allRows === '') {
537
570
  document.getElementById('fileListContainer').innerHTML = `
571
+ ${breadcrumb}
538
572
  <div class="empty-state">
539
- <div style="margin-bottom: 1.5rem; position: relative; display: inline-block;">
573
+ <div class="empty-state-illustration">
540
574
  <i class="fas fa-folder-open" style="font-size: 4rem; color: #cbd5e0;"></i>
541
- <i class="fas fa-search" style="font-size: 2rem; color: var(--primary-color-ml); position: absolute; bottom: -5px; right: -10px; background: white; border-radius: 50%; padding: 5px;"></i>
575
+ <i class="fas fa-search empty-state-search-icon"></i>
542
576
  </div>
543
577
  <h4>No files found</h4>
544
- <p style="max-width: 400px; margin: 0 auto 1.5rem; color: var(--text-muted-color-ml);">
578
+ <p class="empty-state-description">
545
579
  ${prefix ? `This folder <strong>${prefix}</strong> is currently empty.` : 'Your bucket is empty. Start by uploading files or creating a new folder.'}
546
580
  </p>
547
- <div style="display:flex; gap:0.75rem; justify-content:center; flex-wrap: wrap;">
581
+ <div class="empty-state-actions">
548
582
  <button class="btn btn-primary" onclick="window.scrollTo({top: 0, behavior: 'smooth'}); document.getElementById('fileInput').click()">
549
583
  <i class="fas fa-cloud-upload-alt"></i> Upload Files
550
584
  </button>
551
585
  <button class="btn btn-outline-secondary" onclick="createFolder()">
552
586
  <i class="fas fa-folder-plus"></i> New Folder
553
587
  </button>
554
- ${prefix ? `<button class="btn btn-light" onclick="loadFiles(1, '')">
555
- <i class="fas fa-home"></i> Go to Root
588
+ ${prefix ? `<button class="btn btn-light" onclick="loadFiles(1, '')">
589
+ <i class="fas fa-home"></i> Go to ${bucketAppName || 'app'}
556
590
  </button>` : ''}
557
591
  </div>
558
592
  </div>
@@ -562,16 +596,16 @@ function renderFilesTable(files, folders = [], prefix = '') {
562
596
 
563
597
  // Sort icon helper
564
598
  const getSortIcon = (field) => {
565
- if (currentSort !== field) return '<i class="fas fa-sort" style="color:var(--gray-color-ml); opacity:0.3; font-size:0.8rem; margin-left:0.5rem;"></i>';
599
+ if (currentSort !== field) return '<i class="fas fa-sort sort-icon sort-icon-inactive"></i>';
566
600
  return currentOrder === 'asc'
567
- ? '<i class="fas fa-sort-up" style="color:var(--primary-color-ml); font-size:0.9rem; margin-left:0.5rem;"></i>'
568
- : '<i class="fas fa-sort-down" style="color:var(--primary-color-ml); font-size:0.9rem; margin-left:0.5rem;"></i>';
601
+ ? '<i class="fas fa-sort-up sort-icon sort-icon-active"></i>'
602
+ : '<i class="fas fa-sort-down sort-icon sort-icon-active"></i>';
569
603
  };
570
604
 
571
605
  document.getElementById('fileListContainer').innerHTML = `
572
606
  ${breadcrumb}
573
607
  <div class="table-responsive">
574
- <div id="bulkActionsBar" style="display:none; padding: 0.5rem; border-bottom: 1px solid var(--gray-light-color-ml); background: var(--light-color-ml);">
608
+ <div id="bulkActionsBar" class="bulk-actions-bar">
575
609
  <span id="bulkSelectedCount">0 selected</span>
576
610
  <div style="float: right;">
577
611
  <button class="btn btn-danger btn-sm" id="bulkDeleteBtn" disabled>
@@ -869,17 +903,17 @@ function renderUploadQueue() {
869
903
  return `
870
904
  <div class="upload-item" data-id="${item.id}">
871
905
  <div class="meta">
872
- <div style="display:flex; align-items:center; gap:0.75rem; min-width:0; width:100%; margin-bottom:5px;">
906
+ <div class="upload-item-meta-row">
873
907
  <div class="file-icon"><i class="fas fa-file"></i></div>
874
908
  <div class="name">${item.file.name}</div>
875
- <div class="form-text file-size" style="white-space:nowrap; color:var(--text-muted-color-ml); font-size:0.875rem; margin-left:auto;">${formatFileSize(item.file.size)}</div>
909
+ <div class="form-text file-size upload-item-size-label">${formatFileSize(item.file.size)}</div>
876
910
  </div>
877
911
  </div>
878
912
  <div style="min-width:220px;">
879
913
  <div class="progress-container" style="height: 8px; margin-bottom: 6px;">
880
914
  <div class="progress-bar" style="width: ${Math.round(item.progress)}%; height: 8px;"></div>
881
915
  </div>
882
- <div style="display:flex; justify-content:space-between; align-items:center; gap:0.5rem;">
916
+ <div class="upload-item-status-row">
883
917
  <small class="text-muted status">${statusText}</small>
884
918
  <div class="controls">${actionBtn}</div>
885
919
  </div>
@@ -1142,7 +1176,7 @@ document.addEventListener('DOMContentLoaded', function () {
1142
1176
 
1143
1177
  // Load initial files (support both legacy 'prefix' and new 'folder' query param)
1144
1178
  const urlParams = new URLSearchParams(window.location.search);
1145
- const initialPrefix = urlParams.get('folder') || urlParams.get('prefix') || bucketAppName || '';
1179
+ const initialPrefix = urlParams.get('folder') || urlParams.get('prefix') || '';
1146
1180
  const initialPage = parseInt(urlParams.get('page')) || 1;
1147
1181
  loadFiles(initialPage, initialPrefix);
1148
1182
 
@@ -1185,6 +1219,37 @@ document.addEventListener('DOMContentLoaded', function () {
1185
1219
  return;
1186
1220
  }
1187
1221
 
1222
+ const cbtn = e.target.closest('.copy-btn');
1223
+ console.log(cbtn);
1224
+ if (cbtn) {
1225
+ e.stopPropagation();
1226
+ const keyEnc = cbtn.dataset.keyEnc;
1227
+ if (!keyEnc) return;
1228
+ const link = `${window.location.origin}/mbkbucket/view/${keyEnc}`;
1229
+ const fallbackCopy = () => {
1230
+ try {
1231
+ const ta = document.createElement('textarea');
1232
+ ta.value = link;
1233
+ ta.style.position = 'fixed';
1234
+ ta.style.opacity = '0';
1235
+ document.body.appendChild(ta);
1236
+ ta.select();
1237
+ document.execCommand('copy');
1238
+ document.body.removeChild(ta);
1239
+ showAlert('Link copied to clipboard!', 'success');
1240
+ } catch {
1241
+ showAlert('Failed to copy link', 'error');
1242
+ }
1243
+ };
1244
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1245
+ navigator.clipboard.writeText(link).then(() => {
1246
+ showAlert('Link copied to clipboard!', 'success');
1247
+ }).catch(fallbackCopy);
1248
+ } else {
1249
+ fallbackCopy();
1250
+ }
1251
+ return;
1252
+ }
1188
1253
 
1189
1254
  });
1190
1255
  });
@@ -1209,25 +1274,32 @@ function handleViewFile(key, filename, event) {
1209
1274
  const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico'];
1210
1275
  const videoTypes = ['mp4', 'webm', 'ogg'];
1211
1276
 
1212
- // Use inline preview modal for supported types
1277
+ // Determine preview mode from extension groups
1213
1278
  const previewableImage = imageTypes.includes(extension);
1214
1279
  const previewableVideo = videoTypes.includes(extension);
1215
1280
  const previewableAudio = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a'].includes(extension);
1216
1281
  const previewablePdf = extension === 'pdf';
1217
1282
  const previewableText = ['txt', 'md', 'json', 'xml', 'csv', 'log', 'js', 'ts', 'html', 'htm', 'css', 'py', 'java', 'cpp', 'c', 'h', 'cs', 'rb', 'go', 'rs', 'sql', 'sh', 'bat', 'ps1', 'yaml', 'yml', 'toml', 'ini', 'conf'].includes(extension);
1218
1283
 
1284
+ let previewMode = 'unsupported';
1285
+ if (previewableImage) previewMode = 'image';
1286
+ else if (previewableVideo) previewMode = 'video';
1287
+ else if (previewableAudio) previewMode = 'audio';
1288
+ else if (previewablePdf) previewMode = 'pdf';
1289
+ else if (previewableText) previewMode = 'text';
1290
+
1219
1291
  // Build an encoded key safely (if key already appears encoded, don't double-encode)
1220
1292
  const encodedKey = (key && key.includes('%')) ? key : encodeURIComponent(key || filename);
1221
1293
 
1222
1294
  // Show the inline preview
1223
- showPreviewModal(encodedKey, filename, extension);
1295
+ showPreviewModal(encodedKey, filename, extension, previewMode);
1224
1296
  return false;
1225
1297
  }
1226
1298
 
1227
1299
  // Function to get file type category
1228
1300
 
1229
1301
  // Preview modal helpers
1230
- function showPreviewModal(encodedKey, filename, extension) {
1302
+ function showPreviewModal(encodedKey, filename, extension, previewMode = 'auto') {
1231
1303
  const modal = document.getElementById('previewModal');
1232
1304
  if (!modal) {
1233
1305
  window.open(`/mbkbucket/view/${encodedKey}`, '_blank', 'noopener,noreferrer');
@@ -1259,9 +1331,17 @@ function showPreviewModal(encodedKey, filename, extension) {
1259
1331
  const videoTypes = ['mp4', 'webm', 'ogg'];
1260
1332
  const audioTypes = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a'];
1261
1333
  const textTypes = ['txt', 'md', 'json', 'xml', 'csv', 'log', 'js', 'ts', 'html', 'htm', 'css', 'py', 'java', 'cpp', 'c', 'h', 'cs', 'rb', 'go', 'rs', 'sql', 'sh', 'bat', 'ps1', 'yaml', 'yml', 'toml', 'ini', 'conf'];
1334
+ const effectiveMode = previewMode === 'auto'
1335
+ ? (imgTypes.includes(extension) ? 'image'
1336
+ : videoTypes.includes(extension) ? 'video'
1337
+ : audioTypes.includes(extension) ? 'audio'
1338
+ : extension === 'pdf' ? 'pdf'
1339
+ : textTypes.includes(extension) ? 'text'
1340
+ : 'unsupported')
1341
+ : previewMode;
1262
1342
 
1263
1343
  // Render content and adjust dialog size
1264
- if (imgTypes.includes(extension)) {
1344
+ if (effectiveMode === 'image') {
1265
1345
  dialog.classList.add('preview-compact');
1266
1346
  const img = document.createElement('img');
1267
1347
  img.className = 'preview-img';
@@ -1271,7 +1351,7 @@ function showPreviewModal(encodedKey, filename, extension) {
1271
1351
  img.onerror = () => { contentEl.textContent = 'Could not load preview.'; };
1272
1352
  contentEl.innerHTML = '';
1273
1353
  contentEl.appendChild(img);
1274
- } else if (videoTypes.includes(extension)) {
1354
+ } else if (effectiveMode === 'video') {
1275
1355
  dialog.classList.add('preview-large');
1276
1356
  const video = document.createElement('video');
1277
1357
  video.className = 'preview-video';
@@ -1279,7 +1359,7 @@ function showPreviewModal(encodedKey, filename, extension) {
1279
1359
  video.src = `/mbkbucket/view/${encodedKey}`;
1280
1360
  contentEl.innerHTML = '';
1281
1361
  contentEl.appendChild(video);
1282
- } else if (audioTypes.includes(extension)) {
1362
+ } else if (effectiveMode === 'audio') {
1283
1363
  dialog.classList.add('preview-compact');
1284
1364
  const audio = document.createElement('audio');
1285
1365
  audio.className = 'preview-audio';
@@ -1287,14 +1367,14 @@ function showPreviewModal(encodedKey, filename, extension) {
1287
1367
  audio.src = `/mbkbucket/view/${encodedKey}`;
1288
1368
  contentEl.innerHTML = '';
1289
1369
  contentEl.appendChild(audio);
1290
- } else if (extension === 'pdf') {
1370
+ } else if (effectiveMode === 'pdf') {
1291
1371
  dialog.classList.add('preview-large');
1292
1372
  const iframe = document.createElement('iframe');
1293
1373
  iframe.className = 'preview-iframe';
1294
1374
  iframe.src = `/mbkbucket/view/${encodedKey}`;
1295
1375
  contentEl.innerHTML = '';
1296
1376
  contentEl.appendChild(iframe);
1297
- } else if (textTypes.includes(extension)) {
1377
+ } else if (effectiveMode === 'text') {
1298
1378
  dialog.classList.add('preview-compact');
1299
1379
 
1300
1380
  fetch(`/mbkbucket/view/${encodedKey}`)
@@ -63,12 +63,12 @@
63
63
  /* Removed unused sidebar/menu styles to reduce CSS size. */
64
64
 
65
65
  .logo {
66
- font-size: 1.25rem;
66
+ font-size: 2rem;
67
67
  font-weight: 700;
68
68
  color: var(--primary-color-ml);
69
69
  display: flex;
70
70
  align-items: center;
71
- gap: .5rem
71
+ gap: .3rem
72
72
  }
73
73
 
74
74
  .logo img {
@@ -205,7 +205,7 @@
205
205
  <nav class="top-nav">
206
206
  <div class="logo">
207
207
  <img onclick="window.location='/'" src="/icon.svg" alt="Logo">
208
- <span onclick="window.location='/'" style="cursor:pointer;">MBKBucket</span>
208
+ <span onclick="window.location='/'" style="cursor:pointer;">BKBucket</span>
209
209
  </div>
210
210
 
211
211
  {{> profilemenu }}
@@ -0,0 +1,15 @@
1
+ {{#if message}}
2
+ <div class="alert alert-success alert-dismissible fade show" role="alert" id="successAlert">
3
+ <i class="fas fa-check-circle"></i>
4
+ <span id="successMessage">{{message}}</span>
5
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">&times;</button>
6
+ </div>
7
+ {{/if}}
8
+
9
+ {{#if error}}
10
+ <div class="alert alert-danger alert-dismissible fade show" role="alert" id="errorAlert">
11
+ <i class="fas fa-exclamation-triangle"></i>
12
+ <span id="errorMessage">{{error}}</span>
13
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">&times;</button>
14
+ </div>
15
+ {{/if}}