mbkbucket 3.7.1 → 3.8.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.
package/README.md CHANGED
@@ -267,14 +267,14 @@ All functions are exported from the package root. Full TypeScript declarations i
267
267
 
268
268
  | Template | Description |
269
269
  |---|---|
270
- | `bucketportal.handlebars` | Admin dashboard shell |
271
- | `bucketadmincontent.handlebars` | File listing & management UI |
272
- | `bucketadmin_alerts.handlebars` | Alert/notification partial |
273
- | `bucketadmin_delete_modal.handlebars` | Delete confirmation modal |
274
- | `bucketadmin_preview_modal.handlebars` | File preview modal |
275
- | `bucketadmin_filelist_skeleton.handlebars` | Loading skeleton |
276
- | `bucket.handlebars` | Public file viewer |
277
- | `mbkbucket_info.handlebars` | Info/health page |
270
+ | `bucketportal.hbs` | Admin dashboard shell |
271
+ | `bucketadmincontent.hbs` | File listing & management UI |
272
+ | `bucketadmin_alerts.hbs` | Alert/notification partial |
273
+ | `bucketadmin_delete_modal.hbs` | Delete confirmation modal |
274
+ | `bucketadmin_preview_modal.hbs` | File preview modal |
275
+ | `bucketadmin_filelist_skeleton.hbs` | Loading skeleton |
276
+ | `bucket.hbs` | Public file viewer |
277
+ | `mbkbucket_info.hbs` | Info/health page |
278
278
 
279
279
  ---
280
280
 
@@ -675,10 +675,23 @@
675
675
  position: relative;
676
676
  }
677
677
 
678
+ .view-btn#view-thumbs {
679
+ border-left: 1px solid var(--border-color);
680
+ }
681
+
678
682
  .fm-item-thumb {
679
683
  width: 100%;
680
684
  height: 100%;
681
685
  object-fit: cover;
686
+ transition: opacity 0.25s ease-in-out;
687
+ }
688
+
689
+ .fm-item-thumb.fm-lazy-thumb {
690
+ opacity: 0;
691
+ }
692
+
693
+ .fm-item-thumb.fm-lazy-thumb.is-loaded {
694
+ opacity: 1;
682
695
  }
683
696
 
