style: remove hyphens from compound adjectives in comments and messages

Remove hyphens from compound adjectives such as "logged-in", "one-time", "password-protected", "full-height", "multi-file", and "S3-compatible" in comments, test error messages, and UI labels to improve readability and consistency.
This commit is contained in:
2026-06-16 01:34:13 +03:00
parent 78b767a4a2
commit 78bf3ef11b
135 changed files with 87 additions and 20487 deletions

View File

@@ -68,7 +68,7 @@ func TestLoggedInUploadStoresOwnerAndAnonymousUploadDoesNot(t *testing.T) {
}
}
if !foundOwned {
t.Fatalf("logged-in upload did not store owner id %q", user.ID)
t.Fatalf("logged in upload did not store owner id %q", user.ID)
}
}
@@ -674,7 +674,7 @@ func TestAPIDocsHeaderReflectsLoggedInUser(t *testing.T) {
body := response.Body.String()
header := body[:strings.Index(body, "<main")]
if !strings.Contains(header, "Dashboard") || strings.Contains(header, "Sign in") || strings.Contains(header, "Health") {
t.Fatalf("api header did not reflect logged-in state: %s", body)
t.Fatalf("api header did not reflect logged in state: %s", body)
}
}
@@ -775,7 +775,7 @@ func TestAdminOverviewRendersInlineBarDimensions(t *testing.T) {
}
body := response.Body.String()
if !strings.Contains(body, `style="height: 150px"`) {
t.Fatalf("admin overview did not render a full-height pixel bar: %s", body)
t.Fatalf("admin overview did not render a full height pixel bar: %s", body)
}
if !strings.Contains(body, `data-height-px="150"`) || !strings.Contains(body, `data-chart-value=`) {
t.Fatalf("admin overview did not render chart fallback data attributes: %s", body)

View File

@@ -398,7 +398,7 @@ func buildAdminOverview(boxes []services.AdminBox, stats services.AdminStats) ad
statusBars := []adminStatBar{
{Label: "Active", Value: strconv.Itoa(activeBoxes), RawValue: activeBoxes, WidthPercent: percentOf(activeBoxes, maxStatusValue)},
{Label: "Expired", Value: strconv.Itoa(stats.ExpiredBoxes), RawValue: stats.ExpiredBoxes, WidthPercent: percentOf(stats.ExpiredBoxes, maxStatusValue)},
{Label: "Password-protected", Value: strconv.Itoa(stats.ProtectedBoxes), RawValue: stats.ProtectedBoxes, WidthPercent: percentOf(stats.ProtectedBoxes, maxStatusValue)},
{Label: "password protected", Value: strconv.Itoa(stats.ProtectedBoxes), RawValue: stats.ProtectedBoxes, WidthPercent: percentOf(stats.ProtectedBoxes, maxStatusValue)},
}
return adminOverview{
@@ -1934,7 +1934,7 @@ func (a *App) storageConfigFromForm(r *http.Request, provider string) services.S
func adminStorageProviderOptions() []adminStorageProviderView {
return []adminStorageProviderView{
{Provider: services.StorageProviderS3, Label: "S3 Bucket", Description: "Generic S3-compatible object storage.", Icon: "cloud"},
{Provider: services.StorageProviderS3, Label: "S3 Bucket", Description: "Generic S3 compatible object storage.", Icon: "cloud"},
{Provider: services.StorageProviderContabo, Label: "Contabo Object Storage", Description: "Contabo COS with TLS and path-style lookup locked on.", Icon: "cloud"},
{Provider: services.StorageProviderSFTP, Label: "SFTP", Description: "SSH file transfer to a server or NAS.", Icon: "database"},
{Provider: services.StorageProviderSMB, Label: "Samba / SMB", Description: "Windows share or network attached storage.", Icon: "folder"},

View File

@@ -59,7 +59,7 @@ func (a *App) ShareXAnonymousConfig(w http.ResponseWriter, r *http.Request) {
"RequestURL": a.cfg.BaseURL + "/api/v1/upload",
"Headers": map[string]string{
"Accept": "application/json",
// Group a multi-file selection (sent as back-to-back requests) into
// Group a multiple file selection (sent as back to back requests) into
// one box. Remove this header for one box per file.
uploadBatchHeader: "sharex",
},

View File

@@ -127,7 +127,7 @@ func (a *App) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /d/{boxID}/manage/{token}", a.ManageBox)
mux.HandleFunc("POST /d/{boxID}/manage/{token}/delete", a.ManageDeleteBox)
// GET variant so ShareX (which issues a GET to the configured DeletionURL)
// can delete a box via its secret one-time delete token.
// can delete a box via its secret one time delete token.
mux.HandleFunc("GET /d/{boxID}/manage/{token}/delete", a.ManageDeleteBox)
mux.HandleFunc("POST /d/{boxID}/unlock", a.UnlockBox)
mux.HandleFunc("GET /d/{boxID}/zip", a.DownloadZip)

View File

@@ -160,7 +160,7 @@ func (a *App) AccountSettings(w http.ResponseWriter, r *http.Request) {
}
// CreateUserToken mints a new personal access token and renders the account
// page with the one-time plaintext shown. The secret is never recoverable after
// page with the one time plaintext shown. The secret is never recoverable after
// this response.
func (a *App) CreateUserToken(w http.ResponseWriter, r *http.Request) {
user, ok := a.requireUser(w, r)

View File

@@ -162,7 +162,7 @@ func (a *App) DownloadPage(w http.ResponseWriter, r *http.Request) {
pageURL := absoluteURL(r, fmt.Sprintf("/d/%s", box.ID))
// All user uploads are private/temporary noindex by default.
// All user uploads are private/temporary. noindex by default.
robots := web.RobotsNone
a.renderPage(w, r, http.StatusOK, "download.html", web.PageData{

View File

@@ -34,7 +34,7 @@ type mimeRule struct {
icon fileIcon
}
// fileIconSet is the loaded icon map: an extension lookup plus content-type
// fileIconSet is the loaded icon map: an extension lookup plus Content-Type
// rules and a fallback. It is built once at startup from icon-map.json.
type fileIconSet struct {
byExt map[string]fileIcon
@@ -43,7 +43,7 @@ type fileIconSet struct {
}
// loadFileIcons reads static/file-icons/icon-map.json and indexes it by
// extension and content type so icons can be assigned at render time.
// extension and Content-Type so icons can be assigned at render time.
func loadFileIcons(staticDir string) (*fileIconSet, error) {
data, err := os.ReadFile(filepath.Join(staticDir, "file-icons", "icon-map.json"))
if err != nil {
@@ -111,8 +111,8 @@ func validateFileIconPath(staticDir, theme, name string) error {
}
// lookup resolves a file's icon from its name (extension) first, falling back to
// its content type, then to the default icon. Extension wins because stored
// content types are often the generic application/octet-stream.
// its Content-Type, then to the default icon. Extension wins because stored
// Content-Types are often the generic application/octet-stream.
func (s *fileIconSet) lookup(name, contentType string) fileIcon {
if s == nil {
return fileIcon{}

View File

@@ -15,7 +15,7 @@ func (a *App) RobotsTxt(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `User-agent: *
Allow: /
# Private routes do not crawl
# Private routes. do not crawl
Disallow: /admin/
Disallow: /api/
Disallow: /app/

View File

@@ -552,7 +552,7 @@ func collageGrid(n int) (cols, rows int) {
}
// drawCover scales src to completely fill dst, cropping the overflow (centred),
// preserving aspect ratio the CSS object-fit: cover equivalent.
// preserving aspect ratio. the CSS object-fit: cover equivalent.
func drawCover(dst *image.RGBA, cell image.Rectangle, src image.Image) {
b := src.Bounds()
iw, ih := b.Dx(), b.Dy()

View File

@@ -91,7 +91,7 @@ func (a *App) homeExpiryOptions(settings services.UploadPolicySettings, user ser
unlimited = true
case loggedIn:
maxDays = a.settingsService.EffectivePolicyForUser(settings, user).MaxDays
// A negative per-user MaxDays override means unlimited retention.
// A negative per user MaxDays override means unlimited retention.
if maxDays < 0 {
unlimited = true
}

View File

@@ -222,7 +222,7 @@ func (a *App) createOrAppendBox(r *http.Request, user services.User, loggedIn bo
}
// batchBoxMatches guards that a batched append only ever touches a box owned by
// the same uploader (account for logged-in users, creator IP for anonymous).
// the same uploader (account for logged in users, creator IP for anonymous).
func (a *App) batchBoxMatches(box services.Box, user services.User, loggedIn bool, r *http.Request) bool {
if loggedIn {
return box.OwnerID == user.ID

View File

@@ -7,11 +7,11 @@ import (
// uploadGroupWindow is how long after a batched upload a follow-up upload with
// the same X-Warpbox-Batch value (and same account/IP) is folded into the same
// box. ShareX sends a multi-file selection as separate back-to-back requests;
// box. ShareX sends a multiple file selection as separate back to back requests;
// the batch header lets it land them in one box.
const uploadGroupWindow = 20 * time.Second
// uploadBatchHeader is the opt-in request header. Without it, uploads behave
// uploadBatchHeader is the opt in request header. Without it, uploads behave
// exactly as before (one box per request). With it, requests sharing the same
// value (per account/IP) within uploadGroupWindow are grouped into one box.
const uploadBatchHeader = "X-Warpbox-Batch"
@@ -20,7 +20,7 @@ const uploadBatchHeader = "X-Warpbox-Batch"
// can't grow without bound (one key per account/IP + batch value otherwise).
const uploadGroupPruneInterval = 5 * time.Minute
// uploadGrouper tracks the most recent box per batch key so opt-in batched
// uploadGrouper tracks the most recent box per batch key so opt in batched
// uploads land in a single box. Each key has its own lock, which also serialises
// that key's concurrent uploads so they append to the same box instead of racing
// to create several.

View File

@@ -51,17 +51,17 @@ func GenerateThumbnailsForBoxAsync(uploadService *services.UploadService, logger
return
}
if services.BoxHasTrouble(box) {
logger.Warn("thumbnail one-shot skipped trouble box", "source", "thumbnail", "severity", "warn", "code", 4206, "box_id", boxID, "error", services.BoxTroubleReason(box))
logger.Warn("thumbnail one shot skipped trouble box", "source", "thumbnail", "severity", "warn", "code", 4206, "box_id", boxID, "error", services.BoxTroubleReason(box))
return
}
result, err := generateMissingThumbnailsForBox(uploadService, logger, box)
if err != nil {
logger.Warn("thumbnail one-shot job failed", "source", "thumbnail", "severity", "warn", "code", 4205, "box_id", boxID, "error", err.Error())
logger.Warn("thumbnail one shot job failed", "source", "thumbnail", "severity", "warn", "code", 4205, "box_id", boxID, "error", err.Error())
return
}
if result.Generated > 0 || result.Failed > 0 {
logger.Info("thumbnail one-shot job complete", "source", "thumbnail", "severity", "user_activity", "code", 2205, "box_id", boxID, "generated", result.Generated, "failed", result.Failed)
logger.Info("thumbnail one shot job complete", "source", "thumbnail", "severity", "user_activity", "code", 2205, "box_id", boxID, "generated", result.Generated, "failed", result.Failed)
}
}()
}

View File

@@ -120,7 +120,7 @@ type Collection struct {
UpdatedAt time.Time `json:"updatedAt"`
}
// APIToken is a long-lived personal access token. Only the SHA-256 hash of the
// APIToken is a long lived personal access token. Only the SHA-256 hash of the
// secret is stored; the plaintext is shown to the user exactly once at creation.
type APIToken struct {
ID string `json:"id"`
@@ -131,7 +131,7 @@ type APIToken struct {
LastUsedAt *time.Time `json:"lastUsedAt,omitempty"`
}
// APITokenResult carries the one-time plaintext alongside the stored token.
// APITokenResult carries the one time plaintext alongside the stored token.
type APITokenResult struct {
Token APIToken
Plaintext string
@@ -907,7 +907,7 @@ func validateUserPolicy(policy UserPolicy) error {
return fmt.Errorf("active box override must be positive or -1 for unlimited")
}
if policy.ShortWindowRequests != nil && *policy.ShortWindowRequests <= 0 && *policy.ShortWindowRequests != -1 {
return fmt.Errorf("short-window request override must be positive or -1 for unlimited")
return fmt.Errorf("short window request override must be positive or -1 for unlimited")
}
return nil
}

View File

@@ -118,7 +118,7 @@ func TestAPITokenLifecycle(t *testing.T) {
if result.Plaintext == "" || !strings.HasPrefix(result.Plaintext, apiTokenPrefix) {
t.Fatalf("plaintext = %q, want %q prefix", result.Plaintext, apiTokenPrefix)
}
// The secret must never be stored in plaintext only its hash.
// The secret must never be stored in plaintext. only its hash.
if strings.Contains(result.Token.TokenHash, result.Plaintext) || result.Token.TokenHash == result.Plaintext {
t.Fatalf("stored token hash leaks the plaintext secret")
}

View File

@@ -313,7 +313,7 @@ func (s *BanService) Match(ip string, now time.Time) (MatchedBan, bool, error) {
now = now.UTC()
var matched BanRecord
var matchedKey []byte
// Read-only scan first: the common case (no match) only takes a concurrent
// read only scan first: the common case (no match) only takes a concurrent
// read transaction, instead of grabbing the single bbolt write lock on every
// request that flows through the ban middleware.
err := s.db.View(func(tx *bbolt.Tx) error {

View File

@@ -19,7 +19,7 @@ func ClientIPFromContext(r *http.Request) (string, bool) {
}
// ClientIP resolves the effective client IP. When trustedProxies is empty,
// forwarded headers are trusted for easy reverse-proxy/container defaults.
// forwarded headers are trusted for easy reverse proxy/container defaults.
func ClientIP(remoteAddr, forwardedFor, realIP string, trustedProxies []string) string {
remoteIP := IPOnly(remoteAddr)
if len(trustedProxies) == 0 || remoteTrusted(remoteIP, trustedProxies) {

View File

@@ -455,7 +455,7 @@ func (s *SettingsService) validate(settings UploadPolicySettings) error {
return fmt.Errorf("active box limits must be positive")
}
if settings.ShortWindowRequests <= 0 || settings.ShortWindowSeconds <= 0 {
return fmt.Errorf("short-window rate limits must be positive")
return fmt.Errorf("short window rate limits must be positive")
}
if settings.ResumableChunkSizeMB <= 0 {
return fmt.Errorf("resumable chunk size must be positive")

View File

@@ -306,7 +306,7 @@ func (s *UploadService) CreateBoxFromIncomingContext(ctx context.Context, files
var expiresAt time.Time
switch {
case opts.ExpiresInMinutes < 0 || opts.MaxDays < 0:
// "Forever" a date far enough out that the box effectively never
// "Forever". a date far enough out that the box effectively never
// expires. No schema change; CanDownload/cleanup keep working as-is.
expiresAt = now.AddDate(100, 0, 0)
case opts.ExpiresInMinutes > 0:
@@ -361,7 +361,7 @@ func (s *UploadService) CreateBoxFromIncomingContext(ctx context.Context, files
return s.resultForBox(box, deleteToken), nil
}
// AppendFiles adds files to an existing box (used to group a ShareX multi-file
// AppendFiles adds files to an existing box (used to group a ShareX multiple file
// selection into a single box). The box keeps its original expiry, password and
// other settings; only the new files are written.
func (s *UploadService) AppendFiles(boxID string, files []*multipart.FileHeader, opts UploadOptions) (UploadResult, error) {
@@ -1099,7 +1099,7 @@ func (s *UploadService) resultForBox(box Box, deleteToken string) UploadResult {
}
// The box-level thumbnail points at the most recently added file, so a
// per-file ShareX upload previews the file it just sent.
// per file ShareX upload previews the file it just sent.
thumbnailURL := fmt.Sprintf("%s/d/%s/og-image.jpg", s.baseURL, box.ID)
if len(files) > 0 {
thumbnailURL = files[len(files)-1].ThumbnailURL

View File

@@ -18,7 +18,7 @@
--danger: #fb7185;
--radius: 0.875rem;
--shadow: 0 24px 70px rgba(8, 4, 32, 0.6);
--font-sans: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-sans: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans serif;
--header-bg: rgba(11, 11, 22, 0.68);
--body-bg:
radial-gradient(circle at 50% -10%, rgba(139, 92, 246, 0.18), transparent 34rem),
@@ -48,7 +48,7 @@
--danger: #fca5a5;
--radius: 0.625rem;
--shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans serif;
--header-bg: rgba(9, 9, 11, 0.84);
--body-bg:
radial-gradient(circle at 50% -10%, rgba(82, 82, 91, 0.32), transparent 34rem),
@@ -78,7 +78,7 @@
--danger: #fb4934;
--radius: 0.65rem;
--shadow: 0 24px 70px rgba(0, 0, 0, 0.42);
--font-sans: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-sans: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans serif;
--header-bg: rgba(29, 32, 33, 0.86);
--body-bg:
radial-gradient(circle at 20% -8%, rgba(215, 153, 33, 0.2), transparent 28rem),
@@ -109,7 +109,7 @@
--danger: #ff2a6d;
--radius: 0.35rem;
--shadow: 0 24px 70px rgba(255, 42, 109, 0.16), 0 0 34px rgba(0, 240, 255, 0.12);
--font-sans: "Inter", "Rajdhani", "Orbitron", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-sans: "Inter", "Rajdhani", "Orbitron", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans serif;
--header-bg: rgba(8, 7, 13, 0.86);
--body-bg:
radial-gradient(circle at 10% -10%, rgba(255, 242, 0, 0.2), transparent 26rem),
@@ -145,7 +145,7 @@
inset 1px 1px 0 #ffffff,
inset -2px -2px 0 #808080,
inset 2px 2px 0 #dfdfdf;
--font-sans: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans-serif;
--font-sans: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans serif;
--header-bg: #c0c0c0;
--body-bg: #000000;
--surface-1: #ffffff;

View File

@@ -189,7 +189,7 @@ html.warpbox-dialog-open body {
background: #c0c0c0;
color: #000000;
box-shadow: inset -1px -1px 0 #404040, inset 1px 1px 0 #ffffff, inset -2px -2px 0 #808080, inset 2px 2px 0 #dfdfdf, 4px 4px 0 rgba(0, 0, 0, 0.45);
font-family: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans-serif;
font-family: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans serif;
}
:root[data-theme="retro"] .warpbox-dialog-head {

View File

@@ -1,16 +1,16 @@
/*
* "retro" theme flourishes — modelled on danlegt.com.
* "retro" theme flourishes. Modelled on danlegt.com.
*
* Windows 98 chrome over a black pixel-star desktop, PixeloidSans pixel font,
* crisp (non-antialiased, pixelated) rendering. Scoped entirely to
* :root[data-theme="retro"] so it never touches the other themes.
*
* CSP-safe: external stylesheet + self-hosted fonts only (font-src 'self'),
* CSP-safe: external stylesheet + self hosted fonts only (font-src 'self'),
* no inline styles, no remote assets. The starfield is pure CSS so we don't
* depend on img-src for a background gif.
*/
/* Self-hosted pixel fonts (mirrored locally GGBotNet PixeloidSans is free,
/* self hosted pixel fonts (mirrored locally. GGBotNet PixeloidSans is free,
PixelOperator is CC0). ------------------------------------------------- */
@font-face {
font-family: "PixeloidSans";
@@ -50,7 +50,7 @@
image-rendering: pixelated;
}
/* Square everything Win98 had no rounded corners. */
/* Square everything. Win98 had no rounded corners. */
:root[data-theme="retro"] *,
:root[data-theme="retro"] *::before,
:root[data-theme="retro"] *::after {
@@ -736,7 +736,7 @@
align-self: start;
}
:root[data-theme="retro"] .file-type,
:root[data-theme="retro"] .file type,
:root[data-theme="retro"] .file-size,
:root[data-theme="retro"] .file-main small {
color: inherit;

View File

@@ -97,7 +97,7 @@
:root[data-theme="retro"] .warpbox-popups {
inset-block-start: 2.65rem;
font-family: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans-serif;
font-family: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans serif;
}
:root[data-theme="retro"] .warpbox-popup {

View File

@@ -1285,7 +1285,7 @@ html.reaction-picker-open body {
object-fit: contain;
}
/* Retro (Win98) icons are tiny pixel art — keep them crisp and swap them in
/* Retro (Win98) icons are tiny pixel art. Keep them crisp and swap them in
only when the retro theme is active. */
.file-icon-retro {
display: none;
@@ -1318,7 +1318,7 @@ html.reaction-picker-open body {
white-space: nowrap;
}
.file-type,
.file type,
.file-size {
overflow: hidden;
color: var(--muted-foreground);
@@ -1401,7 +1401,7 @@ html.reaction-picker-open body {
padding-top: 0.25rem;
}
.file-browser.is-thumbs .file-type,
.file-browser.is-thumbs .file type,
.file-browser.is-thumbs .file-size {
display: none;
}

View File

@@ -11,7 +11,7 @@
}
/* ============================================================
API documentation sidebar layout
API documentation: sidebar layout
============================================================ */
.api-docs {
@@ -315,6 +315,7 @@
.method-get { background: #2563eb; }
.method-post { background: #16a34a; }
.method-put { background: #d97706; }
.method-delete { background: #dc2626; }
.endpoint-list {
display: flex;

View File

@@ -13,7 +13,7 @@
--md-block-code-fg: #f8fafc;
--md-block-code-border: rgba(248, 250, 252, 0.16);
--md-shadow: rgba(0, 0, 0, 0.28);
--md-font: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--md-font: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans serif;
--md-mono: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
}
@@ -48,7 +48,7 @@
--md-block-code-fg: #f5f5f5;
--md-block-code-border: #808080;
--md-shadow: transparent;
--md-font: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans-serif;
--md-font: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans serif;
--md-mono: "PixelOperatorMono", Consolas, monospace;
}

View File

@@ -160,7 +160,7 @@
grid-template-columns: 3rem minmax(0, 1fr) auto;
}
.file-type {
.file type {
display: none;
}

View File

@@ -6,8 +6,8 @@
* localStorage (no cookie, no server round-trip) and applies site-wide.
*
* CSP note: this is an external /static file, so it is allowed under
* script-src 'self'. We only toggle an attribute / class never inject inline
* <style> which keeps style-src 'self' happy.
* script-src 'self'. We only toggle an attribute / class and never inject inline
* <style>, which keeps style-src 'self' happy.
*/
(function () {
var STORAGE_KEY = "warpbox-theme";

View File

@@ -529,7 +529,7 @@
});
const totalLabel = window.Warpbox.formatBytes(totalSelectedBytes(files));
const message = `You're on a slow or metered connection. You're about to upload ${files.length} file${files.length === 1 ? "" : "s"} (${totalLabel} total) — this could take a while or use up your data plan.`;
const message = `You're on a slow or metered connection. You're about to upload ${files.length} file${files.length === 1 ? "" : "s"} (${totalLabel} total). This could take a while or use up your data plan.`;
return window.Warpbox.confirmDialog(message, {
title: "Slow connection detected",

View File

@@ -916,11 +916,11 @@
function markdownThemeStyle(theme) {
var themes = {
revamp: ["dark", "#0b0b16", "#f5f3ff", "#aaa4d6", "#17142d", "#211b3e", "rgba(168,150,255,0.24)", "#67e8f9", "#a78bfa", "#1b1724", "#0f111a", "#f8fafc", "rgba(248,250,252,0.16)", "rgba(0,0,0,0.28)", "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif", "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace"],
classic: ["dark", "#09090b", "#fafafa", "#a1a1aa", "#18181b", "#27272a", "rgba(255,255,255,0.13)", "#e4e4e7", "#d4d4d8", "#111113", "#09090b", "#fafafa", "rgba(250,250,250,0.15)", "rgba(0,0,0,0.3)", "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif", "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace"],
retro: ["light", "#c0c0c0", "#000000", "#404040", "#ffffff", "#dfdfdf", "#000000", "#000078", "#000078", "#ffffff", "#000000", "#f5f5f5", "#808080", "transparent", "\"PixeloidSans\",\"PixelOperator\",\"Microsoft Sans Serif\",Tahoma,sans-serif", "\"PixelOperatorMono\",Consolas,monospace"],
gruvbox: ["dark", "#1d2021", "#ebdbb2", "#bdae93", "#282828", "#32302f", "rgba(235,219,178,0.2)", "#fabd2f", "#d79921", "#1b1d1e", "#161819", "#fbf1c7", "rgba(251,241,199,0.18)", "rgba(0,0,0,0.26)", "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif", "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace"],
cyberpunk: ["dark", "#08070d", "#fff36f", "#9bfaff", "#16131f", "#251d34", "rgba(255,242,0,0.34)", "#00f0ff", "#ff2a6d", "#100d18", "#07060b", "#f8fafc", "rgba(0,240,255,0.26)", "rgba(255,42,109,0.14)", "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif", "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace"]
revamp: ["dark", "#0b0b16", "#f5f3ff", "#aaa4d6", "#17142d", "#211b3e", "rgba(168,150,255,0.24)", "#67e8f9", "#a78bfa", "#1b1724", "#0f111a", "#f8fafc", "rgba(248,250,252,0.16)", "rgba(0,0,0,0.28)", "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans serif", "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace"],
classic: ["dark", "#09090b", "#fafafa", "#a1a1aa", "#18181b", "#27272a", "rgba(255,255,255,0.13)", "#e4e4e7", "#d4d4d8", "#111113", "#09090b", "#fafafa", "rgba(250,250,250,0.15)", "rgba(0,0,0,0.3)", "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans serif", "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace"],
retro: ["light", "#c0c0c0", "#000000", "#404040", "#ffffff", "#dfdfdf", "#000000", "#000078", "#000078", "#ffffff", "#000000", "#f5f5f5", "#808080", "transparent", "\"PixeloidSans\",\"PixelOperator\",\"Microsoft Sans Serif\",Tahoma,sans serif", "\"PixelOperatorMono\",Consolas,monospace"],
gruvbox: ["dark", "#1d2021", "#ebdbb2", "#bdae93", "#282828", "#32302f", "rgba(235,219,178,0.2)", "#fabd2f", "#d79921", "#1b1d1e", "#161819", "#fbf1c7", "rgba(251,241,199,0.18)", "rgba(0,0,0,0.26)", "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans serif", "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace"],
cyberpunk: ["dark", "#08070d", "#fff36f", "#9bfaff", "#16131f", "#251d34", "rgba(255,242,0,0.34)", "#00f0ff", "#ff2a6d", "#100d18", "#07060b", "#f8fafc", "rgba(0,240,255,0.26)", "rgba(255,42,109,0.14)", "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans serif", "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace"]
};
var t = themes[theme] || themes.revamp;
var vars = "color-scheme:" + t[0] + ";--md-bg:" + t[1] + ";--md-fg:" + t[2] + ";--md-muted:" + t[3] + ";--md-panel:" + t[4] + ";--md-panel-2:" + t[5] + ";--md-border:" + t[6] + ";--md-link:" + t[7] + ";--md-accent:" + t[8] + ";--md-code-bg:" + t[9] + ";--md-block-code-bg:" + t[10] + ";--md-block-code-fg:" + t[11] + ";--md-block-code-border:" + t[12] + ";--md-shadow:" + t[13] + ";--md-font:" + t[14] + ";--md-mono:" + t[15] + ";";

File diff suppressed because one or more lines are too long

View File

@@ -39,7 +39,7 @@
<label><span>New password</span><input type="password" name="new_password" autocomplete="new-password" minlength="8" required></label>
<button class="button button-primary" type="submit">Update password</button>
</form>
<p class="muted-copy">Public forgot-password is deferred until SMTP support is added. Admins can generate reset links.</p>
<p class="muted-copy">Public forgot password is deferred until SMTP support is added. Admins can generate reset links.</p>
</div>
</div>
@@ -56,7 +56,7 @@
{{if .Data.NewToken}}
<div class="token-reveal">
<p class="token-reveal-title">Copy your new token now — it won't be shown again.</p>
<p class="token-reveal-title">Copy your new token now. It won't be shown again.</p>
<div class="token-reveal-row">
<code class="token-reveal-value" data-token-value>{{.Data.NewToken}}</code>
<button class="button button-outline button-sm" type="button" data-token-copy>Copy</button>

View File

@@ -106,7 +106,7 @@
</select>
</label>
</div>
<p class="pagination-summary">Showing {{.Data.RangeFrom}}{{.Data.RangeTo}} of {{.Data.Total}} · Page {{.Data.Page}} of {{.Data.TotalPages}}</p>
<p class="pagination-summary">Showing {{.Data.RangeFrom}}. {{.Data.RangeTo}} of {{.Data.Total}} · Page {{.Data.Page}} of {{.Data.TotalPages}}</p>
</div>
</div>
</div>

View File

@@ -113,7 +113,7 @@
</select>
</label>
</div>
<p class="pagination-summary">Showing {{.Data.Logs.RangeFrom}}{{.Data.Logs.RangeTo}} of {{.Data.Logs.Total}} · Page {{.Data.Logs.Page}} of {{.Data.Logs.TotalPages}}</p>
<p class="pagination-summary">Showing {{.Data.Logs.RangeFrom}}. {{.Data.Logs.RangeTo}} of {{.Data.Logs.Total}} · Page {{.Data.Logs.Page}} of {{.Data.Logs.TotalPages}}</p>
</div>
</div>
</div>

View File

@@ -117,11 +117,11 @@
<input name="local_storage_max_gb" value="{{.Data.Settings.LocalStorageMaxGB}}" required>
</label>
<label>
<span>Short-window requests</span>
<span>short window requests</span>
<input type="number" name="short_window_requests" min="1" value="{{.Data.Settings.ShortWindowRequests}}" required>
</label>
<label>
<span>Short-window seconds</span>
<span>short window seconds</span>
<input type="number" name="short_window_seconds" min="1" value="{{.Data.Settings.ShortWindowSeconds}}" required>
</label>
</div>

View File

@@ -52,7 +52,7 @@
<div class="table-header">
<div>
<h2>Identity and limits</h2>
<p>Blank limit fields inherit the global user defaults. Use <code>-1</code> for unlimited in any limit field upload size, daily caps, storage quota, max expiration (the box can then last forever), daily boxes, active boxes, and short-window requests. Storage quota <code>0</code> also means unlimited.</p>
<p>Blank limit fields inherit the global user defaults. Use <code>-1</code> for unlimited in any limit field, including upload size, daily caps, storage quota, max expiration (the box can then last forever), daily boxes, active boxes, and short window requests. Storage quota <code>0</code> also means unlimited.</p>
</div>
</div>
<form class="settings-form" action="/admin/users/{{.Data.UserEdit.ID}}/edit" method="post">
@@ -92,7 +92,7 @@
<label><span>Max expiration (days)</span><input type="number" min="-1" name="max_days" value="{{.Data.UserEdit.MaxDays}}" placeholder="inherit"></label>
<label><span>Daily boxes</span><input type="number" min="-1" name="daily_boxes" value="{{.Data.UserEdit.DailyBoxes}}" placeholder="inherit"></label>
<label><span>Active boxes</span><input type="number" min="-1" name="active_boxes" value="{{.Data.UserEdit.ActiveBoxes}}" placeholder="inherit"></label>
<label><span>Short-window requests</span><input type="number" min="-1" name="short_window_requests" value="{{.Data.UserEdit.ShortWindowRequests}}" placeholder="inherit"></label>
<label><span>short window requests</span><input type="number" min="-1" name="short_window_requests" value="{{.Data.UserEdit.ShortWindowRequests}}" placeholder="inherit"></label>
</div>
<button class="button button-primary" type="submit">Save user</button>

View File

@@ -152,12 +152,13 @@
<article id="ep-resumable" class="endpoint card">
<div class="card-content">
<h3>Resumable uploads</h3>
<p>For large files. Browser uploads use this by default. Create a session with file metadata, <code>PUT</code> exact-sized chunks, then complete. Chunks are temporary and cleaned if the session expires. Send the same <code>Authorization</code> header on every request for authenticated sessions.</p>
<p>For large files. Browser uploads use this by default. Create a session with file metadata, <code>PUT</code> exact sized chunks, then complete. Chunks are temporary and cleaned if the session expires. Send the same <code>Authorization</code> header on every request for authenticated sessions.</p>
<div class="endpoint-list">
<div><span class="method method-post">POST</span><code>/api/v1/uploads/resumable</code><em>Create a session</em></div>
<div><span class="method method-get">GET</span><code>/api/v1/uploads/resumable/{sessionID}</code><em>Session status</em></div>
<div><span class="method method-put">PUT</span><code>/api/v1/uploads/resumable/{sessionID}/files/{fileID}/chunks/{index}</code><em>Upload one chunk</em></div>
<div><span class="method method-post">POST</span><code>/api/v1/uploads/resumable/{sessionID}/complete</code><em>Finalize (returns the upload JSON)</em></div>
<div><span class="method method-delete">DELETE</span><code>/api/v1/uploads/resumable/{sessionID}</code><em>Cancel and delete an unfinished session</em></div>
</div>
<figure class="code-block">
<pre><code># 1. Create a session.
@@ -173,7 +174,11 @@ dd if=./report.pdf bs=8388608 count=1 skip=0 2>/dev/null | \
# 3. Complete after all chunks are present. The response is the normal upload JSON.
curl -X POST -H 'Accept: application/json' \
{{.Data.BaseURL}}/api/v1/uploads/resumable/SESSION_ID/complete</code></pre>
{{.Data.BaseURL}}/api/v1/uploads/resumable/SESSION_ID/complete
# Optional: cancel an unfinished session.
curl -X DELETE -H 'X-Warpbox-Resume-Token: TOKEN' \
{{.Data.BaseURL}}/api/v1/uploads/resumable/SESSION_ID</code></pre>
</figure>
<p class="muted-copy">Incomplete chunks are stored under <code>data/tmp/uploads</code> before finalizing into the selected storage backend.</p>
</div>
@@ -257,7 +262,7 @@ Add-Content $PROFILE 'function warpbox { &amp; "$HOME\warpbox.ps1" @args }'
<div class="field-grid">
<span><code>-p, --password</code></span><p>Require a password to open the box.</p>
<span><code>-e, --expiry</code></span><p>Lifetime: <code>30m</code>, <code>6h</code>, <code>2d</code>, <code>1w</code> (or bare minutes).</p>
<span><code>-n, --max-downloads</code></span><p>Expire after N downloads.</p>
<span><code>-n, --max downloads</code></span><p>Expire after N downloads.</p>
<span><code>-o, --obfuscate</code></span><p>Hide names/counts until unlock (needs <code>--password</code>).</p>
<span><code>--json</code></span><p>Print the full JSON response instead of just the URL.</p>
<span><code>--host</code></span><p>Server to upload to. Defaults to your <code>WARPBOX_HOST</code>.</p>
@@ -476,11 +481,15 @@ $resp.boxUrl</code></pre>
</details>
<details class="faq-item">
<summary>How do I get file URLs and a delete link back?</summary>
<p>Send <code>Accept: application/json</code>. The response includes <code>boxUrl</code>, per-file <code>url</code>s, and the private <code>manageUrl</code>/<code>deleteUrl</code> (shown only once). See <a href="#responses" data-doc-link>the JSON response</a>.</p>
<p>Send <code>Accept: application/json</code>. The response includes <code>boxUrl</code>, per file <code>url</code>s, and the private <code>manageUrl</code>/<code>deleteUrl</code> (shown only once). See <a href="#responses" data-doc-link>the JSON response</a>.</p>
</details>
<details class="faq-item">
<summary>How do I upload one big file reliably?</summary>
<p>Use the <a href="#ep-resumable" data-doc-link>resumable endpoints</a>: create a session, PUT chunks, then complete. Interrupted uploads can resume from the last chunk.</p>
<p>Use the <a href="#ep-resumable" data-doc-link>resumable endpoints</a>: create a session, PUT chunks, then complete. Interrupted uploads can resume from the last chunk, and unfinished sessions can be deleted with <code>DELETE /api/v1/uploads/resumable/{sessionID}</code>.</p>
</details>
<details class="faq-item">
<summary>Can clients show exact download progress?</summary>
<p>Yes. Individual file downloads and whole box <code>zipUrl</code> downloads include a precise <code>Content-Length</code>; range-capable responses also advertise <code>Accept-Ranges</code>.</p>
</details>
<details class="faq-item">
<summary>Can I upload several files into one shareable link?</summary>

View File

@@ -82,7 +82,7 @@
</details>
</td>
<td>
<div>{{if .CollectionName}}{{.CollectionName}}{{else}}<span class="muted-copy"></span>{{end}}</div>
<div>{{if .CollectionName}}{{.CollectionName}}{{else}}<span class="muted-copy">. </span>{{end}}</div>
<details class="row-edit">
<summary>Move</summary>
<form action="/app/boxes/{{.ID}}/move" method="post" class="row-edit-form">

View File

@@ -111,7 +111,7 @@
<small>{{.Size}} · {{if .Failed}}Failed{{else if .Processing}}Processing{{else}}{{.ContentType}}{{end}}</small>
{{if .Failed}}<small class="file-error">{{.Error}}</small>{{end}}
</span>
<span class="file-type">{{if .Failed}}Failed{{else if .Processing}}Processing{{else}}{{.ContentType}}{{end}}</span>
<span class="file type">{{if .Failed}}Failed{{else if .Processing}}Processing{{else}}{{.ContentType}}{{end}}</span>
<span class="file-size">{{.Size}}</span>
{{if or .Processing .Failed}}</div>{{else}}</a>{{end}}
{{if not $.Data.Locked}}