mbkbucket 2.0.0 → 2.1.1

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.
@@ -12,13 +12,16 @@ let currentApiPrefix = '';
12
12
  const bucketAppName = document.getElementById('bucketAppData')?.dataset.appName || '';
13
13
  const selectedBucket = new URLSearchParams(window.location.search).get('bucket') || '';
14
14
  let activeBucket = selectedBucket;
15
- const isRootApp = String(bucketAppName || '').toLowerCase() === 'portal';
16
- const rootLabel = isRootApp ? 'root' : (bucketAppName || 'app');
15
+ const app = String(bucketAppName || '').toLowerCase();
16
+ const isRootApp = app === 'portal' || app === 'mbkbucket';const rootLabel = isRootApp ? 'root' : (bucketAppName || 'app');
17
17
  let currentQuickFilter = 'all';
18
18
  let virtualRenderLimit = 0;
19
19
  const VIRTUAL_RENDER_THRESHOLD = 500;
20
20
  const VIRTUAL_RENDER_BATCH = 250;
21
21
  let lastRenderContext = null;
22
+ const MEDIA_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'mp4', 'webm', 'ogg', 'avi', 'mov', 'mp3', 'wav', 'flac', 'aac', 'm4a']);
23
+ const DOC_EXTENSIONS = new Set(['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv']);
24
+ const CODE_EXTENSIONS = new Set(['js', 'ts', 'html', 'htm', 'css', 'php', 'py', 'java', 'cpp', 'c', 'h', 'cs', 'rb', 'go', 'rs', 'sql', 'sh', 'bat', 'ps1', 'yaml', 'yml', 'toml', 'ini', 'conf', 'json', 'xml', 'log']);
22
25
 