684
697
  .fm-item-icon .ext {
@@ -20,6 +20,8 @@
20
20
  let currentOrder = 'asc';
21
21
  let currentViewMode = localStorage.getItem('mbk_view_mode') || 'grid'; // 'grid' or 'list'
22
22
  let currentQuickFilter = 'all'; // 'all', 'media', 'docs', 'code', 'starred'
23
+ let showThumbnails = localStorage.getItem('mbk_show_thumbnails') === 'true'; // Fast Mode by default (saves Vercel resources & tokens)
24
+ let thumbObserver = null;
23
25
  let pageFiles = [];
24
26
  let pageFolders = [];
25
27
  let currentApiPrefix = '';
@@ -502,15 +504,16 @@
502
504
  const formattedSize = file.Size ? formatFileSize(file.Size) : '—';
503
505
  const formattedDate = file.LastModified ? formatDate(file.LastModified) : '—';
504
506
 
507
+ const showImgThumb = isImg && showThumbnails;
505
508
  let iconInner = `<span class="ext">${typeCfg.label}</span>`;
506
- if (isImg) {
507
- iconInner = `<img src="${getViewUrl(rawKey)}" class="fm-item-thumb" alt="${safeName}" loading="lazy" onerror="this.outerHTML='<span class=\\'ext\\'>IMG</span>'">`;
509
+ if (showImgThumb) {
510
+ iconInner = `<img data-src="${getViewUrl(rawKey)}" class="fm-item-thumb fm-lazy-thumb" alt="${safeName}" onload="this.classList.add('is-loaded')" onerror="this.outerHTML='<span class=\\'ext\\'>IMG</span>'">`;
508
511
  }
509
512
 
510
513
  if (currentViewMode === 'grid') {
511
514
  html += `
512
515
  <div class="fm-item ${isStarred ? 'starred' : ''}" data-type="file" data-key="${safeKey}" tabindex="0">
513
- <div class="fm-item-icon" style="${isImg ? '' : `background:${typeCfg.bg};color:${typeCfg.color}`}">
516
+ <div class="fm-item-icon" style="${showImgThumb ? '' : `background:${typeCfg.bg};color:${typeCfg.color}`}">
514
517
  ${iconInner}
515
518
  </div>
516
519
  <div class="fm-item-details">
@@ -549,6 +552,44 @@
549
552
  });
550
553
 
551
554
  gridEl.innerHTML = html;
555
+ observeThumbnails();
556
+ }
557
+
558
+ function observeThumbnails() {
559
+ const lazyImages = document.querySelectorAll('img.fm-lazy-thumb[data-src]');
560
+ if (!lazyImages.length) return;
561
+
562
+ if (!('IntersectionObserver' in window)) {
563
+ lazyImages.forEach(img => {
564
+ if (img.dataset.src) {
565
+ img.src = img.dataset.src;
566
+ img.removeAttribute('data-src');
567
+ }
568
+ });
569
+ return;
570
+ }
571
+
572
+ if (thumbObserver) {
573
+ thumbObserver.disconnect();
574
+ }
575
+
576
+ thumbObserver = new IntersectionObserver((entries, observer) => {
577
+ entries.forEach(entry => {
578
+ if (entry.isIntersecting) {
579
+ const img = entry.target;
580
+ if (img.dataset.src) {
581
+ img.src = img.dataset.src;
582
+ img.removeAttribute('data-src');
583
+ }
584
+ observer.unobserve(img);
585
+ }
586
+ });
587
+ }, {
588
+ rootMargin: '100px 0px',
589
+ threshold: 0.01
590
+ });
591
+
592
+ lazyImages.forEach(img => thumbObserver.observe(img));
552
593
  }
553
594
 
554
595
  // Load Files API Call
@@ -996,6 +1037,19 @@
996
1037
  // View mode toggle
997
1038
  const gridBtn = document.getElementById('view-grid');
998
1039
  const listBtn = document.getElementById('view-list');
1040
+ const thumbsBtn = document.getElementById('view-thumbs');
1041
+
1042
+ if (showThumbnails) {
1043
+ thumbsBtn?.classList.add('active');
1044
+ }
1045
+
1046
+ thumbsBtn?.addEventListener('click', () => {
1047
+ showThumbnails = !showThumbnails;
1048
+ localStorage.setItem('mbk_show_thumbnails', String(showThumbnails));
1049
+ thumbsBtn.classList.toggle('active', showThumbnails);
1050
+ renderFiles();
1051
+ showToast(showThumbnails ? 'Image previews enabled' : 'Fast Mode: Previews disabled (saving tokens & bandwidth)', 'info');
1052
+ });
999
1053
 
1000
1054
  gridBtn?.addEventListener('click', () => {
1001
1055
  currentViewMode = 'grid';
@@ -1207,6 +1261,9 @@
1207
1261
  document.getElementById('view-list')?.classList.add('active');
1208
1262
  document.getElementById('view-grid')?.classList.remove('active');
1209
1263
  }
1264
+ if (showThumbnails) {
1265
+ document.getElementById('view-thumbs')?.classList.add('active');
1266
+ }
1210
1267
 
1211
1268
  initEventListeners();
1212
1269
 
@@ -13,7 +13,7 @@
13
13
  <title>Helper Utils Demo — MBKBucket</title>
14
14
  <meta name="description" content="Interactive demo of MBKBucket frontend helper utilities: gallery picker, file input, upload widget, multi-select, live preview binding, and API helpers.">
15
15
 
16
- <!-- Fonts (same as bucket.handlebars) -->
16
+ <!-- Fonts (same as bucket.hbs) -->
17
17
  <link rel="preconnect" href="https://fonts.googleapis.com">
18
18
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
19
19
  <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=IBM+Plex+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
@@ -23,7 +23,7 @@
23
23
  <link rel="stylesheet" href="/mbkbucket/mbkbucket-helper.css">
24
24
 
25
25
  <style>
26
- /* ── Page shell (mirrors bucket.handlebars body) ───────────── */
26
+ /* ── Page shell (mirrors bucket.hbs body) ───────────── */
27
27
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
28
28
 
29
29
  body {
package/lib/src/app.js CHANGED
@@ -72,8 +72,8 @@ export function createApp() {
72
72
  ].filter(dir => fs.existsSync(dir));
73
73
 
74
74
  // Handlebars template engine
75
- server.engine("handlebars", engine({
76
- extname: ".handlebars",
75
+ server.engine("hbs", engine({
76
+ extname: ".hbs",
77
77
  defaultLayout: false,
78
78
  partialsDir: [...new Set(partialsDir)],
79
79
  cache: process.env.NODE_ENV === "production",
@@ -83,7 +83,7 @@ export function createApp() {
83
83
  }
84
84
  }));
85
85
 
86
- server.set("view engine", "handlebars");
86
+ server.set("view engine", "hbs");
87
87
  server.set("views", [...new Set(viewDirs)]);
88
88
 
89
89
  server.use(mbkautheRoutes);
@@ -11,10 +11,10 @@ export function renderBucketDashboard(req, res) {
11
11
  const allBuckets = getAvailableBucketNames();
12
12
  const configuredDefaultBucket = mbkautheVar?.bucket;
13
13
  const isPortal = mbkautheVar?.APP_NAME === "portal";
14
- const bucketfilePath = isPortal ? "bucketportal.handlebars" : "bucket.handlebars";
14
+ const bucketfilePath = isPortal ? "bucketportal.hbs" : "bucket.hbs";
15
15
 
16
16
  const mainLayoutPath = path.resolve(process.cwd(), "views", "layouts", "main");
17
- const hasMainLayout = fs.existsSync(`${mainLayoutPath}.handlebars`) || fs.existsSync(mainLayoutPath);
17
+ const hasMainLayout = fs.existsSync(`${mainLayoutPath}.hbs`) || fs.existsSync(mainLayoutPath);
18
18
  const portalLayout = (isPortal && hasMainLayout) && mainLayoutPath;
19
19
 
20
20
  let selectedBucket;
@@ -19,7 +19,7 @@ async function getInfoData() {
19
19
  * Render the mbkbucket info page (Handlebars).
20
20
  */
21
21
  export async function infoPage(req, res) {
22
- renderPage(req, res, "mbkbucket_info.handlebars", false, {
22
+ renderPage(req, res, "mbkbucket_info.hbs", false, {
23
23
  page: "MBK Bucket Info",
24
24
  ...(await getInfoData())
25
25
  });
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "#logger";
2
- import { isJsonRequest, sendError, sanitizeErrorDetails } from "mbkauthe";
2
+ import { isJsonRequest, sendError, sanitizeErrorDetails, renderPage } from "mbkauthe";
3
3
 
4
4
  const debugServer = createLogger('server');
5
5
  const BASE_ERROR_DATA = { layout: false, pagename: "Home", page: "/" };
@@ -18,7 +18,8 @@ export function notFoundHandler(req, res, next) {
18
18
  });
19
19
  }
20
20
 
21
- return res.status(404).render("Error/dError.handlebars", {
21
+ res.status(404);
22
+ return renderPage(req, res, "Error/dError.hbs", false, {
22
23
  ...BASE_ERROR_DATA,
23
24
  code: 404,
24
25
  error: "Not Found",
@@ -40,7 +41,8 @@ export function globalErrorHandler(err, req, res, _next) {
40
41
 
41
42
  const sanitizedDetails = err.message ? sanitizeErrorDetails(err.message) : undefined;
42
43
 
43
- return res.status(statusCode).render("Error/dError.handlebars", {
44
+ res.status(statusCode);
45
+ return renderPage(req, res, "Error/dError.hbs", false, {
44
46
  ...BASE_ERROR_DATA,
45
47
  code: statusCode,
46
48
  error: statusCode >= 500 ? "Internal app Error" : (err.name || "Client Error"),
@@ -1,12 +1,14 @@
1
1
  import rateLimit from "express-rate-limit";
2
+ import { renderPage } from "mbkauthe";
2
3
 
3
4
  export const generalLimiter = rateLimit({
4
5
  windowMs: 2 * 60 * 1000,
5
6
  max: 150,
6
7
  standardHeaders: true,
7
8
  legacyHeaders: false,
8
- handler: (_req, res) => {
9
- res.status(429).render('error.handlebars', { message: 'Too many requests from your IP. Try again later.', code: 429 });
9
+ handler: (req, res) => {
10
+ res.status(429);
11
+ return renderPage(req, res, 'error.hbs', false, { message: 'Too many requests from your IP. Try again later.', code: 429 });
10
12
  }
11
13
  });
12
14
 
@@ -128,6 +128,7 @@ export function buildCacheControl(contentType, { isStaticAsset = false, supports
128
128
  return supportsRanges ? 'public, max-age=86400, must-revalidate' : 'public, max-age=300, must-revalidate';
129
129
  }
130
130
  if (isSensitiveTextType(contentType)) return 'private, no-store';
131
+ if (isStaticAsset) return 'private, max-age=86400, stale-while-revalidate=604800';
131
132
  return supportsRanges ? 'private, max-age=1800, must-revalidate' : 'private, max-age=300, must-revalidate';
132
133
  }
133
134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkbucket",
3
- "version": "3.7.1",
3
+ "version": "3.8.0",
4
4
  "description": "Flexible S3/R2 bucket management — library, Express router, and CLI for mbktech.org applications. Provides upload, download, listing, multipart utilities, signed URLs, and a command-line interface with built-in configuration validation and health checks",
5
5
  "author": "Muhammad Bin Khalid",
6
6
  "homepage": "https://github.com/MIbnEKhalid/mbkbucket#readme",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "scripts": {
15
15
  "dev": "cross-env NODE_ENV=dev nodemon index.js",
16
+ "publish": "node scripts/publish.js",
16
17
  "test": "vitest run"
17
18
  },
18
19
  "imports": {
@@ -28,7 +29,7 @@
28
29
  "express": "^5.1.0",
29
30
  "express-handlebars": "^9.0.1",
30
31
  "express-rate-limit": "^7.5.1",
31
- "mbkauthe": "^6.0.0",
32
+ "mbkauthe": "file:../mbkauthe",
32
33
  "multer": "^2.0.2",
33
34
  "pg": "^8.16.3"
34
35
  },
@@ -16,7 +16,7 @@
16
16
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
17
  <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=IBM+Plex+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
18
18
  <script src="/mbkauthe/main.js" defer></script>
19
- <script src="https://portal.mbktech.org/Assets/Scripts/auto-ids.js" defer></script>
19
+ <script src="https://portal.mbktech.org/qssets/js/auto-ids.js" defer></script>
20
20
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
21
21
  <link rel="icon" type="image/x-icon" href="/icon.svg">
22
22
  <style>
@@ -89,6 +89,9 @@
89
89
  <button type="button" class="view-btn" id="view-list" aria-label="List view" title="List view">
90
90
  <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="2" y1="4" x2="14" y2="4"/><line x1="2" y1="8" x2="14" y2="8"/><line x1="2" y1="12" x2="14" y2="12"/></svg>
91
91
  </button>
92
+ <button type="button" class="view-btn" id="view-thumbs" aria-label="Toggle image previews" title="Toggle image previews (Fast Mode saves bandwidth & tokens)">
93
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><circle cx="5.5" cy="5.5" r="1.5"/><path d="M14 10l-4-4-6 6"/></svg>
94
+ </button>
92
95
  </div>
93
96
  </div>
94
97
  </div>
@@ -15,7 +15,7 @@
15
15
  content="Log in to portal.mbktech.org to access your resources and manage projects securely.">
16
16
  <meta name="keywords" content="MBK Tech, Web-Portal, Web, Portal, Admin-Panel, Admin, login">
17
17
  <meta property="og:title" content="Version and Configuration Information | MBK Tech" />
18
- <meta property="og:image" content="https://mbktech.org/Assets/Images/Icon/logo.png" />
18
+ <meta property="og:image" content="https://mbktech.org/assets/images/logo.png" />
19
19
  <meta property="og:url" content="/mbkbucket/info">
20
20
  <title>Version and Configuration Information | MBK Tech Portal</title>
21
21
  <link rel="icon" type="image/svg+xml" href="/icon.svg">