@putkoff/abstract-securefiles 0.0.50 → 0.0.51
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.cjs +34 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +34 -21
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +4 -1
- package/package.json +1 -1
- package/src/SecureFiles.tsx +1 -1
- package/src/ShareDialog.tsx +26 -5
- package/src/types.ts +4 -1
package/dist/index.cjs
CHANGED
|
@@ -245,6 +245,25 @@ function CopyButton({ text, label = "Copy link" }) {
|
|
|
245
245
|
return (jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: copy, children: copied ? "Copied" : label }));
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
function ConfirmButton({ label, onConfirm, disabled, }) {
|
|
249
|
+
const [armed, setArmed] = react.useState(false);
|
|
250
|
+
react.useEffect(() => {
|
|
251
|
+
if (!armed)
|
|
252
|
+
return;
|
|
253
|
+
const t = setTimeout(() => setArmed(false), 3000);
|
|
254
|
+
return () => clearTimeout(t);
|
|
255
|
+
}, [armed]);
|
|
256
|
+
return (jsxRuntime.jsx("button", { type: "button", className: `sf-btn sf-btn--danger${armed ? " sf-btn--armed" : ""}`, disabled: disabled, onClick: () => {
|
|
257
|
+
if (armed) {
|
|
258
|
+
setArmed(false);
|
|
259
|
+
onConfirm();
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
setArmed(true);
|
|
263
|
+
}
|
|
264
|
+
}, children: armed ? "Confirm" : label }));
|
|
265
|
+
}
|
|
266
|
+
|
|
248
267
|
function ShareDialog({ file, onClose, onSave, }) {
|
|
249
268
|
const [shareable, setShareable] = react.useState(file.shareable);
|
|
250
269
|
const [limit, setLimit] = react.useState(file.download_limit ? String(file.download_limit) : "");
|
|
@@ -260,6 +279,19 @@ function ShareDialog({ file, onClose, onSave, }) {
|
|
|
260
279
|
}, [onClose]);
|
|
261
280
|
const limitValid = limit === "" || /^\d+$/.test(limit);
|
|
262
281
|
const passwordValid = pwMode !== "set" || password.length > 0;
|
|
282
|
+
const newLink = async () => {
|
|
283
|
+
setSaving(true);
|
|
284
|
+
setError(null);
|
|
285
|
+
try {
|
|
286
|
+
await onSave({ id: file.id, shareable: true, regenerate_link: true });
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
setSaving(false);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
263
295
|
const save = async () => {
|
|
264
296
|
setSaving(true);
|
|
265
297
|
setError(null);
|
|
@@ -284,26 +316,7 @@ function ShareDialog({ file, onClose, onSave, }) {
|
|
|
284
316
|
setSaving(false);
|
|
285
317
|
}
|
|
286
318
|
};
|
|
287
|
-
return (jsxRuntime.jsx("div", { className: "sf-overlay", onMouseDown: (e) => e.target === e.currentTarget && onClose(), children: jsxRuntime.jsxs("div", { className: "sf-dialog", role: "dialog", "aria-modal": "true", "aria-label": `Share settings for ${file.filename}`, children: [jsxRuntime.jsxs("header", { className: "sf-dialog__head", children: [jsxRuntime.jsx("h3", { children: "Share settings" }), jsxRuntime.jsx("button", { type: "button", className: "sf-icon-btn", "aria-label": "Close", onClick: onClose, children: "\u2715" })] }), jsxRuntime.jsx("p", { className: "sf-muted", children: file.filepath }), jsxRuntime.jsxs("label", { className: "sf-check", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: shareable, onChange: (e) => setShareable(e.target.checked) }), "Anyone with the link can download"] }), jsxRuntime.jsxs("fieldset", { disabled: !shareable, className: "sf-fieldset", children: [jsxRuntime.jsxs("label", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Download limit (blank = unlimited)" }), jsxRuntime.jsx("input", { className: "sf-input", inputMode: "numeric", value: limit, onChange: (e) => setLimit(e.target.value.trim()), placeholder: "unlimited" }), !limitValid && jsxRuntime.jsx("span", { className: "sf-error", children: "Enter a whole number" })] }), jsxRuntime.jsxs("p", { className: "sf-muted", children: ["Downloaded ", file.download_count, " time", file.download_count === 1 ? "" : "s", file.download_limit ? ` of ${file.download_limit}` : "", ".", " ", jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: resetCount, onChange: (e) => setResetCount(e.target.checked) }), "reset count"] })] }), jsxRuntime.jsxs("div", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Password" }), jsxRuntime.jsxs("div", { className: "sf-row", children: [file.has_password && (jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "keep", onChange: () => setPwMode("keep") }), "keep current"] })), jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "set", onChange: () => setPwMode("set") }), file.has_password ? "change" : "require password"] }), jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "remove", onChange: () => setPwMode("remove") }), file.has_password ? "remove" : "none"] })] }), pwMode === "set" && (jsxRuntime.jsx("input", { className: "sf-input", type: "password", autoComplete: "new-password", value: password, onChange: (e) => setPassword(e.target.value), placeholder: "New password" }))] }), jsxRuntime.jsxs("div", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Share link" }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("input", { className: "sf-input", readOnly: true, value: file.share_url, onFocus: (e) => e.target.select() }), jsxRuntime.jsx(CopyButton, { text: file.share_url })] }), !file.shareable && jsxRuntime.jsx("span", { className: "sf-muted", children: "The link works once you save with sharing on." })] })] }), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), jsxRuntime.jsxs("footer", { className: "sf-row sf-dialog__foot", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: onClose, children: "Cancel" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", disabled: saving || !limitValid || !passwordValid, onClick: save, children: saving ? "Saving…" : "Save" })] })] }) }));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function ConfirmButton({ label, onConfirm, disabled, }) {
|
|
291
|
-
const [armed, setArmed] = react.useState(false);
|
|
292
|
-
react.useEffect(() => {
|
|
293
|
-
if (!armed)
|
|
294
|
-
return;
|
|
295
|
-
const t = setTimeout(() => setArmed(false), 3000);
|
|
296
|
-
return () => clearTimeout(t);
|
|
297
|
-
}, [armed]);
|
|
298
|
-
return (jsxRuntime.jsx("button", { type: "button", className: `sf-btn sf-btn--danger${armed ? " sf-btn--armed" : ""}`, disabled: disabled, onClick: () => {
|
|
299
|
-
if (armed) {
|
|
300
|
-
setArmed(false);
|
|
301
|
-
onConfirm();
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
setArmed(true);
|
|
305
|
-
}
|
|
306
|
-
}, children: armed ? "Confirm" : label }));
|
|
319
|
+
return (jsxRuntime.jsx("div", { className: "sf-overlay", onMouseDown: (e) => e.target === e.currentTarget && onClose(), children: jsxRuntime.jsxs("div", { className: "sf-dialog", role: "dialog", "aria-modal": "true", "aria-label": `Share settings for ${file.filename}`, children: [jsxRuntime.jsxs("header", { className: "sf-dialog__head", children: [jsxRuntime.jsx("h3", { children: "Share settings" }), jsxRuntime.jsx("button", { type: "button", className: "sf-icon-btn", "aria-label": "Close", onClick: onClose, children: "\u2715" })] }), jsxRuntime.jsx("p", { className: "sf-muted", children: file.filepath }), jsxRuntime.jsxs("label", { className: "sf-check", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: shareable, onChange: (e) => setShareable(e.target.checked) }), "Anyone with the link can download"] }), jsxRuntime.jsxs("fieldset", { disabled: !shareable, className: "sf-fieldset", children: [jsxRuntime.jsxs("label", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Download limit (blank = unlimited)" }), jsxRuntime.jsx("input", { className: "sf-input", inputMode: "numeric", value: limit, onChange: (e) => setLimit(e.target.value.trim()), placeholder: "unlimited" }), !limitValid && jsxRuntime.jsx("span", { className: "sf-error", children: "Enter a whole number" })] }), jsxRuntime.jsxs("p", { className: "sf-muted", children: ["Downloaded ", file.download_count, " time", file.download_count === 1 ? "" : "s", file.download_limit ? ` of ${file.download_limit}` : "", ".", " ", jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: resetCount, onChange: (e) => setResetCount(e.target.checked) }), "reset count"] })] }), jsxRuntime.jsxs("div", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Password" }), jsxRuntime.jsxs("div", { className: "sf-row", children: [file.has_password && (jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "keep", onChange: () => setPwMode("keep") }), "keep current"] })), jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "set", onChange: () => setPwMode("set") }), file.has_password ? "change" : "require password"] }), jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "remove", onChange: () => setPwMode("remove") }), file.has_password ? "remove" : "none"] })] }), pwMode === "set" && (jsxRuntime.jsx("input", { className: "sf-input", type: "password", autoComplete: "new-password", value: password, onChange: (e) => setPassword(e.target.value), placeholder: "New password" }))] }), jsxRuntime.jsxs("div", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Share link" }), file.share_url ? (jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("input", { className: "sf-input", readOnly: true, value: file.share_url, onFocus: (e) => e.target.select() }), jsxRuntime.jsx(CopyButton, { text: file.share_url }), jsxRuntime.jsx(ConfirmButton, { label: "New link", disabled: saving, onConfirm: newLink })] })) : (jsxRuntime.jsx("span", { className: "sf-muted", children: "A private link is created when you save with sharing on." })), file.share_url && !file.shareable && (jsxRuntime.jsx("span", { className: "sf-muted", children: "The link works once you save with sharing on." })), jsxRuntime.jsx("span", { className: "sf-muted", children: "\"New link\" invalidates the current one immediately." })] })] }), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), jsxRuntime.jsxs("footer", { className: "sf-row sf-dialog__foot", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: onClose, children: "Cancel" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", disabled: saving || !limitValid || !passwordValid, onClick: save, children: saving ? "Saving…" : "Save" })] })] }) }));
|
|
307
320
|
}
|
|
308
321
|
|
|
309
322
|
function formatSize(bytes) {
|
|
@@ -589,7 +602,7 @@ function SecureFiles({ apiBase }) {
|
|
|
589
602
|
const searching = search.trim().length > 0;
|
|
590
603
|
const renderFile = (f, depth) => {
|
|
591
604
|
const busy = busyIds.has(f.id);
|
|
592
|
-
return (jsxRuntime.jsxs("tr", { className: busy ? "sf-busy" : undefined, children: [jsxRuntime.jsx("td", { className: "sf-col-check", children: jsxRuntime.jsx("input", { type: "checkbox", "aria-label": `Select ${f.filename}`, checked: selected.has(f.id), onChange: (e) => toggle([f.id], e.target.checked) }) }), jsxRuntime.jsxs("td", { style: { paddingLeft: 10 + depth * 20 }, children: [jsxRuntime.jsx("span", { className: f.available ? undefined : "sf-strike", children: f.filename }), f.kind === "folder" && (jsxRuntime.jsx("span", { className: "sf-tag", title: "Downloads as one uncompressed, resumable .tar of the whole folder", children: "folder \u00B7 .tar" })), f.linked && (jsxRuntime.jsx("span", { className: "sf-tag", title: f.source_path ?? undefined, children: "linked" })), !f.available && (jsxRuntime.jsx("span", { className: "sf-tag sf-tag--warn", title: f.linked ? "Source file is gone or its directory is no longer allowed" : "File is missing on disk", children: "missing" }))] }), jsxRuntime.jsxs("td", { children: [jsxRuntime.jsx("button", { type: "button", className: `sf-pill${f.shareable ? " sf-pill--on" : ""}`, disabled: busy || !f.available, onClick: () => setShared([f], !f.shareable), title: f.shareable ? "Click to stop sharing" : "Click to share", children: f.shareable ? "Shared" : "Private" }), f.shareable && f.has_password && jsxRuntime.jsx("span", { title: "Password protected", children: " \uD83D\uDD12" })] }), jsxRuntime.jsxs("td", { children: [f.download_count, " / ", f.download_limit ?? "∞"] }), jsxRuntime.jsx("td", { className: "sf-col-actions", children: jsxRuntime.jsxs("div", { className: "sf-row sf-row--end", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: busy || !f.available, onClick: () => download([f]), children: "Download" }), f.shareable && f.available && jsxRuntime.jsx(CopyButton, { text: f.share_url }), jsxRuntime.jsx("button", { type: "button", className: "sf-icon-btn", "aria-label": `Share settings for ${f.filename}`, title: "Share settings", onClick: () => setEditing(f), children: "\u2699\uFE0F" }), jsxRuntime.jsx(ConfirmButton, { label: f.linked ? "Unlink" : "Delete", disabled: busy, onConfirm: () => remove([f]) })] }) })] }, f.id));
|
|
605
|
+
return (jsxRuntime.jsxs("tr", { className: busy ? "sf-busy" : undefined, children: [jsxRuntime.jsx("td", { className: "sf-col-check", children: jsxRuntime.jsx("input", { type: "checkbox", "aria-label": `Select ${f.filename}`, checked: selected.has(f.id), onChange: (e) => toggle([f.id], e.target.checked) }) }), jsxRuntime.jsxs("td", { style: { paddingLeft: 10 + depth * 20 }, children: [jsxRuntime.jsx("span", { className: f.available ? undefined : "sf-strike", children: f.filename }), f.kind === "folder" && (jsxRuntime.jsx("span", { className: "sf-tag", title: "Downloads as one uncompressed, resumable .tar of the whole folder", children: "folder \u00B7 .tar" })), f.linked && (jsxRuntime.jsx("span", { className: "sf-tag", title: f.source_path ?? undefined, children: "linked" })), !f.available && (jsxRuntime.jsx("span", { className: "sf-tag sf-tag--warn", title: f.linked ? "Source file is gone or its directory is no longer allowed" : "File is missing on disk", children: "missing" }))] }), jsxRuntime.jsxs("td", { children: [jsxRuntime.jsx("button", { type: "button", className: `sf-pill${f.shareable ? " sf-pill--on" : ""}`, disabled: busy || !f.available, onClick: () => setShared([f], !f.shareable), title: f.shareable ? "Click to stop sharing" : "Click to share", children: f.shareable ? "Shared" : "Private" }), f.shareable && f.has_password && jsxRuntime.jsx("span", { title: "Password protected", children: " \uD83D\uDD12" })] }), jsxRuntime.jsxs("td", { children: [f.download_count, " / ", f.download_limit ?? "∞"] }), jsxRuntime.jsx("td", { className: "sf-col-actions", children: jsxRuntime.jsxs("div", { className: "sf-row sf-row--end", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: busy || !f.available, onClick: () => download([f]), children: "Download" }), f.shareable && f.available && f.share_url && jsxRuntime.jsx(CopyButton, { text: f.share_url }), jsxRuntime.jsx("button", { type: "button", className: "sf-icon-btn", "aria-label": `Share settings for ${f.filename}`, title: "Share settings", onClick: () => setEditing(f), children: "\u2699\uFE0F" }), jsxRuntime.jsx(ConfirmButton, { label: f.linked ? "Unlink" : "Delete", disabled: busy, onConfirm: () => remove([f]) })] }) })] }, f.id));
|
|
593
606
|
};
|
|
594
607
|
const renderFolder = (node, depth) => {
|
|
595
608
|
const rows = [];
|