23
26
  function withBucketUrl(url) {
24
27
  if (!activeBucket) return url;
@@ -72,6 +75,20 @@ function escapeAttr(value = '') {
72
75
  return escapeHtml(value);
73
76
  }
74
77
 
78
+ function getBaseName(path = '') {
79
+ const value = String(path || '');
80
+ const lastSlashIndex = value.lastIndexOf('/');
81
+ return lastSlashIndex === -1 ? value : value.substring(lastSlashIndex + 1);
82
+ }
83
+
84
+ function dataAttr(name, value = '') {
85
+ return `data-${name}="${escapeAttr(value)}"`;
86
+ }
87
+
88
+ function mutedDash() {
89
+ return '<span class="muted-cell-value">&mdash;</span>';
90
+ }
91
+
75
92
  function syncStateUrl(page = currentPage, prefix = currentPrefix, search = currentSearch) {
76
93
  try {
77
94
  const parts = [];
@@ -100,14 +117,10 @@ function getActiveBucketLabel() {
100
117
  }
101
118
 
102
119
  function matchesQuickFilter(fileName = '', filter = currentQuickFilter) {
103
- const ext = String(fileName).split('.').pop().toLowerCase();
104
- const media = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'mp4', 'webm', 'ogg', 'avi', 'mov', 'mp3', 'wav', 'flac', 'aac', 'm4a']);
105
- const docs = new Set(['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv']);
106
- const code = new Set(['js', 'ts', 'html', 'htm', 'css', 'php', 'py', 'java', 'cpp', 'c', 'h', 'cs', 'rb', 'go', 'rs', 'sql', 'sh', 'bat', 'ps1', 'yaml', 'yml', 'toml', 'ini', 'conf', 'json', 'xml', 'log']);
107
-
108
- if (filter === 'media') return media.has(ext);
109
- if (filter === 'docs') return docs.has(ext);
110
- if (filter === 'code') return code.has(ext);
120
+ const ext = getBaseName(fileName).split('.').pop().toLowerCase();
121
+ if (filter === 'media') return MEDIA_EXTENSIONS.has(ext);
122
+ if (filter === 'docs') return DOC_EXTENSIONS.has(ext);
123
+ if (filter === 'code') return CODE_EXTENSIONS.has(ext);
111
124
  return true;
112
125
  }
113
126
 
@@ -155,9 +168,9 @@ function showAlert(message, type = 'success') {
155
168
  `;
156
169
 
157
170
  toast.innerHTML = `
158
- <i class="fas ${isSuccess ? 'fa-check-circle' : 'fa-exclamation-circle'}" style="font-size: 1.25rem;"></i>
159
- <div style="flex: 1;">${message}</div>
160
- <button onclick="this.parentElement.remove()" class="toast-close-btn">&times;</button>
171
+ <i class="fas ${isSuccess ? 'fa-check-circle' : 'fa-exclamation-circle'} toast-icon"></i>
172
+ <div class="toast-message">${message}</div>
173
+ <button type="button" data-action="close-toast" class="toast-close-btn">&times;</button>
161
174
  `;
162
175
 
163
176
  container.appendChild(toast);
@@ -259,7 +272,7 @@ async function loadFiles(page = 1, prefix = '', search = '', sort = currentSort,
259
272
  <i class="fas fa-folder-open"></i>
260
273
  <h4>No files found</h4>
261
274
  <p>Bucket <strong>${escapeHtml(getActiveBucketLabel())}</strong> is empty. Upload some files to get started!</p>
262
- <button class="btn btn-lg" onclick="document.getElementById('fileInput').click()">
275
+ <button class="btn btn-lg" data-action="upload-files">
263
276
  <i class="fas fa-plus"></i> Upload Your First File
264
277
  </button>
265
278
  </div>
@@ -284,13 +297,16 @@ async function loadFiles(page = 1, prefix = '', search = '', sort = currentSort,
284
297
  paginationContainer.style.display = 'block';
285
298
  }
286
299
  } catch (error) {
300
+ console.error("app: ", app);
301
+ console.error("isRootApp: ", isRootApp, "rootLabel: ", rootLabel);
302
+ console.error("bucketAppName : ", bucketAppName);
287
303
  console.error('Error loading files:', error);
288
304
  container.innerHTML = `
289
305
  <div class="empty-state">
290
- <i class="fas fa-exclamation-triangle" style="color: var(--danger-color-ml);"></i>
306
+ <i class="fas fa-exclamation-triangle empty-state-error-icon"></i>
291
307
  <h4>Error Loading Files</h4>
292
308
  <p>Bucket <strong>${escapeHtml(getActiveBucketLabel())}</strong>: ${escapeHtml(error.message)}</p>
293
- <button class="btn btn-primary" onclick="loadFiles(${page}, '${prefix}')">
309
+ <button class="btn btn-primary" data-action="retry-load" ${dataAttr('retry-page', page)} ${dataAttr('retry-prefix', prefix)}>
294
310
  <i class="fas fa-redo"></i> Retry
295
311
  </button>
296
312
  </div>
@@ -319,8 +335,8 @@ function toggleSort(field) {
319
335
  valA = new Date(a.LastModified || 0).getTime();
320
336
  valB = new Date(b.LastModified || 0).getTime();
321
337
  } else {
322
- valA = (a.Key || '').split('/').pop().toLowerCase();
323
- valB = (b.Key || '').split('/').pop().toLowerCase();
338
+ valA = getBaseName(a.Key).toLowerCase();
339
+ valB = getBaseName(b.Key).toLowerCase();
324
340
  }
325
341
 
326
342
  if (valA < valB) return currentOrder === 'asc' ? -1 : 1;
@@ -360,7 +376,6 @@ function setQuickFilter(filter = 'all') {
360
376
  // Extract unique folders at current level
361
377
  function extractFoldersAtCurrentLevel(files, prefix) {
362
378
  const folderSet = new Set();
363
- const prefixDepth = prefix ? prefix.split('/').filter(p => p).length : 0;
364
379
 
365
380
  files.forEach(file => {
366
381
  const key = file.Key;
@@ -447,6 +462,9 @@ function extractMatchingFolders(files, search) {
447
462
  // Render files table
448
463
  function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
449
464
  lastRenderContext = { files, folders, prefix, apiPrefix };
465
+ const normalizedPrefix = String(prefix || '').replace(/\/+$/, '');
466
+ const basePrefix = apiPrefix || prefix;
467
+ const relativeBaseOffset = basePrefix ? basePrefix.length + (basePrefix.endsWith('/') ? 0 : 1) : 0;
450
468
 
451
469
  // Determine which files/folders to show based on view mode
452
470
  let displayFiles = [];
@@ -466,7 +484,7 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
466
484
  // Skip folder marker objects (keys ending with /)
467
485
  if (f.Key.endsWith('/')) return false;
468
486
 
469
- const fileName = f.Key.split('/').pop();
487
+ const fileName = getBaseName(f.Key);
470
488
  return fileName.toLowerCase().includes(searchLower);
471
489
  });
472
490
 
@@ -485,8 +503,7 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
485
503
  // Skip folder marker objects (keys ending with /)
486
504
  if (file.Key.endsWith('/')) return false;
487
505
 
488
- const basePrefix = apiPrefix || prefix;
489
- const relativePath = basePrefix ? file.Key.substring(basePrefix.length + (basePrefix.endsWith('/') ? 0 : 1)) : file.Key;
506
+ const relativePath = basePrefix ? file.Key.substring(relativeBaseOffset) : file.Key;
490
507
  // File is at current level if it has no '/' in relative path
491
508
  return !relativePath.includes('/');
492
509
  });
@@ -495,7 +512,7 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
495
512
 
496
513
  // Apply quick file-type filter after mode/path filtering.
497
514
  displayFiles = displayFiles.filter((f) => {
498
- const name = String(f?.Key || '').split('/').pop();
515
+ const name = getBaseName(f?.Key);
499
516
  return matchesQuickFilter(name, currentQuickFilter);
500
517
  });
501
518
 
@@ -505,13 +522,15 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
505
522
  // (In flat mode, the prefix acts as a base filter, but we see recursive files under it)
506
523
  if (prefix) {
507
524
  const parts = prefix.split('/').filter(p => p);
508
- let breadcrumbHTML = `
525
+ const safeRootLabel = escapeHtml(rootLabel);
526
+ const safeRootTitle = escapeAttr(rootLabel);
527
+ const breadcrumbParts = [`
509
528
  <div class="breadcrumb-nav">
510
529
  <div class="breadcrumb-item">
511
530
  <i class="fas fa-home"></i>
512
- <span class="breadcrumb-link" onclick="loadFiles(1, '')" title="Go to ${rootLabel}">${rootLabel}</span>
531
+ <button type="button" class="breadcrumb-link" ${dataAttr('nav-prefix', '')} title="Go to ${safeRootTitle}">${safeRootLabel}</button>
513
532
  </div>
514
- `;
533
+ `];
515
534
 
516
535
  let currentPath = '';
517
536
  parts.forEach((part, index) => {
@@ -519,51 +538,57 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
519
538
  const isLast = index === parts.length - 1;
520
539
 
521
540
  if (isLast) {
522
- breadcrumbHTML += `
541
+ const safePart = escapeHtml(part);
542
+ breadcrumbParts.push(`
523
543
  <span class="breadcrumb-separator"><i class="fas fa-chevron-right"></i></span>
524
544
  <div class="breadcrumb-item">
525
- <span class="breadcrumb-current">${part}</span>
545
+ <span class="breadcrumb-current">${safePart}</span>
526
546
  </div>
527
- `;
547
+ `);
528
548
  } else {
529
549
  const pathCopy = currentPath;
530
- breadcrumbHTML += `
550
+ const safePart = escapeHtml(part);
551
+ const safePathTitle = escapeAttr(pathCopy);
552
+ breadcrumbParts.push(`
531
553
  <span class="breadcrumb-separator"><i class="fas fa-chevron-right"></i></span>
532
554
  <div class="breadcrumb-item">
533
- <span class="breadcrumb-link" onclick="loadFiles(1, '${pathCopy}')" title="Go to ${pathCopy}">${part}</span>
555
+ <button type="button" class="breadcrumb-link" ${dataAttr('nav-prefix', pathCopy)} title="Go to ${safePathTitle}">${safePart}</button>
534
556
  </div>
535
- `;
557
+ `);
536
558
  }
537
559
  });
538
560
 
539
- breadcrumbHTML += `</div>`;
540
- breadcrumb = breadcrumbHTML;
561
+ breadcrumbParts.push(`</div>`);
562
+ breadcrumb = breadcrumbParts.join('');
541
563
  }
