@putkoff/abstract-securefiles 0.0.43 → 0.0.47

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/dist/index.js CHANGED
@@ -2,8 +2,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { createPortal } from 'react-dom';
3
3
  import * as React from 'react';
4
4
  import { useCallback, useState, useEffect, useRef, useMemo } from 'react';
5
- import { requireToken, alertit, currentUsername } from '@putkoff/abstract-utilities';
6
- import { secureFetchIt } from '@putkoff/abstract-apis';
5
+ import { alertit, currentUsername } from '@putkoff/abstract-utilities';
7
6
 
8
7
  function ClipboardIcon({
9
8
  title,
@@ -164,110 +163,6 @@ async function downloadFile(item) {
164
163
  triggerDownload(blob, item.filename || "download");
165
164
  }
166
165
 
167
- async function downloadSelect(item) {
168
- const token = requireToken();
169
- const body = item;
170
- const headers = {
171
- Authorization: `Bearer ${token}`,
172
- Accept: "application/octet-stream",
173
- "Content-Type": "application/json"
174
- };
175
- const blob = await secureFetchIt("download", body, "POST", headers, true, true);
176
- if (!blob || blob.size === 0) {
177
- throw new Error(`Received empty file data for “${item.filename}”`);
178
- }
179
- triggerDownload(blob, item.filename);
180
- }
181
-
182
- /** Given a checkbox inside a table-row → download that row’s file. */
183
- async function downloadRow(cb) {
184
- const row = cb.closest("tr");
185
- const filepath = cb.value;
186
- const filename = row?.children[1]?.textContent?.trim() || "downloaded_file";
187
- if (filepath)
188
- await downloadSelect({ filepath, filename });
189
- }
190
- /** Bulk download: pass a NodeList or array of checkboxes. */
191
- async function downloadSelected(checkboxes) {
192
- const list = Array.from(checkboxes);
193
- if (!list.length) {
194
- alert("No files selected.");
195
- return;
196
- }
197
- for (const cb of list) {
198
- await downloadRow(cb);
199
- }
200
- }
201
-
202
- /**
203
- * Classic “select all rows” logic (still handy in non-React views
204
- * such as the admin dashboard).
205
- */
206
- function syncHeaderCheckbox() {
207
- const all = document.querySelectorAll(".file-checkbox");
208
- const checked = document.querySelectorAll(".file-checkbox:checked");
209
- const header = document.getElementById("select-all-checkbox");
210
- header.indeterminate =
211
- checked.length > 0 && checked.length < all.length;
212
- header.checked = checked.length === all.length;
213
- }
214
-
215
- /** Wire up header + row checkboxes once the table is in the DOM. */
216
- function initSelectAll() {
217
- const header = document.getElementById("select-all-checkbox");
218
- const btn = document.getElementById("select-all");
219
- // 1) header → toggle all
220
- header.addEventListener("change", (e) => {
221
- const on = e.target.checked;
222
- document
223
- .querySelectorAll(".file-checkbox")
224
- .forEach((cb) => (cb.checked = on));
225
- });
226
- // 2) button → toggle all
227
- btn.addEventListener("click", () => {
228
- const cbs = Array.from(document.querySelectorAll(".file-checkbox"));
229
- if (!cbs.length) {
230
- alert("No files to select.");
231
- return;
232
- }
233
- const anyUnchecked = cbs.some((cb) => !cb.checked);
234
- cbs.forEach((cb) => (cb.checked = anyUnchecked));
235
- syncHeaderCheckbox();
236
- });
237
- // 3) row boxes → keep header state
238
- document
239
- .querySelectorAll(".file-checkbox")
240
- .forEach((cb) => cb.addEventListener("change", syncHeaderCheckbox));
241
- syncHeaderCheckbox();
242
- }
243
-
244
- /** Utility for other screens: toggle a list of checkboxes on/off. */
245
- function toggleCheckboxes(cbs) {
246
- if (!cbs.length) {
247
- alert("No files to select.");
248
- return;
249
- }
250
- const anyUnchecked = cbs.some((cb) => !cb.checked);
251
- cbs.forEach((cb) => (cb.checked = anyUnchecked));
252
- syncHeaderCheckbox();
253
- }
254
-
255
- const API_BASE$1 = '/api/secure-files';
256
- /** Generic PATCH helper for file-level admin settings. */
257
- async function patchFileSetting(fileId, field, value) {
258
- const res = await fetch(`${API_BASE$1}/files/${fileId}/settings/${field}`, {
259
- method: 'PATCH',
260
- credentials: 'include',
261
- headers: { 'Content-Type': 'application/json' },
262
- body: JSON.stringify({ value }),
263
- });
264
- if (!res.ok) {
265
- const err = await res.json().catch(() => ({}));
266
- throw new Error(err.error || res.statusText);
267
- }
268
- return (await res.json());
269
- }
270
-
271
166
  /**
272
167
  * Cookie-authenticated API helpers.
273
168
  * All requests include credentials: 'include' so the browser sends the
@@ -304,10 +199,10 @@ function resolveApiBase() {
304
199
  // 4. Same-origin default
305
200
  return "/api/secure-files";
306
201
  }
307
- const API_BASE = resolveApiBase();
202
+ const API_BASE$1 = resolveApiBase();
308
203
  function apiUrl(path) {
309
204
  const p = path.startsWith("/") ? path : `/${path}`;
310
- return `${API_BASE}${p}`;
205
+ return `${API_BASE$1}${p}`;
311
206
  }
312
207
  /**
313
208
  * Generic JSON fetch with session-cookie auth.
@@ -368,6 +263,118 @@ async function cookieUpload(file, relPath = "") {
368
263
  throw new Error(`Upload failed: HTTP ${res.status}`);
369
264
  return res.json();
370
265
  }
266
+ /** The resolved API base URL, exported for debugging / display purposes. */
267
+ const SECURE_FILES_API_BASE = API_BASE$1;
268
+
269
+ async function downloadSelect(item) {
270
+ const res = await fetch(`${SECURE_FILES_API_BASE}/download`, {
271
+ method: "POST",
272
+ credentials: "include",
273
+ headers: {
274
+ "Content-Type": "application/json",
275
+ Accept: "application/octet-stream",
276
+ },
277
+ body: JSON.stringify(item),
278
+ });
279
+ if (res.status === 401)
280
+ throw new Error("Not authenticated – please log in.");
281
+ if (!res.ok)
282
+ throw new Error(`Download failed: HTTP ${res.status}`);
283
+ const blob = await res.blob();
284
+ if (!blob || blob.size === 0) {
285
+ throw new Error(`Received empty file data for "${item.filename}"`);
286
+ }
287
+ triggerDownload(blob, item.filename);
288
+ }
289
+
290
+ /** Given a checkbox inside a table-row → download that row’s file. */
291
+ async function downloadRow(cb) {
292
+ const row = cb.closest("tr");
293
+ const filepath = cb.value;
294
+ const filename = row?.children[1]?.textContent?.trim() || "downloaded_file";
295
+ if (filepath)
296
+ await downloadSelect({ filepath, filename });
297
+ }
298
+ /** Bulk download: pass a NodeList or array of checkboxes. */
299
+ async function downloadSelected(checkboxes) {
300
+ const list = Array.from(checkboxes);
301
+ if (!list.length) {
302
+ alert("No files selected.");
303
+ return;
304
+ }
305
+ for (const cb of list) {
306
+ await downloadRow(cb);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Classic “select all rows” logic (still handy in non-React views
312
+ * such as the admin dashboard).
313
+ */
314
+ function syncHeaderCheckbox() {
315
+ const all = document.querySelectorAll(".file-checkbox");
316
+ const checked = document.querySelectorAll(".file-checkbox:checked");
317
+ const header = document.getElementById("select-all-checkbox");
318
+ header.indeterminate =
319
+ checked.length > 0 && checked.length < all.length;
320
+ header.checked = checked.length === all.length;
321
+ }
322
+
323
+ /** Wire up header + row checkboxes once the table is in the DOM. */
324
+ function initSelectAll() {
325
+ const header = document.getElementById("select-all-checkbox");
326
+ const btn = document.getElementById("select-all");
327
+ // 1) header → toggle all
328
+ header.addEventListener("change", (e) => {
329
+ const on = e.target.checked;
330
+ document
331
+ .querySelectorAll(".file-checkbox")
332
+ .forEach((cb) => (cb.checked = on));
333
+ });
334
+ // 2) button → toggle all
335
+ btn.addEventListener("click", () => {
336
+ const cbs = Array.from(document.querySelectorAll(".file-checkbox"));
337
+ if (!cbs.length) {
338
+ alert("No files to select.");
339
+ return;
340
+ }
341
+ const anyUnchecked = cbs.some((cb) => !cb.checked);
342
+ cbs.forEach((cb) => (cb.checked = anyUnchecked));
343
+ syncHeaderCheckbox();
344
+ });
345
+ // 3) row boxes → keep header state
346
+ document
347
+ .querySelectorAll(".file-checkbox")
348
+ .forEach((cb) => cb.addEventListener("change", syncHeaderCheckbox));
349
+ syncHeaderCheckbox();
350
+ }
351
+
352
+ /** Utility for other screens: toggle a list of checkboxes on/off. */
353
+ function toggleCheckboxes(cbs) {
354
+ if (!cbs.length) {
355
+ alert("No files to select.");
356
+ return;
357
+ }
358
+ const anyUnchecked = cbs.some((cb) => !cb.checked);
359
+ cbs.forEach((cb) => (cb.checked = anyUnchecked));
360
+ syncHeaderCheckbox();
361
+ }
362
+
363
+ const API_BASE = '/api/secure-files';
364
+ /** Generic PATCH helper for file-level admin settings. */
365
+ async function patchFileSetting(fileId, field, value) {
366
+ const res = await fetch(`${API_BASE}/files/${fileId}/settings/${field}`, {
367
+ method: 'PATCH',
368
+ credentials: 'include',
369
+ headers: { 'Content-Type': 'application/json' },
370
+ body: JSON.stringify({ value }),
371
+ });
372
+ if (!res.ok) {
373
+ const err = await res.json().catch(() => ({}));
374
+ throw new Error(err.error || res.statusText);
375
+ }
376
+ return (await res.json());
377
+ }
371
378
 
372
379
  /** File-upload helper (used by non-React fallback pages). */
373
380
  async function uploadFile(file, subdir) {