542
564
 
543
565
  // Generate folder rows (only used in folder mode)
544
566
  const folderRows = displayFolders.map(folderPath => {
545
567
  // Handle folder paths which typically end in a slash (e.g., "folder/subfolder/")
546
568
  const rawFolderPath = String(folderPath || '');
547
- const cleanPath = rawFolderPath.replace(/\/+$/, '');
548
- const folderKeyForDelete = `${cleanPath}/`;
549
- const folderName = cleanPath.split('/').pop();
569
+ const cleanPath = rawFolderPath.replace(/\/+$/, '');
570
+ const folderPathForAction = normalizedPrefix && cleanPath && !cleanPath.startsWith(`${normalizedPrefix}/`)
571
+ ? `${normalizedPrefix}/${cleanPath}`
572
+ : cleanPath;
573
+ const folderKeyForDelete = `${folderPathForAction}/`;
574
+ const folderName = getBaseName(cleanPath);
550
575
  const safeFolderName = escapeHtml(folderName);
551
- const safeFolderPathAttr = escapeAttr(rawFolderPath);
576
+ const safeFolderPathAttr = escapeAttr(`${folderPathForAction}/`);
552
577
  const safeFolderDeleteKeyAttr = escapeAttr(folderKeyForDelete);
553
578
 
554
579
  return `
555
580
  <tr class="folder-row" data-folder-path="${safeFolderPathAttr}" title="Open folder: ${safeFolderName}">
556
- <td style="text-align:center;">
581
+ <td class="table-cell-center">
557
582
  <!-- no selection for folders -->
558
583
  </td>
559
- <td style="padding-left: 1.5rem;">
560
- <div style="display: flex; align-items: center;">
584
+ <td class="file-name-cell">
585
+ <div class="file-row-main">
561
586
  <div class="file-icon">
562
587
  <i class="fas fa-folder"></i>
563
588
  </div>
564
589
  <div class="file-info">
565
- <div class="file-name" style="font-weight: 700;">
566
- <i class="fas fa-folder" style="margin-right: 0.5rem; color: #f59e0b;"></i>${safeFolderName}/
590
+ <div class="file-name folder-name">
591
+ <i class="fas fa-folder folder-leading-icon"></i>${safeFolderName}/
567
592
  </div>
568
593
  </div>
569
594
  <button type="button" class="btn btn-danger btn-sm delete-btn dtm" data-key="${safeFolderDeleteKeyAttr}" data-is-folder="true" title="Delete folder">
@@ -572,12 +597,12 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
572
597
  </div>
573
598
  </td>
574
599
  <td>
575
- <span style="color: var(--gray-color-ml);">\u2014</span>
600
+ ${mutedDash()}
576
601
  </td>
577
- <td style="text-align: center;">
578
- <span style="color: var(--gray-color-ml);">\u2014</span>
602
+ <td class="table-cell-center">
603
+ ${mutedDash()}
579
604
  </td>
580
- <td style="text-align: center;">
605
+ <td class="table-cell-center">
581
606
  <div class="btn-group" role="group">
582
607
  <button type="button" class="btn btn-danger btn-sm delete-btn" data-key="${safeFolderDeleteKeyAttr}" data-is-folder="true" title="Delete folder">
583
608
  <i class="fas fa-trash"></i>
@@ -620,21 +645,23 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
620
645
  const safeFilename = escapeHtml(filename);
621
646
  const safeTruncatedName = escapeHtml(truncatedName);
622
647
  const safeFolderPath = escapeHtml(folderPath);
648
+ const viewButtonClass = isFileViewable ? 'btn btn-primary btn-sm view-btn' : 'btn btn-primary btn-sm view-btn is-disabled';
649
+ const viewButtonDisabled = isFileViewable ? '' : 'disabled';
623
650
 
624
651
  // If in flat mode, we might want to show the folder path more prominently
625
652
  const pathDisplay = (currentViewMode === 'flat' && folderPath && folderPath !== prefix + (prefix ? '/' : ''))
626
653
  ? `<div class="file-path-display">
627
- <i class="fas fa-folder" style="margin-right:4px; opacity:0.5;"></i> ${safeFolderPath}
654
+ <i class="fas fa-folder path-display-icon"></i> ${safeFolderPath}
628
655
  </div>`
629
656
  : '';
630
657
 
631
658
  return `
632
659
  <tr class="file-row">
633
- <td style="text-align:center;">
660
+ <td class="table-cell-center">
634
661
  <input type="checkbox" class="selectFileCheckbox" data-key="${safeKeyAttr}" aria-label="Select ${safeKeyAttr}" />
635
662
  </td>
636
- <td style="padding-left: 1.5rem;">
637
- <div style="display: flex; align-items: center;">
663
+ <td class="file-name-cell">
664
+ <div class="file-row-main">
638
665
  <div class="file-icon ${typeCategory}">
639
666
  <i class="${iconClass}"></i>
640
667
  </div>
@@ -645,18 +672,18 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
645
672
  </div>
646
673
  </td>
647
674
  <td>
648
- ${file.Size ? `<span class="file-size">${formatFileSize(file.Size)}</span>` : '<span style="color: var(--gray-color-ml);">N/A</span>'}
675
+ ${file.Size ? `<span class="file-size">${formatFileSize(file.Size)}</span>` : '<span class="muted-cell-value">N/A</span>'}
649
676
  </td>
650
677
  <td>
651
- ${file.LastModified ? `<span style="color: var(--gray-color-ml);">${formatDate(file.LastModified)}</span>` : '<span style="color: var(--gray-color-ml);">N/A</span>'}
678
+ ${file.LastModified ? `<span class="muted-cell-value">${formatDate(file.LastModified)}</span>` : '<span class="muted-cell-value">N/A</span>'}
652
679
  </td>
653
- <td style="text-align: center;">
680
+ <td class="table-cell-center">
654
681
  <div class="btn-group" role="group">
655
- <button type="button" class="btn btn-primary btn-sm view-btn" data-key="${safeKeyAttr}" data-filename="${safeKeyAttr}"
656
- data-key-enc="${encodedKey}" title="${isFileViewable ? 'View file in browser' : 'File type not supported for viewing'}" ${!isFileViewable ? 'disabled style="opacity:0.5"' : ''}>
682
+ <button type="button" class="${viewButtonClass}" data-key="${safeKeyAttr}" data-filename="${safeKeyAttr}"
683
+ data-key-enc="${encodedKey}" title="${isFileViewable ? 'View file in browser' : 'File type not supported for viewing'}" ${viewButtonDisabled}>
657
684
  <i class="fas fa-eye"></i>
658
685
  </button>
659
- <button type="button" class="btn btn-info btn-sm copy-btn" data-key-enc="${encodedKey}" title="Copy Link" style="color:white;">
686
+ <button type="button" class="btn btn-info btn-sm copy-btn" data-key-enc="${encodedKey}" title="Copy Link">
660
687
  <i class="fas fa-link"></i>
661
688
  </button>
662
689
  <a href="${getDownloadUrl(encodedKey)}" class="btn btn-success btn-sm"
@@ -679,7 +706,7 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
679
706
  ${breadcrumb}
680
707
  <div class="empty-state">
681
708
  <div class="empty-state-illustration">
682
- <i class="fas fa-folder-open" style="font-size: 4rem; color: #cbd5e0;"></i>
709
+ <i class="fas fa-folder-open empty-state-folder-icon"></i>
683
710
  <i class="fas fa-search empty-state-search-icon"></i>
684
711
  </div>
685
712
  <h4>No files found</h4>
@@ -687,13 +714,13 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
687
714
  ${prefix ? `This folder <strong>${escapeHtml(prefix)}</strong> is currently empty in bucket <strong>${escapeHtml(getActiveBucketLabel())}</strong>.` : `Bucket <strong>${escapeHtml(getActiveBucketLabel())}</strong> is empty. Start by uploading files or creating a new folder.`}
688
715
  </p>
689
716
  <div class="empty-state-actions">
690
- <button class="btn btn-primary" onclick="window.scrollTo({top: 0, behavior: 'smooth'}); document.getElementById('fileInput').click()">
717
+ <button class="btn btn-primary" data-action="upload-files">
691
718
  <i class="fas fa-cloud-upload-alt"></i> Upload Files
692
719
  </button>
693
- <button class="btn btn-outline-secondary" onclick="createFolder()">
720
+ <button class="btn btn-outline-secondary" data-action="create-folder">
694
721
  <i class="fas fa-folder-plus"></i> New Folder
695
722
  </button>
696
- ${prefix ? `<button class="btn btn-light" onclick="loadFiles(1, '')">
723
+ ${prefix ? `<button class="btn btn-light" data-action="go-root">
697
724
  <i class="fas fa-home"></i> Go to ${rootLabel}
698
725
  </button>` : ''}
699
726
  </div>
@@ -714,7 +741,7 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
714
741
  ? `<div class="virtualized-info">
715
742
  <span>Showing ${visibleFiles.length} of ${displayFiles.length} files in this view.</span>
716
743
  <div class="virtualized-actions">
717
- ${visibleFiles.length < displayFiles.length ? '<button class="btn btn-sm btn-outline-secondary" onclick="expandVirtualRows()">Load more</button>' : ''}
744
+ ${visibleFiles.length < displayFiles.length ? '<button class="btn btn-sm btn-outline-secondary" data-action="expand-virtual">Load more</button>' : ''}
718
745
  </div>
719
746
  </div>`
720
747
  : '';
@@ -724,7 +751,7 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
724
751
  <div class="table-responsive">
725
752
  <div id="bulkActionsBar" class="bulk-actions-bar">
726
753
  <span id="bulkSelectedCount">0 selected</span>
727
- <div style="float: right;">
754
+ <div class="bulk-actions-controls">
728
755
  <button class="btn btn-danger btn-sm" id="bulkDeleteBtn" disabled>
729
756
  <i class="fas fa-trash"></i> Delete Selected
730
757
  </button>
@@ -733,19 +760,19 @@ function renderFilesTable(files, folders = [], prefix = '', apiPrefix = '') {
733
760
  <table class="table table-hover mb-0">
734
761
  <thead>
735
762
  <tr>
736
- <th style="width:48px; text-align:center;">
763
+ <th class="table-select-col">
737
764
  <input type="checkbox" id="selectAllCheckbox" aria-label="Select all files" />
738
765
  </th>
739
- <th style="padding-left: 1.5rem; cursor: pointer;" onclick="toggleSort('name')">
766
+ <th class="sortable-heading file-name-heading" data-sort-field="name">
740
767
  <i class="fas fa-file-alt"></i> Name ${getSortIcon('name')}
741
768
  </th>
742
- <th style="cursor: pointer;" onclick="toggleSort('size')">
769
+ <th class="sortable-heading" data-sort-field="size">
743
770
  <i class="fas fa-weight"></i> Size ${getSortIcon('size')}
744
771
  </th>
745
- <th style="cursor: pointer;" onclick="toggleSort('date')">
772
+ <th class="sortable-heading" data-sort-field="date">
746
773
  <i class="fas fa-calendar"></i> Last Modified ${getSortIcon('date')}
747
774
  </th>
748
- <th style="text-align: center;">
775
+ <th class="table-cell-center">
749
776
  <i class="fas fa-cogs"></i> Actions
750
777
  </th>
751
778
  </tr>
@@ -767,6 +794,17 @@ function renderPagination(data) {
767
794
  const { currentPage, totalPages, hasPrevPage, hasNextPage, mode } = data;
768
795
 
769
796
  let paginationHTML = '';
797
+ const pageLink = (page, label, extra = {}) => {
798
+ const attrs = [
799
+ dataAttr('page', page),
800
+ dataAttr('prefix', currentPrefix),
801
+ dataAttr('search', currentSearch),
802
+ dataAttr('sort', extra.sort || currentSort),
803
+ dataAttr('order', extra.order || currentOrder)
804
+ ];
805
+ if (extra.token) attrs.push(dataAttr('token', extra.token));
806
+ return `<a class="page-link" href="#" ${attrs.join(' ')}>${label}</a>`;
807
+ };
770
808
 
771
809
  if (mode === 'optimized') {
772
810
  // Optimized Mode: Token-based pagination (Next only, limited Prev)
@@ -789,13 +827,9 @@ function renderPagination(data) {
789
827
 
790
828
  // Next button
791
829
  if (hasNextPage) {
792
- // Quote the token correctly
793
- const tokenStr = currentNextToken ? currentNextToken.replace(/'/g, "\\'") : "";
794
830
  paginationHTML += `
795
831
  <li class="page-item">
796
- <a class="page-link" href="#" onclick="event.preventDefault(); loadFiles(${currentPage + 1}, '${currentPrefix}', '${currentSearch}', '${currentSort}', '${currentOrder}', '${tokenStr}')">
797
- Next <i class="fas fa-chevron-right"></i>
798
- </a>
832
+ ${pageLink(currentPage + 1, 'Next <i class="fas fa-chevron-right"></i>', { token: currentNextToken || '' })}
799
833
  </li>
800
834
  `;
801
835
  } else {
@@ -815,9 +849,7 @@ function renderPagination(data) {
815
849
  if (hasPrevPage) {
816
850
  paginationHTML += `
817
851
  <li class="page-item">
818
- <a class="page-link" href="#" onclick="event.preventDefault(); loadFiles(${currentPage - 1}, '${currentPrefix}', '${currentSearch}')">
819
- <i class="fas fa-chevron-left"></i> Previous
820
- </a>
852
+ ${pageLink(currentPage - 1, '<i class="fas fa-chevron-left"></i> Previous')}
821
853
  </li>
822
854
  `;
823
855
  } else {
@@ -843,7 +875,7 @@ function renderPagination(data) {
843
875
  if (startPage > 1) {
844
876
  paginationHTML += `
845
877
  <li class="page-item">
846
- <a class="page-link" href="#" onclick="event.preventDefault(); loadFiles(1, '${currentPrefix}', '${currentSearch}')">1</a>
878
+ ${pageLink(1, '1')}
847
879
  </li>`;
848
880
  if (startPage > 2) {
849
881
  paginationHTML += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
@@ -860,7 +892,7 @@ function renderPagination(data) {
860
892
  } else {
861
893
  paginationHTML += `
862
894
  <li class="page-item">
863
- <a class="page-link" href="#" onclick="event.preventDefault(); loadFiles(${i}, '${currentPrefix}', '${currentSearch}')">${i}</a>
895
+ ${pageLink(i, String(i))}
864
896
  </li>
865
897
  `;
866
898
  }
@@ -873,7 +905,7 @@ function renderPagination(data) {
873
905
  }
874
906
  paginationHTML += `
875
907
  <li class="page-item">
876
- <a class="page-link" href="#" onclick="event.preventDefault(); loadFiles(${totalPages}, '${currentPrefix}', '${currentSearch}')">${totalPages}</a>
908
+ ${pageLink(totalPages, String(totalPages))}
877
909
  </li>`;
878
910
  }
879
911
 
@@ -881,9 +913,7 @@ function renderPagination(data) {
881
913
  if (hasNextPage) {
882
914
  paginationHTML += `
883
915
  <li class="page-item">
884
- <a class="page-link" href="#" onclick="event.preventDefault(); loadFiles(${currentPage + 1}, '${currentPrefix}', '${currentSearch}')">
885
- Next <i class="fas fa-chevron-right"></i>
886
- </a>
916
+ ${pageLink(currentPage + 1, 'Next <i class="fas fa-chevron-right"></i>')}
887
917
  </li>
888
918
  `;
889
919
  } else {
@@ -976,7 +1006,7 @@ setTimeout(() => {
976
1006
  // Upload queue, drag-and-drop and multi-file support
977
1007
  const uploadQueue = []; // { id, file, status, progress, controller/xhr, uploadId }
978
1008
  const xhrMap = new Map();
979
- const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB chunk threshold
1009
+ const CHUNK_SIZE = 50 * 1024 * 1024; // 50MB chunk threshold
980
1010
 
981
1011
  function createId() { return Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 9); }
982
1012
 
@@ -1030,9 +1060,9 @@ function renderUploadQueue() {
1030
1060
  <div class="form-text file-size upload-item-size-label">${formatFileSize(item.file.size)}</div>
1031
1061
  </div>
1032
1062
  </div>
1033
- <div style="min-width:220px;">
1034
- <div class="progress-container" style="height: 8px; margin-bottom: 6px;">
1035
- <div class="progress-bar" style="width: ${Math.round(item.progress)}%; height: 8px;"></div>
1063
+ <div class="upload-progress-cell">
1064
+ <div class="progress-container upload-progress-track">
1065
+ <div class="progress-bar upload-progress-bar" style="width: ${Math.round(item.progress)}%;"></div>
1036
1066
  </div>
1037
1067
  <div class="upload-item-status-row">
1038
1068
  <small class="text-muted status">${statusText}</small>
@@ -1326,7 +1356,7 @@ document.addEventListener('DOMContentLoaded', function () {
1326
1356
 
1327
1357
  this.disabled = true;
1328
1358
  this.setAttribute('aria-busy', 'true');
1329
- if (bucketSwitchStatusEl) bucketSwitchStatusEl.style.display = 'inline';
1359
+ bucketSwitchStatusEl?.classList.remove('is-hidden');
1330
1360
 
1331
1361
  const qs = nextParams.toString();
1332
1362
  window.location.href = `/mbkbucket${qs ? `?${qs}` : ''}`;
@@ -1384,6 +1414,70 @@ document.addEventListener('DOMContentLoaded', function () {
1384
1414
 
1385
1415
  // Delegate view and delete button clicks to avoid inline JS with unescaped data
1386
1416
  document.addEventListener('click', function (e) {
1417
+ const actionBtn = e.target.closest('[data-action]');
1418
+ if (actionBtn) {
1419
+ const action = actionBtn.dataset.action;
1420
+ if (action === 'upload-files') {
1421
+ window.scrollTo({ top: 0, behavior: 'smooth' });
1422
+ document.getElementById('fileInput')?.click();
1423
+ } else if (action === 'create-folder') {
1424
+ createFolder();
1425
+ } else if (action === 'create-file') {
1426
+ createFile();
1427
+ } else if (action === 'go-root') {
1428
+ loadFiles(1, '');
1429
+ } else if (action === 'expand-virtual') {
1430
+ expandVirtualRows();
1431
+ } else if (action === 'search') {
1432
+ performSearch();
1433
+ } else if (action === 'clear-search') {
1434
+ clearSearch();
1435
+ } else if (action === 'retry-load') {
1436
+ loadFiles(parseInt(actionBtn.dataset.retryPage, 10) || 1, actionBtn.dataset.retryPrefix || '');
1437
+ } else if (action === 'close-toast') {
1438
+ actionBtn.closest('.toast-notification')?.remove();
1439
+ }
1440
+ return;
1441
+ }
1442
+
1443
+ const quickFilterBtn = e.target.closest('[data-filter]');
1444
+ if (quickFilterBtn) {
1445
+ setQuickFilter(quickFilterBtn.dataset.filter || 'all');
1446
+ return;
1447
+ }
1448
+
1449
+ const viewModeBtn = e.target.closest('[data-view-mode]');
1450
+ if (viewModeBtn) {
1451
+ changeViewMode(viewModeBtn.dataset.viewMode);
1452
+ return;
1453
+ }
1454
+
1455
+ const navBtn = e.target.closest('[data-nav-prefix]');
1456
+ if (navBtn) {
1457
+ loadFiles(1, navBtn.dataset.navPrefix || '');
1458
+ return;
1459
+ }
1460
+
1461
+ const sortHeader = e.target.closest('[data-sort-field]');
1462
+ if (sortHeader) {
1463
+ toggleSort(sortHeader.dataset.sortField);
1464
+ return;
1465
+ }
1466
+
1467
+ const pageLink = e.target.closest('.page-link[data-page]');
1468
+ if (pageLink) {
1469
+ e.preventDefault();
1470
+ loadFiles(
1471
+ parseInt(pageLink.dataset.page, 10) || 1,
1472
+ pageLink.dataset.prefix || '',
1473
+ pageLink.dataset.search || '',
1474
+ pageLink.dataset.sort || currentSort,
1475
+ pageLink.dataset.order || currentOrder,
1476
+ pageLink.dataset.token || ''
1477
+ );
1478
+ return;
1479
+ }
1480
+
1387
1481
  const folderRow = e.target.closest('.folder-row');
1388
1482
  if (folderRow && !e.target.closest('.delete-btn')) {
1389
1483
  const folderPath = folderRow.dataset.folderPath;
@@ -2005,4 +2099,4 @@ async function createFile() {
2005
2099
  };
2006
2100
  };
2007
2101
 
2008
- })(window);
2102
+ })(window);
@@ -9,7 +9,6 @@
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
  <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
11
11
  <script src="/mbkauthe/main.js" defer></script>
12
- <script src="https://portal.mbktech.org/Assets/Scripts/timeoutlogout.js" defer></script>
13
12
  <script src="https://portal.mbktech.org/Assets/Scripts/auto-ids.js" defer></script>
14
13
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
15
14
  <link rel="icon" type="image/x-icon" href="/icon.svg">