diff -NarU2 a/contrib/plugins/graft-thumbs.js b/contrib/plugins/graft-thumbs.js --- a/contrib/plugins/graft-thumbs.js 2026-07-08 15:13:32.813795232 +0000 +++ b/contrib/plugins/graft-thumbs.js 2026-07-08 15:11:40.605178933 +0000 @@ -83,13 +83,13 @@ // ...and then the trick! near the end of loadgrid, - // thegrid.bagit is called to initialize the baguettebox + // msel.bagit is called to initialize the baguettebox // (image/video gallery); this is the perfect function to // "hook" (hijack) so we can run our code :^) // need to grab a backup of the original function first, - var orig_func = thegrid.bagit; + var orig_func = msel.bagit; // and then replace it with our own: - thegrid.bagit = function (isrc) { + msel.bagit = function (isrc) { if (isrc !== '#ggrid') @@ -105,5 +105,5 @@ setTimeout(graft_thumbs, 1); - // and finally, call the original thegrid.bagit function + // and finally, call the original msel.bagit function return orig_func(isrc); }; diff -NarU2 a/contrib/plugins/minimal-up2k.html b/contrib/plugins/minimal-up2k.html --- a/contrib/plugins/minimal-up2k.html 2026-07-08 15:13:32.813795232 +0000 +++ b/contrib/plugins/minimal-up2k.html 2026-05-22 21:41:25.288307384 +0000 @@ -19,5 +19,5 @@ #ops, #tree, #path, #wfp, /* main tabs and navigators (tree/breadcrumbs) */ - #u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */ + #u2conf tr:first-child>td[rowspan], /* most of the config options */ #srch_dz, #srch_zd, /* the filesearch dropzone */ @@ -31,5 +31,4 @@ /* add some margins because now it's weird */ .opview {margin-top: 2.5em} - #op_up2k {margin-top: 6em} /* and embiggen the upload button */ @@ -37,8 +36,5 @@ /* adjust the button area a bit */ - #u2conf.w, #u2conf.ww {width: 35em !important; margin: 5em auto} - - /* a */ - #op_up2k {min-height: 0} + #u2conf.w {width: 35em !important; margin: 5em auto} diff -NarU2 a/contrib/plugins/minimal-up2k.js b/contrib/plugins/minimal-up2k.js --- a/contrib/plugins/minimal-up2k.js 2026-07-08 15:13:32.813795232 +0000 +++ b/contrib/plugins/minimal-up2k.js 2026-07-08 15:11:40.605178933 +0000 @@ -40,18 +40,4 @@ } #u2conf {margin:5em auto 0 auto !important} -#u2conf.ww {width:70em} -#u2conf.w {width:50em} -#u2conf.w .c, -#u2conf.w #u2btn_cw {text-align:left} -#u2conf.w #u2btn_cw {width:70%} -#u2etaw {margin:3em auto} -#u2etaw.w { - text-align: center; - margin: -3.5em auto 5em auto; -} -#u2etaw.w #u2etas {margin-right:-37em} -#u2etaw.w #u2etas.o {margin-top:-2.2em} -#u2etaw.ww {margin:-1em auto} -#u2etaw.ww #u2etas {padding-left:4em} #u2etas { background: none !important; diff -NarU2 a/contrib/plugins/rave.js b/contrib/plugins/rave.js --- a/contrib/plugins/rave.js 2026-07-08 15:13:32.813795232 +0000 +++ b/contrib/plugins/rave.js 2026-05-22 21:41:25.288307384 +0000 @@ -120,5 +120,5 @@ document.body.style.marginLeft = hrand() * uofs + 'px'; ebi('tree').style.marginLeft = hrand() * uofs + 'px'; - for (var a of QSA('#ops>a, #path>a, #pctl>a')) + for (var a of QSA('#ops>a, #path>a, .pctl>a')) a.style.transform = 'translate(' + hrand() * uofs * 1 + 'px, ' + hrand() * uofs * 0.7 + 'px) rotate(' + Math.random() * uofs * 0.7 + 'deg)' } diff -NarU2 a/copyparty/authsrv.py b/copyparty/authsrv.py --- a/copyparty/authsrv.py 2026-07-28 23:16:17.534485289 +0000 +++ b/copyparty/authsrv.py 2026-07-28 23:14:14.881200160 +0000 @@ -3260,4 +3260,10 @@ continue # propagates later in this func vf = vn.flags + if "theme" in vf: + if re.match(r'[0-9]', vf["theme"]): + zs = int(vf["theme"]) + bri = "zy"[zs % 2 :][:1] + ch = "abcdefghijklmnopqrstuvwx"[int(zs / 2)] + vf["theme"] = "{0}{1} {0} {1}".format(ch, bri) vn.js_ls = { "idx": "e2d" in vf, @@ -3278,4 +3284,7 @@ "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"), "rw_edit": vf["rw_edit"], + "dtheme": vf["theme"], + "bg_img": vf["bg_img"], + "tcolor": vf["tcolor"], } if "ufavico_h" in vf: @@ -3304,4 +3313,7 @@ "sba_lg": vf.get("lg_sba") or "", "rw_edit": vf["rw_edit"], + "dtheme": vf["theme"], + "bg_img": vf["bg_img"], + "tcolor": vf["tcolor"], "txt_ext": self.args.textfiles.replace(",", " "), "def_hcols": list(vf.get("mth") or []), diff -NarU2 a/copyparty/cfg.py b/copyparty/cfg.py --- a/copyparty/cfg.py 2026-07-28 23:16:17.534485289 +0000 +++ b/copyparty/cfg.py 2026-07-28 23:14:14.881707228 +0000 @@ -104,4 +104,5 @@ for k in ( "apnd_who", + "bg_img", "bup_ck", "cachectl", @@ -114,4 +115,5 @@ "db_xattr", "du_who", + "theme", "epilogues", "ufavico", @@ -307,6 +309,6 @@ }, 'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': { - "mte=artist,title": "media-tags to index/display", - "mth=fmt,res,ac": "media-tags to hide by default", + "mte=artist,title": "media-tags/file-columns to index/display", + "mth=fmt,res,ac": "media-tags/file-columns to hide by default", "mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)', "mtp=ahash,vhash=media-hash.py": "collects two tags at once", @@ -349,4 +351,6 @@ "client and ux": { "grid": "show grid/thumbnails by default", + "bg_img": "set a background image", + "theme": "set a default theme", "gsel": "select files in grid by ctrl-click", "sort": "default sort order", diff -NarU2 a/copyparty/httpcli.py b/copyparty/httpcli.py --- a/copyparty/httpcli.py 2026-07-28 23:16:17.536485324 +0000 +++ b/copyparty/httpcli.py 2026-07-28 23:14:14.882573726 +0000 @@ -367,4 +367,5 @@ self.out_headers = { "Cache-Control": "no-store, max-age=0", + "Service-Worker-Allowed": "/", } @@ -1553,4 +1554,7 @@ return self.tx_svcs() + if "config" in self.uparam: + return self.tx_config() + if "shares" in self.uparam: return self.tx_shares() @@ -5537,5 +5541,5 @@ # so url-param "raster" returns a png/webp instead # (useragent-sniffing kinshi due to caching proxies) - mime, ico = self.conn.hsrv.ico.get(txt, not small, "raster" in self.uparam) + mime, ico = self.conn.hsrv.ico.get(txt, not small, "raster" in self.uparam, self.uparam.get('a')) lm = formatdate(self.E.t0) @@ -5684,4 +5688,15 @@ return True + def tx_config(self) -> bool: + html = self.j2s( + "config", + this=self, + accs=bool(self.asrv.acct), + s="s" if self.is_https else "", + bg_img=self.args.bg_img, + ) + self.reply(html.encode("utf-8")) + return True + def tx_svcs(self) -> bool: aname = re.sub("[^0-9a-zA-Z]+", "", self.args.vname) or "a" @@ -6897,5 +6912,5 @@ "lead": "", "href": "%s/" % (x,), - "ext": "---", + "ext": "", "sz": 0, "ts": 0, @@ -7107,5 +7122,5 @@ if is_dir: - return self.tx_svg("folder") + return self.reply(b"(no thumbnail)") thp = None @@ -7127,5 +7142,5 @@ raise Pebkac(415) - return self.tx_ico(rem) + return self.reply(b"(no thumbnail)") elif self.can_write and th_fmt is not None: @@ -7231,4 +7246,5 @@ zi = vn.flags["du_iwho"] + volspace = [] if zi and ( zi == 9 @@ -7255,7 +7271,10 @@ except: pass - h1 = humansize(free or 0) + if not free: + free = 0 + h1 = humansize(free) h2 = humansize(total) srv_info.append("{} free of {}".format(h1, h2)) + volspace = [h1, h2, int((total - free) / total * 10000)] elif zs: self.log("diskfree(%r): %s" % (abspath, zs), 3) @@ -7308,4 +7327,5 @@ "taglist": [], "srvinf": srv_infot, + "volspace": volspace, "acct": self.uname, "perms": perms, @@ -7316,4 +7336,5 @@ "acct": self.uname, "perms": perms, + "volspace": volspace, } # also see `js_htm` in authsrv.py @@ -7331,5 +7352,8 @@ "title": html_escape("%s %s" % (self.args.bname, self.vpath), crlf=True), "srv_info": srv_infot, + "srv_name": "" if self.args.nih else self.args.name, + "srv_url": self.args.name_url if self.args.name_url else "/", "dtheme": self.args.theme, + "unfun": self.args.unfun, } @@ -7476,10 +7500,10 @@ elif add_dk: zs = absreal(fspath) - margin = 'zip' % ( + margin = 'zip' % ( quotep(href), self.gen_fk(2, self.args.dk_salt, zs, 0, 0)[:add_dk], ) else: - margin = 'zip' % ( + margin = 'zip' % ( quotep(href), ) @@ -7505,5 +7529,5 @@ if is_dir: - ext = "---" + ext = "" elif "." in fn: ext = ptn_hr.sub("@", fn.rsplit(".", 1)[1]) diff -NarU2 a/copyparty/httpsrv.py b/copyparty/httpsrv.py --- a/copyparty/httpsrv.py 2026-07-28 23:16:17.536485324 +0000 +++ b/copyparty/httpsrv.py 2026-07-28 23:14:14.882763044 +0000 @@ -190,4 +190,5 @@ "browser2", "cf", + "config", "idp", "md", diff -NarU2 a/copyparty/ico.py b/copyparty/ico.py --- a/copyparty/ico.py 2026-07-28 23:16:17.536485324 +0000 +++ b/copyparty/ico.py 2026-07-28 23:14:14.882839870 +0000 @@ -12,22 +12,17 @@ +RE_CSS_COLOR = re.compile(r"[^a-zA-Z0-9#()%.,/-]") + + class Ico(object): def __init__(self, args: argparse.Namespace) -> None: self.args = args - def get(self, ext: str, as_thumb: bool, chrome: bool) -> tuple[str, bytes]: + def get(self, ext: str, as_thumb: bool, png: bool, accent: str) -> tuple[str, bytes]: """placeholder to make thumbnails not break""" bext = ext.encode("ascii", "replace") ext = bext.decode("utf-8") - zb = hashlib.sha1(bext).digest()[2:4] - if PY2: - zb = [ord(x) for x in zb] # type: ignore - - c1 = colorsys.hsv_to_rgb(zb[0] / 256.0, 1, 0.3) - c2 = colorsys.hsv_to_rgb(zb[0] / 256.0, 0.8 if HAVE_PILF else 1, 1) - ci = [int(x * 255) for x in list(c1) + list(c2)] - c = "".join(["%02x" % (x,) for x in ci]) - + w = 100 h = 30 @@ -36,5 +31,11 @@ h = int(100.0 / (float(sw) / float(sh))) - if chrome: + if accent: + accent = RE_CSS_COLOR.sub("", accent) + + # obsolete. 2000+ svgs slows chrome down massively, but no crash. + # further, unique svgs are only used for non-standard thumbnails, + # so it's unrealistic to see that many in use + if png & 0: # cannot handle more than ~2000 unique SVGs if HAVE_PILF: @@ -49,9 +50,9 @@ h = int(128.0 * h / w) w = 128 - img = Image.new("RGB", (w, h), "#" + c[:6]) + img = Image.new("RGBA", (w, h), "#00000000") pb = ImageDraw.Draw(img) _, _, tw, th = pb.textbbox((0, 0), ext2, font_size=16) xy = (int((w - tw) / 2), int((h - th) / 2)) - pb.text(xy, ext2, fill="#" + c[6:], font_size=16) + pb.text(xy, ext2, fill=accent, font_size=16) img = img.resize((w * 2, h * 2), Image.NEAREST) @@ -70,5 +71,5 @@ h = int(64.0 * h / w) w = 64 - img = Image.new("RGB", (w, h), "#" + c[:6]) + img = Image.new("RGBA", (w, h), "#00000000") pb = ImageDraw.Draw(img) try: @@ -80,5 +81,5 @@ cw = tw // len(ext) x = ((w - tw) // 2) - (cw * 2) // 3 - fill = "#" + c[6:] + fill = accent for ch in ext: pb.text((x, (h - th) // 2), " %s " % (ch,), fill=fill) @@ -94,7 +95,7 @@ - + {} + fill="{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{} """ @@ -110,5 +111,5 @@ y = "50%" - svg = svg.format(h, c[:6], y, c[6:], txt) + svg = svg.format(h, y, accent or '#000', txt) return "image/svg+xml", svg.encode("utf-8") diff -NarU2 a/copyparty/__init__.py b/copyparty/__init__.py --- a/copyparty/__init__.py 2026-07-28 23:16:17.533485272 +0000 +++ b/copyparty/__init__.py 2026-07-28 23:14:14.880319846 +0000 @@ -73,5 +73,9 @@ web/browser2.html web/cf.html +web/config.css +web/config.html +web/config.js web/copyparty.gif +web/copyparty.svg web/deps/busy.mp3 web/deps/easymde.css @@ -90,4 +94,5 @@ web/idp.html web/iiam.gif +web/manifest.json web/md.css web/md.html @@ -111,4 +116,5 @@ web/svcs.html web/svcs.js +web/sw.js web/tl/chi.js web/tl/cze.js diff -NarU2 a/copyparty/__main__.py b/copyparty/__main__.py --- a/copyparty/__main__.py 2026-07-28 23:16:17.533485272 +0000 +++ b/copyparty/__main__.py 2026-07-28 23:14:14.880755109 +0000 @@ -1922,6 +1922,6 @@ ap2.add_argument("--db-xattr", metavar="t,t", type=u, default="", help="read file xattrs as metadata tags; [\033[32ma,b\033[0m] reads keys \033[33ma\033[0m and \033[33mb\033[0m as tags \033[33ma\033[0m and \033[33mb\033[0m, [\033[32ma=foo,b=bar\033[0m] reads keys \033[33ma\033[0m and \033[33mb\033[0m as tags \033[33mfoo\033[0m and \033[33mbar\033[0m, [\033[32m~~a,b\033[0m] does everything except \033[33ma\033[0m and \033[33mb\033[0m, [\033[32m~~\033[0m] does everything. NOTE: Each tag must also be enabled with \033[33m-mte\033[0m (volflag=db_xattr)") ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add/replace metadata mapping") - ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE) - ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as \033[33m-mte\033[0m", default=DEF_MTH) + ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags/file-columns to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE) + ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags/file-columns to hide by default (comma-sep.); assign/add/remove same as \033[33m-mte\033[0m", default=DEF_MTH) ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m read tag \033[33mM\033[0m using program \033[33mBIN\033[0m to parse the file") ap2.add_argument("--have-db-xattr", action="store_true", help=argparse.SUPPRESS) @@ -1963,15 +1963,16 @@ def add_ui(ap, retry: int): - THEMES = 10 + THEMES = 12 ap2 = ap.add_argument_group("ui options") ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)") - ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)") + ap2.add_argument("--bg-img", metavar="M,M,M", type=u, default="", help="URL to .jpg/png/gif/svg/webm/mp4/mkv/m4v/mov file to use as background, for example \033[32m/folder/image.jpg,opacity=0.5(optional),blur=5(optional)\033[0m supports relative paths. (volflag=bg_img)") + ap2.add_argument("--no-gsel", action="store_true", help="ctrl-click in gridview will open-in-new-tab instead of select-file (volflag=-gsel)") ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC") - ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="1", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, 6, 6c, 7, 7c, fuzzy (see UI)") + ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="6", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, 6, 6c, 7, 7c, fuzzy (see UI)") ap2.add_argument("--gauto", metavar="PERCENT", type=int, default=0, help="switch to gridview if more than \033[33mPERCENT\033[0m of files are pics/vids; 0=disabled") ap2.add_argument("--rcm", metavar="TXT", default="yy", help="rightclick-menu; two yes/no options: 1st y/n is enable-custom-menu, 2nd y/n is enable-double") ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...") ap2.add_argument("--glang", action="store_true", help="guess the browser's default language, otherwise fall back to \033[33m--lang\033[0m") - ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d)" % (THEMES - 1,)) + ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d) (volflag=theme)" % (THEMES - 1,)) ap2.add_argument("--themes", metavar="NUM", type=int, default=THEMES, help="number of themes installed") ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent") @@ -1981,5 +1982,5 @@ ap2.add_argument("--see-dots", action="store_true", help="default-enable seeing dotfiles; only takes effect if user has the necessary permissions") ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)") - ap2.add_argument("--dlni", action="store_true", help="force download (don't show inline) when files are clicked (volflag:dlni)") + ap2.add_argument("--dlni", action="store_true", help="force download (don't show inline) when files are clicked (volflag=dlni)") ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)") ap2.add_argument("--dothidden", action="store_true", help="hide specific files in a folder by listing them in a file named .hidden -- WARNING: Mostly cosmetic! Download-as-zip/tar will still download them. Do not rely on this for security (volflag=dothidden)") @@ -2029,4 +2030,6 @@ ap2.add_argument("--ui-norepl", action="store_true", help="hide repl-button in the UI (volflag=ui_norepl)") ap2.add_argument("--have-unlistc", action="store_true", help=argparse.SUPPRESS) + ap2.add_argument("--gsel", action="store_true", default=True, help=argparse.SUPPRESS) + ap2.add_argument("--unfun", action="store_true", help="hides many of the emojis in the UI") diff -NarU2 a/copyparty/util.py b/copyparty/util.py --- a/copyparty/util.py 2026-07-28 23:16:17.539485376 +0000 +++ b/copyparty/util.py 2026-07-28 23:14:14.885165997 +0000 @@ -471,5 +471,8 @@ -UNPLICATIONS = [["no_dav", "daw"]] +UNPLICATIONS = [ + ["no_dav", "daw"], + ["no_gsel", "gsel"], +] diff -NarU2 a/copyparty/web/baguettebox.js b/copyparty/web/baguettebox.js --- a/copyparty/web/baguettebox.js 2026-07-08 15:13:32.819482011 +0000 +++ b/copyparty/web/baguettebox.js 2026-07-28 23:09:10.621788298 +0000 @@ -29,5 +29,5 @@ readDirRtl: false, }, - overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnZoom, btnVmode, btnReadDir, btnGoPage, btnClose, + overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnZoom, btnPixelated, btnVmode, btnReadDir, btnGoPage, btnClose, currentGallery = [], currentIndex = 0, @@ -38,6 +38,6 @@ scrollTimer = 0, re_i = APPLE ? - /^[^?]+\.(a?png|avif|bmp|gif|hei[cf]s?|jfif|jpe?g|jxl|svg|tiff?|webp)(\?|$)/i : - /^[^?]+\.(a?png|avif|bmp|gif|jfif|jpe?g|jxl|svg|tiff?|webp)(\?|$)/i, + /^[^?]+\.(a?png|avif|bmp|gif|hei[cf]s?|jfif|jpe?g|jxl|svg|ico|tiff?|webp)(\?|$)/i : + /^[^?]+\.(a?png|avif|bmp|gif|jfif|jpe?g|jxl|svg|ico|tiff?|webp)(\?|$)/i, re_v = /^[^?]+\.(webm|mkv|mp4|m4v|mov)(\?|$)/i, re_cbz = /^[^?]+\.(cbz)(\?|$)/i, @@ -154,4 +154,7 @@ } + var userAction = false; + function getUserAction(){return userAction} + function resetUserAction(){userAction = false} function bindImageClickListeners(selector, userOptions) { var galleryNodeList = QSA(selector); @@ -185,4 +188,10 @@ e.preventDefault ? e.preventDefault() : e.returnValue = false; + var newHash = '#g' + this.getAttribute('id') + getsort(); + if(location.hash != newHash){ + console.log("opened bbox manually"); + userAction = true; + hist_push(newHash); + } prepareOverlay(gallery, userOptions); showOverlay(imageIndex); @@ -318,20 +327,21 @@ var ctr = mknod('div'); ctr.innerHTML = ( - '\n\n' + '
\n' + @@ -949,4 +1122,28 @@ }; +ebi('h_up2k').onclick = function(){ + ebi('up_info').scrollTop = + ebi('op_up2k').scrollTop = + ebi('up_hor').scrollTop = + ebi('up_content').scrollTop = + 0; +} + +ebi('cl_up').onclick = +ebi('up_outside').onclick = + function () { + hist_push('#') + modaltoggle('up2k', false); + } + +var up2k_lgcy = false; +ebi('reloc_up').onclick = function(){ + up2k_lgcy = !up2k_lgcy; + this.innerHTML = up2k_lgcy ? '' : ''; + clmod(ebi('up2k'), 'unmodal', up2k_lgcy); + swrite('up2k_lgcy', up2k_lgcy); +} +if(sread('up2k_lgcy') == 'true') + ebi('reloc_up').click(); ebi('wrap').insertBefore(mknod('div', 'lazy'), ebi('epi')); @@ -956,11 +1153,25 @@ ''), x); +var bup = ebi('op_bup'); +ebi('bup_tgl').appendChild(bup); +ebi('h_bup').onclick = function() { + var open = !clgot(bup, 'act'); + if(open){ + location.hash = '#h_bup'; + modaltoggle('bup', true); + } + else{ + location.hash = '#h_up2k'; + modaltoggle('up2k', true); + } + ebi('bup_tgl').open = !open; +}; (function () { var o = mknod('div'); o.innerHTML = ( - '
\n' + - '
🚀 ' + L.udt_up + '
🚀' + L.udt_up + '
' + L.udt_up + '🚀
\n' + - '
🔎 ' + L.udt_srch + '
🔎' + L.udt_srch + '
' + L.udt_srch + '🔎
\n' + + '
\n' + + ' ' + L.ul_par + '\n' + + ' \n' + + ' \n' + + '
\n' + + ' + \n' + + '
\n' + + ' \n' + + ' \n' + + + '
\n' + + ' \n' + + ' \n' + + '
\n' + + + '
\n' + + ' \n' + + ' \n' + + '
\n' + + + '
\n' + + ' \n' + + ' \n' + + '
\n' + ' \n' + '\n' + - '
\n' + - '

' + L.cl_favico + ' 🎉

\n' + + + '
\n' + + '

🎈 ' + L.cl_opts + '

\n' + '
\n' + - ' ' + - ' ' + - ' ' + - ' \n' + + ' 💭\n' + + ' ' + + ' 💤\n' + + ' az\n' + + ' 🔔\n' + + ' 🔊\n' + '
\n' + '
\n' + + '
\n' + - '

' + L.cl_bigdir + '

\n' + + '

📁 ' + L.cl_bigdir + '

\n' + '
\n' + ' ' + - ' ask\n' + + ' ask\n' + ' \n' + '
\n' + '
\n' + '
\n' + - '

' + L.cl_hsort + '

\n' + + '

🔃 ' + L.cl_hsort + '

\n' + '
\n' + ' ' + @@ -1074,31 +1402,128 @@ '
\n' + '
\n' + - '

' + L.cl_keytype + '

\n' + - (!MOBILE ? '

' + L.cl_rcm + '

' + L.cl_hidec + ' / ' : '') + '' + L.cl_reset + '
' + musicSettings + + '

🎼 ' + L.cl_keytype + '

\n' ); +// modalize settings +(function () { + ebi('h_cfg').innerHTML = (fun_tgl ? '⚙️ ' : '') + L.ot_cfg; + var sections = ebi('op_cfg').children; + for (var i = 0; i < sections.length; i++){ + var h = sections[i].children[0]; + var sName = h.innerHTML; + if(!fun_tgl){ + if(sName && sName.match(' ') && sName[0] != '<') + sName = sName.split(' ').slice(1).join(' '); + } + var sId = h.id; + h.id = '' + var subSettings = sections[i].children[1]; + var section = '

' + sName + '

'; + if(subSettings != null){ + for (var ii = 0; ii < subSettings.children.length; ii++){ + var s = subSettings.children[ii]; + var info = tt.parse(s.getAttribute('tt')); + s.removeAttribute('tt'); + section += '
' + + s.outerHTML + + ((info != null && info.length > 0) ? '

' + info + '

' : '') + + '
'; + } + subSettings.innerHTML = ''; + } + section += '
' + ebi('s_list').innerHTML += section; + ebi('s_nav').innerHTML += '' + sName + '\n'; + } + + ebi('h_cfg').onclick = function(){ + ebi('s_list').scrollTop = + ebi('s_hor').scrollTop = + ebi('s_nav').scrollTop = + ebi('s_content').scrollTop = + 0; + } + + ebi('cl_cfg').onclick = + ebi('s_outside').onclick = + function () { + hist_push('#') + modaltoggle('cfg'); + } +})(); + +var cfg_lgcy = false; +ebi('reloc_cfg').onclick = function(){ + cfg_lgcy = !cfg_lgcy; + this.innerHTML = cfg_lgcy ? '' : ''; + clmod(ebi('cfg'), 'unmodal', cfg_lgcy); + swrite('cfg_lgcy', cfg_lgcy); +} +if(sread('cfg_lgcy') == 'true') + ebi('reloc_cfg').click(); + +// accent color +ebi('accent').oninput = ebi('accent_picker').oninput = function () { + var validcolor = parseColor(this.value); + console.log(this.value); + + if(this == ebi('accent')){ + ebi('accent_picker').value = this.value; + } + else if(this == ebi('accent_picker')){ + if(this.value == '#000000') + return; // firefox submits the color picker value on browser startup + ebi('accent').value = this.value; + } + + clmod(ebi('accent'), 'invalid', this.value.length != 0 && validcolor.length == 0); + if(validcolor == accent) + return; + + setTimeout(function(){ + setColor(validcolor); + }, 100); +} +if(accent && accent.length > 3){ + ebi('accent').value = ebi('accent_picker').value = accent; +} + +// corner radius +ebi('radius').oninput = function () { + var r = parseFloat(this.value); + + clmod(ebi('radius'), 'invalid', this.value.length != 0 && r.length == 0); + if(r === radius) + return; + + var usefallback = isNaN(r) || r < 0 || r === ''; + var setV = usefallback ? '' : (r + 'px'); + swrite('radius', !usefallback ? r : ''); + document.documentElement.style.setProperty('--radius', setV); + console.log(setV); +} +var radius = sread('radius'); +if(radius){ + console.log('read radius from settings: ' + radius); + document.documentElement.style.setProperty('--radius', parseFloat(radius) + 'px'); + ebi('radius').value = radius; +} + + +// no fun allowed +bcfg_bind(this, 'fun_tgl', 'fun_tgl', fun_tgl); // navpane -ebi('tree').innerHTML = ( - '
\n' + - ' 🍞...\n' + - ' +\n' + - ' \n' + - ' 🎯\n' + - ' 📃\n' + - ' 📌\n' + - ' a\n' + - ' \n' + - ' 👀\n' + - '
\n' + - '
    \n' + - '
      \n' + - '
        \n' + - '
         
        ' +ebi('treeh').innerHTML += ( + '\n' + + '+\n' + + '🎯\n' + + '📃\n' + + '📌\n' + + 'a\n' + + '\n' + + '👀\n' ); -clmod(ebi('tree'), 'sbar', 1); -ebi('entree').setAttribute('tt', L.tt_entree); -ebi('goh').textContent = L.goh; QS('#op_mkdir input[type="submit"]').value = L.ab_mkdir; QS('#op_new_md input[type="submit"]').value = L.ab_mkdoc; @@ -1107,29 +1532,29 @@ // right-click menu ebi('rcm').innerHTML = ( - '' + L.rc_opn + '' + - '' + L.rc_ply + '' + - '' + L.rc_pla + '' + - '' + L.rc_txt + '' + - '' + L.rc_md + '' + + '' + (fun_tgl ? '↗️ ' : '') + L.rc_opn + '' + + '' + (fun_tgl ? '▶ ' : '') + L.rc_ply + '' + + '' + (fun_tgl ? '🎧 ' : '') + L.rc_pla + '' + + '' + (fun_tgl ? '📄 ' : '') + L.rc_txt + '' + + '' + (fun_tgl ? '📜 ' : '') + L.rc_md + '' + '
        ' + - '' + L.rc_cpl + '' + - '' + L.rc_dl + '' + + '' + (fun_tgl ? '🔗 ' : '') + L.rc_cpl + '' + + '' + (fun_tgl ? '⬇️ ' : '') + L.rc_dl + '' + (have_zip ? - '' + L.rc_zip + '' + '' + (fun_tgl ? '📦 ' : '') + L.rc_zip + '' : '') + '
        ' + - (have_del ? '' + L.rc_del + '' : '') + - (have_mv ? '' + L.rc_cut + '' : '') + - '' + L.rc_cpy + '' + + (have_del ? '' + (fun_tgl ? '🗑️ ' : '') + L.rc_del + '' : '') + + (have_mv ? '' + (fun_tgl ? '✂️ ' : '') + L.rc_cut + '' : '') + + '' + (fun_tgl ? '⧉ ' : '') + L.rc_cpy + '' + (has(perms, "write") ? - '' + L.rc_pst + '' + - (have_mv ? '' + L.rc_rnm + '' : '') + + '' + (fun_tgl ? '📋 ' : '') + L.rc_pst + '' + + (have_mv ? '' + (fun_tgl ? '✏️ ' : '') + L.rc_rnm + '' : '') + '
        ' + - '' + L.rc_nfo + '' + - '' + L.rc_nfi + '' + '' + (fun_tgl ? '📁 ' : '') + L.rc_nfo + '' + + '' + (fun_tgl ? '📄 ' : '') + L.rc_nfi + '' : '') + '
        ' + - '' + L.rc_sal + '' + - '' + L.rc_sin + '' + + '' + (fun_tgl ? '✅ ' : '') + L.rc_sal + '' + + '' + (fun_tgl ? '🔛 ' : '') + L.rc_sin + '' + '' ); @@ -1143,7 +1568,43 @@ ops[a].href = '#v=' + v; } + + ebi('op_acc').onclick = function (e){ + e.stopPropagation(); + }; })(); +// fallback in case href change method somehow doesn't work +var closebtns = QSA('.close'); +for(var a = 0; a < closebtns.length; a++){ + closebtns[a].onclick = function(){ + var modal = QS('.modal.vis'); + if(modal) + modaltoggle(modal.id, false); + } +} + +function modaltoggle(dest, show){ + if(show == null) + show = 't'; + if (show == false || show == 't' && QS('#' + dest + '.vis')) + dest = ''; + + var layoutchange = dest == 'music' || QS('#music.vis'); + + swrite('opmode', dest || null); + + goto(dest); + + var input = QS('.opview.act input:not([type="hidden"])') + if (input && !TOUCH) { + tt.skip = true; + input.focus(); + } + if(layoutchange) { + onwidgetresize(); + mpl.set_mu_cv(); + } +} function opclick(e) { var dest = this.getAttribute('data-dest'); @@ -1151,5 +1612,6 @@ dest = ''; - swrite('opmode', dest || null); + if(dest != 'acc') + swrite('opmode', dest || null); if (ctrl(e)) return; @@ -1159,4 +1621,6 @@ var input = QS('.opview.act input:not([type="hidden"])') + if(dest == 'search') + input = ebi('folder_search'); if (input && !TOUCH) { tt.skip = true; @@ -1175,7 +1639,11 @@ clmod(obj[a], 'act'); + obj = QSA('.modal'); + for (var a = obj.length - 1; a >= 0; a--) + clmod(obj[a], 'vis'); + if (dest) { var lnk = QS('#ops>a[data-dest=' + dest + ']'), - nps = lnk.getAttribute('data-perm'); + nps = lnk && lnk.getAttribute('data-perm'); nps = nps && nps.length ? nps.split(' ') : []; @@ -1189,13 +1657,39 @@ return; - clmod(ebi('op_' + dest), 'act', 1); + var modal = ebi(dest); + if(modal != null){ + clmod(modal, 'vis', true); + if(location.hash.length <= 1) + location.hash = '#h_' + dest; + } + + var opd = ebi('op_' + dest); + var page = opd != null ? opd : QS('#' + dest + ' .opview') + if(page != null) + clmod(page, 'act', 1); clmod(lnk, 'act', 1); + if(dest == 'bup'){ + clmod(ebi('up2k'), 'vis', true); + location.hash = '#h_bup'; + } + if(dest == 'bup' || dest == 'up2k') + ebi('bup_tgl').open = dest == 'bup'; + var fn = window['goto_' + dest]; if (fn) fn(); } + else{ + if(location.hash.startsWith('#h_')) + location.hash = '#'; + } + + ebi('srchfolder_div').style.display = dest == 'search' ? 'block' : 'none'; clmod(document.documentElement, 'op_open', dest); + // enables the use of keyboard page nav on modals + //var a_modal = QS('.modal.vis'); + //clmod(document.documentElement, 'noscroll', a_modal && window.getComputedStyle(a_modal).position == 'fixed'); if (treectl) @@ -1206,4 +1700,47 @@ +window.onhashchange = function() { + console.log('hash change: ' + location.hash) + // keep it clean + if(location.hash == '') + hist_replace(location.pathname + location.search); + + var a_modal = QS('.modal.vis'); + if(location.hash.length <= 1 && a_modal){ + modaltoggle(a_modal.id); + console.log('closing modal due to hash'); + } + if(a_modal && location.hash == '#h_' + a_modal.id){ + if(a_modal.style.position != 'fixed') + return; + var m_header = QS('.modal.vis .modalheader'); + if(m_header){ + m_header.click(); + console.log('going to top of modal'); + } + } + else if (location.hash.startsWith("#h_")){ + var header = ebi(location.hash.slice(1)); + if(!header) + return; + var p_modal = header.closest('.modal'); + if(!p_modal) + return; + if(!clgot(p_modal, 'vis')){ + console.log('forcing modal open due to subheader hash'); + modaltoggle(p_modal.id, true); + } + ebi(location.hash.slice(1)).scrollIntoView(); + } + else if (location.hash.startsWith('#gf-') && !baguetteBox.getUserAction()){ + hash0 = location.hash; + eval_hash() + } + else if(location.hash == '' && QS('#bbox-overlay.visible')){ + baguetteBox.hide(); + } +} + + var m = SPINNER.split(','), SPINNER_CSS = SPINNER.slice(1 + m[0].length); @@ -1246,6 +1783,6 @@ var img_re = APPLE ? - /\.(a?png|avif|bmp|gif|hei[cf]s?|jpe?g|jfif|svg|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i : - /\.(a?png|avif|bmp|gif|jpe?g|jfif|svg|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i; + /\.(a?png|avif|bmp|gif|hei[cf]s?|jpe?g|jxl|jfif|svg|ico|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i : + /\.(a?png|avif|bmp|gif|jpe?g|jxl|jfif|svg|ico|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i; var wopi_set = !window.have_wopi ? null : @@ -1294,8 +1831,9 @@ if (!fullui) { if (window.ui_nombar || /[?&]nombar\b/.exec(sloc0)) x += '#ops,'; - if (window.ui_noacci || /[?&]noacci\b/.exec(sloc0)) x += '#acc_info,'; - if (window.ui_nosrvi || /[?&]nosrvi\b/.exec(sloc0)) x += '#srv_info,#srv_info2,'; + if (window.ui_noacci || /[?&]noacci\b/.exec(sloc0)) x += '#acc_info,#opa_acc,'; + if (window.ui_nosrvi || /[?&]nosrvi\b/.exec(sloc0)) x += '#srv_info,#srv_info2,#tree_footer'; if (window.ui_nocpla || /[?&]nocpla\b/.exec(sloc0)) x += '#goh,'; if (window.ui_nolbar || /[?&]nolbar\b/.exec(sloc0)) x += '#wfp,'; + if (nonav) x += '#path,'; if (window.ui_noctxb || /[?&]noctxb\b/.exec(sloc0)) x += '#wtoggle,'; if (window.ui_norepl || /[?&]norepl\b/.exec(sloc0)) x += '#repl,'; @@ -1304,5 +1842,4 @@ document.head.appendChild(mknod('style', '', x.slice(0, -1) + '{display:none!important}')); - if (location.pathname.indexOf('//') === 0) hist_replace(location.pathname.replace(/^\/+/, '/')); @@ -1349,59 +1886,4 @@ var have_mctl = 'mediaSession' in navigator && window.MediaMetadata; - ebi('op_player').innerHTML = ( - '

        ' + L.cl_opts + '

        ' + - '' + - '' + - '
        ' + - - '

        ' + L.ml_drc + '

        ' + - '

        ' + L.ml_eq + '

        ' + - '

        ' + L.ml_ss + '

        ' + - ''); - var r = { "pb_mode": (sread('pb_mode', ['loop', 'next', 'stop']) || 'next').split('-')[0], @@ -1421,6 +1903,9 @@ bcfg_bind(r, 'shuf', 'au_shuf', false, function () { mp.read_order(); // don't bind + ebi('au_shuf2').classList = ebi('au_shuf').classList; }); + ebi('au_shuf2').onclick = ebi('au_shuf').onclick; bcfg_bind(r, 'aplay', 'au_aplay', true); + bcfg_bind(r, 'afade', 'au_afade', true); bcfg_bind(r, 'preload', 'au_preload', true); bcfg_bind(r, 'prescan', 'au_prescan', true); @@ -1437,5 +1922,24 @@ bcfg_bind(r, 'waves', 'au_waves', true, function (v) { if (!v) pbar.unwave(); + r.selectprogbar(); }); + r.selectprogbar = function(){ + if (r.waves){ + clmod(ebi('progbar'), 'vis', true) + clmod(ebi('altprogbar'), 'vis', false) + if(pbar){ + pbar.unwave(); + if (mpl.waves && mp && mp.au) + pbar.loadwaves(mp.au.src.replace(/\bth=(opus|mp3)&/, '') + '&th=p'); + pbar.onresize(); + } + } + else{ + clmod(ebi('progbar'), 'vis', false) + clmod(ebi('altprogbar'), 'vis', true) + } + } + r.selectprogbar(); + bcfg_bind(r, 'os_seek', 'au_os_seek', !IPHONE, announce); bcfg_bind(r, 'osd_cv', 'au_osd_cv', true, announce); @@ -1475,5 +1979,5 @@ function draw_pb_mode() { - var btns = QSA('#pb_mode>a'); + var btns = QSA('#h_pbmode a'); for (var a = 0, aa = btns.length; a < aa; a++) { clmod(btns[a], 'on', btns[a].getAttribute("m") == r.pb_mode); @@ -1490,4 +1994,48 @@ } + ebi('cyclebtn_loopmode').onclick = function(){ + if(r.pb_mode == 'next'){ + // #4: folder loop was set, switch to none / next folder + r.pb_mode = 'stop'; + } + else if(r.pb_mode == 'loop'){ + // #3: folder loop was set, switch to none / next folder + r.pb_mode = 'next'; + } + else if(clgot(ebi('au_loop'), 'on')) { + // #2: single loop was set, switch to folder loop + ebi('au_loop').click(); + r.pb_mode = 'loop'; + } + else { + // #1: no loop set, click single loop btn + ebi('au_loop').click(); + } + swrite('pb_mode', r.pb_mode); + draw_pb_mode(); + drawLoopStatus(); + } + + function drawLoopStatus(){ + var btn = ebi('cyclebtn_loopmode'); + if(r.pb_mode == 'next'){ + btn.innerHTML = '⏭️'; + clmod(btn, 'on', true); + } + else if(r.pb_mode == 'loop'){ + btn.innerHTML = '🔁'; + clmod(btn, 'on', true); + } + else if(clgot(ebi('au_loop'), 'on')) { + btn.innerHTML = '🔂'; + clmod(btn, 'on', true); + } + else { + btn.innerHTML = '🔁'; + clmod(btn, 'on', false); + } + } + drawLoopStatus(); + function set_tint() { var tint = icfg_get('pb_tint', 0); @@ -1495,5 +2043,5 @@ ebi('barbuf').style.removeProperty('background'); else - ebi('barbuf').style.background = 'rgba(126,163,75,' + (tint / 100.0) + ')'; + ebi('barbuf').style.background = 'rgba(126,126,126,' + (tint / 100.0) + ')'; } ebi('pb_tint').oninput = function (e) { @@ -1562,7 +2110,7 @@ } if (!IPHONE) - btns[1].style.display = btns[2].style.display = 'none'; - btns[4].style.display = have_c2flac ? '' : 'none'; - btns[5].style.display = have_c2wav ? '' : 'none'; + btns[1].parentElement.style.display = btns[2].parentElement.style.display = 'none'; + btns[4].parentElement.style.display = have_c2flac ? '' : 'none'; + btns[5].parentElement.style.display = have_c2wav ? '' : 'none'; if (v) @@ -1576,7 +2124,7 @@ r.ac2 = v; - ebi('ac_flac').setAttribute('tt', L.mt_cflac.split('"')[0].format(v)); - ebi('ac_aac').setAttribute('tt', L.mt_caac.split('"')[0].format(v)); - ebi('ac_oth').setAttribute('tt', L.mt_coth.split('"')[0].format(v)); + ebi('ac_flac').nextSibling.innerHTML = L.mt_cflac.split('"')[0].format(v); + ebi('ac_aac').nextSibling.innerHTML = L.mt_caac.split('"')[0].format(v); + ebi('ac_oth').nextSibling.innerHTML = L.mt_coth.split('"')[0].format(v); }; @@ -1602,4 +2150,21 @@ setaufollow(); + r.set_mu_cv = function() { + var o = QS('#music.vis #mu_th>img'); + if (o && r.cover) { + var url = r.cover.slice(0, -1) + 'wf3'; + + set_loaded(o, false); + o.onload = th_onload; + o.setAttribute('src', url); + + o = QS('html.b #music, html.f #music'); + if (o) + o.style.background = "url('" + url + "') no-repeat center / cover"; + else + ebi('music').style.background = ""; + } + } + function announce() { if (!r.os_ctl || !mp.au) @@ -1620,5 +2185,5 @@ if (r.osd_cv) { - var files = QSA("#files tr>td:nth-child(2)>a[id]"), + var files = QSA("#files .flink"), cover = null; @@ -1630,7 +2195,8 @@ } - cover = addq(cover || mp.au.osrc, 'th=j'); + cover = addq(cover || mp.au.src, 'th=j'); tags.artwork = [{ "src": cover, type: "image/jpeg" }]; } + r.cover = cover; ebi('np_circle').textContent = np.circle || ''; @@ -1639,5 +2205,5 @@ ebi('np_artist').textContent = np.artist || (fns.length > 1 ? fns[0] : ''); ebi('np_title').textContent = np.title || ''; - ebi('np_dur').textContent = np['.dur'] || ''; + ebi('txtsongend').innerHTML = ebi('np_dur').textContent = np['.dur'] || ''; ebi('np_url').textContent = uricom_dec(get_evpath()) + np.file.split('?')[0]; if (!MOBILE && cover) @@ -1654,4 +2220,5 @@ navigator.mediaSession.setActionHandler('nexttrack', next_song); r.pp(); + r.set_mu_cv(); } r.announce = announce; @@ -1731,5 +2298,5 @@ for (var a = 0, aa = trs.length; a < aa; a++) { var tds = trs[a].getElementsByTagName('td'), - link = tds[1].getElementsByTagName('a'); + link = trs[a].querySelectorAll('.flink'); link = link[link.length - 1]; @@ -1792,5 +2359,5 @@ tid = tid.slice(1); - if (r.tracks[tid]) + if (r.tracks[tid]) order.push(tid); } @@ -1812,5 +2379,12 @@ r.au.play(); mpl.pp(); - fader(); + if(mpl.afade != false) + fader(); + else{ + // insta start + r.fvol = r.vol; + mpss.go(); + r.au.volume = r.expvol(r.fvol); + } } }; @@ -1820,5 +2394,11 @@ r.fdir = -0.05 * r.vol * (CHROME ? 2 : 1); r.ftid = r.au.tid; - fader(); + if(mpl.afade != false) + fader(); + else { + // insta stop + r.au.pause(); + mpl.pp(); + } }; r.stopfade = function (hard) { @@ -1944,5 +2524,5 @@ for (var a = 1, aa = th.length; a < aa; a++) { var tv = tr.cells[a].textContent, - tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').pop().toLowerCase(), + tk = tr.cells[a].querySelector('.flink') ? 'file' : th[a].getAttribute('name').split('/').pop().toLowerCase(), vis = th[a].className.indexOf('min') === -1; @@ -1980,5 +2560,4 @@ return false; - clmod(document.documentElement, 'np_open', is_open); clmod(widget, 'open', is_open); bcfg_set('au_open', r.is_open = is_open); @@ -1997,9 +2576,15 @@ if (was_paused != paused) { was_paused = paused; - ebi('bplay').innerHTML = paused ? '▶' : '⏸'; + ebi('bplay').innerHTML = ebi('bplay2').innerHTML = + paused ? svg_play : svg_pause; } }; r.setvis = function () { - widget.style.display = !has(perms, "read") || showfile.abrt ? 'none' : ''; + var display = !has(perms, "read") || showfile.abrt ? 'none' : ''; + ebi('wtico').style.display = + ebi('wtoggle').style.display = + ebi('widgeti').style.display = + ebi('np_inf').style.display = + display; }; wtico.onclick = function (e) { @@ -2094,11 +2679,23 @@ } - -function glossy_grad(can, h, s, l) { +function auto_grad(can, color, color2) { + if(color2 == null) + color2 = '#000'; var g = can.ctx.createLinearGradient(0, 0, 0, can.h), - p = [0, 0.49, 0.50, 1]; + p = [0, 0.49, 0.50, 1], + mix = [70, 100, 90, 70]; - for (var a = 0; a < p.length; a++) - g.addColorStop(p[a], 'hsl(' + h + ',' + s[a] + '%,' + l[a] + '%)'); + for (var a = 0; a < p.length; a++){ + var c = 'color-mix(in oklab, ' + color + ' ' + mix[a] + '%, ' + color2 + ' ' + (100 - mix[a]) + '%)'; + var pc = parseColor(c); + if (pc) { + try { + g.addColorStop(p[a], pc); + } catch (e) { + console.log(e); + g.addColorStop(p[a], color2); + } + } + } return g; @@ -2125,5 +2722,5 @@ r.buf.ctx.font = '.5em sans-serif'; r.pos.ctx.font = '.9em sans-serif'; - r.pos.ctx.strokeStyle = 'rgba(24,56,0,0.5)'; + r.pos.ctx.strokeStyle = 'rgba(40, 40, 40, 0.5)'; r.drawbuf(); r.drawpos(); @@ -2182,5 +2779,5 @@ apos, adur; - if (!widget.is_open) + if (!widget.is_open || !mpl.waves) return; @@ -2192,6 +2789,8 @@ bau = mp.au; + var accent = getComputedStyle(document.body).getPropertyValue('--a'); + var sm = bc.w * 1.0 / mp.au.duration, - gk = bc.h + '/' + themen, + gk = bc.h + '/' + themen + '/' + accent, dz = themen == 'dz', dy = themen == 'dy'; @@ -2199,8 +2798,8 @@ if (gradh != gk) { gradh = gk; - grad = glossy_grad(bc, dz ? 120 : 85, - dy ? [0, 0, 0, 0] : [35, 40, 37, 35], - dy ? [20, 24, 22, 20] : light ? [45, 56, 50, 45] : [42, 51, 47, 42]); + grad = auto_grad(bc, accent); } + if(IE) + grad = 'rgb(85, 144, 255)'; bctx.fillStyle = grad; for (var a = 0; a < mp.au.buffered.length; a++) { @@ -2219,18 +2818,19 @@ var step = sm > 1 ? 1 : sm > 0.4 ? 3 : sm > 0.05 ? 30 : 720; - bctx.fillStyle = light && !dy ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)'; - for (var p = step, mins = adur / 10; p <= mins; p += step) - bctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h); - step = sm > 0.15 ? 1 : sm > 0.05 ? 10 : 360; - bctx.fillStyle = light && !dy ? 'rgba(0,64,0,0.5)' : 'rgba(192,255,96,0.5)'; - for (var p = step, mins = adur / 60; p <= mins; p += step) - bctx.fillRect(Math.floor(sm * p * 60), 0, 2, pc.h); - - step = sm > 0.33 ? 1 : sm > 0.15 ? 5 : sm > 0.05 ? 10 : sm > 0.01 ? 60 : 720; - bctx.fillStyle = dz ? '#0f0' : dy ? '#999' : light ? 'rgba(0,64,0,0.9)' : 'rgba(192,255,96,1)'; - for (var p = step, mins = adur / 60; p <= mins; p += step) { - bctx.fillText(p, Math.floor(sm * p * 60 + 3), pc.h / 3); - } + //bctx.fillStyle = light && !dy ? 'rgba(0,0,0,0.15)' : 'rgba(255, 255, 255, 0.15)'; + //for (var p = step, mins = adur / 10; p <= mins; p += step) + // bctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h); + + //step = sm > 0.15 ? 1 : sm > 0.05 ? 10 : 360; + //bctx.fillStyle = light && !dy ? 'rgba(0, 0, 0, 0.5)' : 'rgba(255, 255, 255, 0.5)'; + //for (var p = step, mins = adur / 60; p <= mins; p += step) + // bctx.fillRect(Math.floor(sm * p * 60), 0, 2, pc.h); + + //step = sm > 0.33 ? 1 : sm > 0.15 ? 5 : sm > 0.05 ? 10 : sm > 0.01 ? 60 : 720; + //bctx.fillStyle = dz ? '#0f0' : dy ? '#999' : light ? 'rgba(0, 0, 0, 0.9)' : 'rgb(255, 255, 255)'; + //for (var p = step, mins = adur / 60; p <= mins; p += step) { + // bctx.fillText(p, Math.floor(sm * p * 60 + 3), pc.h / 3); + //} step = sm > 0.2 ? 10 : sm > 0.1 ? 30 : sm > 0.01 ? 60 : sm > 0.005 ? 720 : 1440; @@ -2240,9 +2840,9 @@ }; - r.drawpos = function () { + r.drawpos = function (force) { var bc = r.buf, pc = r.pos, pctx = pc.ctx, - w = 8, + w = 2, apos, adur; @@ -2282,6 +2882,10 @@ x = sm * apos; + if(adur > 0) + ebi('sliderpos').value = apos / adur; + if (w && html_txt != t2) { - ebi('np_pos').textContent = html_txt = t2; + ebi('txtpos').innerHTML = ebi('np_pos').textContent = html_txt = t2; + ebi('txtsongend').innerHTML = t1; if (mpl.os_ctl) navigator.mediaSession.setPositionState({ @@ -2292,9 +2896,9 @@ } - if (!widget.is_open) + if (!widget.is_open || !mpl.waves && !force) return; - pctx.fillStyle = '#573'; pctx.fillRect((x - w / 2) - 1, 0, w + 2, pc.h); - pctx.fillStyle = '#dfc'; pctx.fillRect((x - w / 2), 0, w, pc.h); + pctx.fillStyle = '#bbb'; pctx.fillRect((x - w / 2) - 1, 0, w + 2, pc.h); + pctx.fillStyle = '#fff'; pctx.fillRect((x - w / 2), 0, w, pc.h); pctx.lineWidth = 2.5; @@ -2304,5 +2908,5 @@ m1b = pctx.measureText(t1 + ":88"), m2 = pctx.measureText(t2), - yt = pc.h * 0.94, + yt = pc.h * 0.65, xt1 = pc.w - (m1.width + 12), xt2 = x < m1.width * 1.4 ? (x + 12) : (Math.min(pc.w - m1b.width, x - 12) - m2.width); @@ -2330,8 +2934,8 @@ lastv = -1, untext = -1, - can, ctx, w, h, grad1, grad2; + can, ctx, w, h, style1, style2; r.onresize = function () { - if (!widget.is_open && r.can) + if (!widget.is_open && r.can ) return; @@ -2343,5 +2947,5 @@ w = r.can.w; h = r.can.h; - r.draw(); + setTimeout(r.draw, 1); } @@ -2350,5 +2954,8 @@ return; - var gh = h + '' + light, + var accent = getComputedStyle(document.body).getPropertyValue('--a'); + var bg = getComputedStyle(document.body).getPropertyValue('--bg-u2'); + + var gh = h + '' + light + '/' + accent, dz = themen == 'dz', dy = themen == 'dy'; @@ -2356,26 +2963,26 @@ if (gradh != gh) { gradh = gh; - grad1 = glossy_grad(r.can, dz ? 120 : 50, - dy ? [0, 0, 0, 0] : light ? [50, 55, 52, 48] : [45, 52, 47, 43], - dy ? [20, 24, 22, 20] : light ? [54, 60, 52, 47] : [42, 51, 47, 42]); - grad2 = glossy_grad(r.can, dz ? 120 : 205, - dz ? [100, 100, 100, 100] : dy ? [0, 0, 0, 0] : [10, 15, 13, 10], - dz ? [10, 14, 12, 10] : dy ? [90, 90, 90, 90] : [16, 20, 18, 16]); - } - ctx.fillStyle = grad2; ctx.fillRect(0, 0, w, h); - ctx.fillStyle = grad1; ctx.fillRect(0, 0, w * mp.vol, h); - - var vt = 'volume ' + Math.floor(mp.vol * 100), - tw = ctx.measureText(vt).width, - x = w * mp.vol - tw - 8, - li = dy; - - if (mp.vol < 0.5) { - x += tw + 16; - li = !li; + style1 = auto_grad(r.can, accent, accent); + style2 = light ? 'color-mix(in oklab, ' + bg + ' 85%, #000 15%' : 'color-mix(in oklab, ' + bg + ' 85%, #fff 15%'; } + if(IE){ + style1 = 'rgb(60, 126, 248)'; + style2 = 'rgb(75, 78, 82)' + } + ctx.fillStyle = style2; ctx.fillRect(0, 0, w, h); + ctx.fillStyle = style1; ctx.fillRect(0, 0, w * mp.vol, h); + + // var vt = Math.floor(mp.vol * 100) + '%', + // tw = ctx.measureText(vt).width, + // x = w * mp.vol - tw - 8, + // li = dy; + + // if (mp.vol < 0.5) { + // x += tw + 16; + // li = !li; + // } - ctx.fillStyle = li ? '#fff' : '#210'; - ctx.fillText(vt, x, h / 3 * 2); + // ctx.fillStyle = li ? '#fff' : '#210'; + // ctx.fillText(vt, x, h / 3 * 2); clearTimeout(untext); @@ -2385,6 +2992,13 @@ var rect; + var cs; + var left, top, width, height; function mousedown(e) { rect = can.getBoundingClientRect(); + cs = getComputedStyle(ebi('pvol')); + top = rect.top + parseFloat(cs.borderTopWidth); + left = rect.left + parseFloat(cs.borderLeftWidth); + height = rect.height - 2 * parseFloat(cs.borderTopWidth); + width = rect.width - 2 * parseFloat(cs.borderLeftWidth); mousemove(e); } @@ -2398,6 +3012,12 @@ } - var x = e.clientX - rect.left, - mul = x * 1.0 / rect.width; + var x = e.clientX - left, + mul = x * 1.0 / width; + + if(height > width) { + // vertical + var y = e.clientY - top; + mul = 1 - y * 1.0 / height; + } if (mul > 0.98) @@ -2413,18 +3033,28 @@ }, 1); } - can.onmousedown = function (e) { + ebi('pvolbg').onmousedown = can.onmousedown = function (e) { if (e.button !== 0) return; - can.onmousemove = mousemove; + ebi('pvolbg').onmousemove = can.onmousemove = mousemove; mousedown(e); }; - can.onmouseup = function (e) { + ebi('pvolbg').onmouseup = can.onmouseup = function (e) { if (e.button === 0) - can.onmousemove = null; + ebi('pvolbg').onmousemove = can.onmousemove = null; }; - if (TOUCH) { - can.ontouchstart = mousedown; - can.ontouchmove = mousemove; + ebi('pvolbg').ontouchstart = can.ontouchstart = mousedown; + ebi('pvolbg').ontouchmove = can.ontouchmove = mousemove; + var mute = ebi('pvolbg').children[0]; + mute.onclick = function (e) { + ev(e); + if(mp.vol > 0) + mp.setvol(0); + else + mp.setvol(1); + r.draw(); + } + mute.onmousedown = function (e) { + ev(e); } return r; @@ -2582,7 +3212,7 @@ // hook up the widget buttons (function () { - ebi('bplay').onclick = playpause; - ebi('bprev').onclick = prev_song; - ebi('bnext').onclick = next_song; + ebi('bplay').onclick = ebi('bplay2').onclick = playpause; + ebi('bprev').onclick = ebi('bprev2').onclick = prev_song; + ebi('bnext').onclick = ebi('bnext2').onclick = next_song; var bar = ebi('barpos'); @@ -2599,31 +3229,36 @@ seek_au_mul(x * 1.0 / rect.width); }; + ebi('sliderpos').oninput = function(){ + if (!mp.au) { + play(0, true); + return mp.fade_in(); + } + seek_au_mul(this.value); + } - if (!TOUCH) { - bar.onwheel = function (e) { - var dist = Math.sign(e.deltaY) * 10; - if (Math.abs(e.deltaY) < 30 && !e.deltaMode) - dist = e.deltaY; + bar.onwheel = function (e) { + var dist = Math.sign(e.deltaY) * 10; + if (Math.abs(e.deltaY) < 30 && !e.deltaMode) + dist = e.deltaY; - if (!dist || !mp.au) - return true; + if (!dist || !mp.au) + return true; - seek_au_rel(dist); - ev(e); - }; - ebi('pvol').onwheel = function (e) { - var dist = Math.sign(e.deltaY) * 10; - if (Math.abs(e.deltaY) < 30 && !e.deltaMode) - dist = e.deltaY; + seek_au_rel(dist); + ev(e); + }; + ebi('pvolbg').onwheel = function (e) { + var dist = Math.sign(e.deltaY) * 10; + if (Math.abs(e.deltaY) < 30 && !e.deltaMode) + dist = e.deltaY; - if (!dist || !mp.au) - return true; + if (!dist) + return true; - dist *= -1; - mp.setvol(Math.round((mp.vol + dist / 500) * 100) / 100 ); - vbar.draw(); - ev(e); - }; - } + dist *= -1; + mp.setvol(Math.round((mp.vol + dist / 250) * 100) / 100 ); + vbar.draw(); + ev(e); + }; })(); @@ -2880,10 +3515,12 @@ swrite('au_eq_chw', r.chw); - var txt = QSA('input.eq_gain'); + var gains = QSA('input.eq_gain'); for (var a = 0; a < r.bands.length; a++) - txt[a].value = r.gains[a]; + gains[a + gains.length / 2].value = gains[a].value = r.gains[a].toFixed(1); - QS('input.eq_gain[band="amp"]').value = r.amp; - QS('input.eq_gain[band="chw"]').value = r.chw; + var amps = QSA('input.eq_gain[band="amp"]'); + amps[0].value = amps[1].value = r.amp; + var chws = QSA('input.eq_gain[band="chw"]'); + chws[0].value = chws[1].value = r.chw; }; @@ -3073,5 +3710,5 @@ v = parseFloat(vs); - if (!isNum(v) || v + '' != vs) + if (!isNum(v) || (v + '' != vs && v.toFixed(1) != vs)) throw new Error('inval band'); @@ -3157,7 +3794,9 @@ } - var html = ['
        '], - h2 = [], h3 = [], h4 = []; + ebi('audio_eq').innerHTML = '' + L.enable + + '

        ' + tt.parse(L.mt_eq) + '


        '; + + var html = ['
        ', - '' + L.enable + '
        '], + h2 = [], h3 = [], h4 = [], h5 = []; var vs = []; @@ -3175,18 +3814,21 @@ for (var a = 0; a < vs.length; a++) { var b = vs[a][0]; - html.push(''); - h2.push(''); + html.push(''); + h2.push(''); + h3.push(''); h4.push(''); - h3.push(''); + h5.push(''); } html = html.join('\n') + ''; html += h2.join('\n') + ''; html += h3.join('\n') + ''; - html += h4.join('\n') + '
        +' + vs[a][1] + '' + vs[a][1] + '+
        '; - ebi('audio_eq').innerHTML = html; + html += h4.join('\n') + ''; + html += h5.join('\n') + '
        '; + ebi('audio_eq').innerHTML += html; + ebi('audio_drc').innerHTML = '' + L.enable + + '

        ' + tt.parse(L.mt_drc) + '


        '; h2 = []; - html = ['
        ']; + html = ['
        ', - '' + L.enable + '
        ']; for (var a = 0; a < r.drch.length; a++) { @@ -3196,9 +3838,10 @@ html = html.join('\n') + ''; html += h2.join('\n') + '
        '; - ebi('audio_drc').innerHTML = html; + ebi('audio_drc').innerHTML += html; + ebi('audio_ss').innerHTML = '' + L.enable + + '

        ' + tt.parse(L.mt_ss) + '


        '; h2 = []; - html = ['
        ']; + html = ['
        ', - '' + L.enable + '
        ']; for (var a = 0; a < r.sscl.length; a++) { @@ -3209,5 +3852,5 @@ html = html.join('\n') + ''; html += h2.join('\n') + '
        '; - ebi('audio_ss').innerHTML = html; + ebi('audio_ss').innerHTML += html; var stp = QSA('a.eq_step'); @@ -3319,6 +3962,20 @@ mp.au.osrc = mp.tracks[tid]; + + var tname = esc(uricom_dec(mp.au.osrc.split('/').pop())); + var el = ebi('trackname'); + el.innerHTML = tname; + el.setAttribute('tt', tname); + tt.att(ebi('pctl')); + afilt.apply(); + // popup player + ebi('mu_tn').innerHTML = tname.replace(/\.[0-9a-z]+$/i, ''); + var m_ext = tname.match(/[^\.]*$/); + if(m_ext){ + QS('#mu_th>svg').style.color = intToHSL(hashCode(m_ext[0])) + QS('#mu_th>span').innerHTML = m_ext[0]; + } setTimeout(function () { mpl.unbuffer(url); @@ -3342,4 +3999,5 @@ clmod(ebi('wtoggle'), 'np', mpl.clip); clmod(ebi('wtoggle'), 'm3u', mpl.m3uen); + if (thegrid) thegrid.loadsel(); @@ -3368,11 +4026,7 @@ mpl.np = ft2dict(t_tr, { 'up_ip': 1 }); - pbar.unwave(); - if (mpl.waves) - pbar.loadwaves(url.replace(/\bth=(opus|mp3)&/, '') + '&th=p'); - mpss.go(); mpui.progress_updater(); - pbar.onresize(); + mpl.selectprogbar(); vbar.onresize(); mpl.announce(); @@ -3576,4 +4230,5 @@ var media = scan_hash(v); if (media) { + console.log('got media in hash') var mtype = media[0], id = media[1], @@ -3588,14 +4243,11 @@ if (mtype == 'g') { - if (!thegrid.en) - ebi('griden').click(); - var t = setInterval(function () { - if (!thegrid.bbox) + if (!msel.bbox || !msel.ready) return; clearInterval(t); baguetteBox.urltime(ts); - var im = QS('#ggrid a[ref="' + id + '"]'); + var im = ebi(id); if (!im) return toast.warn(10, L.im_hnf); @@ -3642,5 +4294,4 @@ d.setAttribute('class', 'ayjump'); d.innerHTML = a ? L.ay_path : L.ay_files; - document.body.insertBefore(d, ebi('ops')); d.onclick = function (e) { ev(e); @@ -3654,11 +4305,14 @@ })(a); - // account-info label - var d = mknod('div', 'acc_info'); - document.body.insertBefore(d, ebi('ops')); - // folder nav - ebi('goh').parentElement.appendChild(mknod('span', null, - ''; + var svg_right = svg_box + ''; + var svg_up = svg_box + ''; + + ebi('gop').innerHTML = svg_left; + ebi('gon').innerHTML = svg_right; + ebi('gou').innerHTML = svg_up; + ebi('gop').onclick = function () { tree_neigh(-1); } ebi('gon').onclick = function () { tree_neigh(1); } @@ -3672,4 +4326,6 @@ setsb(); + bcfg_bind(props, 'enbg', 'enbg', true, function(){ setBg(props.enbg ? window.bg_img : '')}); + // compact media player function setacmp() { @@ -3933,4 +4589,14 @@ } +if(navigator.serviceWorker && caches){ + caches.open('/media').then(function(cache){ + return cache.match('/shared-file-count') + }).then(function(r){ + return r && r.text(); + }).then(function(count){ + window.shr_target = count > 0; + fileman.render(); + }); +} var fileman = (function () { @@ -3939,6 +4605,9 @@ bcut = ebi('fcut'), bcpy = ebi('fcpy'), + bsav = ebi('fsav'), bpst = ebi('fpst'), bshr = ebi('fshr'), + bcpl = ebi('fcpl'), + bclr = ebi('fclr'), t_paste, r = {}; @@ -3967,9 +4636,12 @@ encpy = nsel, enpst = r.clip && r.clip.length, - hren = !(have_mv && has(perms, 'write') && has(perms, 'move')), - hdel = !(have_del && has(perms, 'delete')), - hcut = !(have_mv && has(perms, 'move')), - hpst = !(have_mv && has(perms, 'write')), - hshr = !can_shr || !get_evpath().indexOf(have_shr); + hren = !(have_mv && has(perms, 'write') && has(perms, 'move')) || !nsel, + hdel = !(have_del && has(perms, 'delete')) || !nsel, + hcut = !(have_mv && has(perms, 'move')) || !nsel, + hpst = !(have_mv && has(perms, 'write')) || !enpst, + hshr = !can_shr || !get_evpath().indexOf(have_shr), + hcpl = !nsel, + hsav = !window.shr_target, + enclr = enpst || nsel || window.shr_target; if (!(enren || endel || encut || enpst)) @@ -3981,11 +4653,18 @@ clmod(bcpy, 'en', encpy); clmod(bpst, 'en', enpst); + clmod(bsav, 'en', 1); clmod(bshr, 'en', 1); + clmod(bcpl, 'en', 1); + clmod(bclr, 'en', 1); clmod(bren, 'hide', hren); clmod(bdel, 'hide', hdel); clmod(bcut, 'hide', hcut); - clmod(bpst, 'hide', hpst); + clmod(bcpy, 'hide', !encpy); + clmod(bpst, 'hide', !enpst); + clmod(bsav, 'hide', hsav); clmod(bshr, 'hide', hshr); + clmod(bcpl, 'hide', hcpl); + clmod(bclr, 'hide', !enclr); clmod(ebi('wfm'), 'act', QS('#wfm a.en:not(.hide)')); @@ -4001,4 +4680,7 @@ bpst.setAttribute('tt', L.ft_paste.format(r.clip.length)); bshr.setAttribute('tt', nsel ? L.fs_ss : L.fs_sc); + + ebi('spaceFree').innerHTML = volspace[0] + ' free of ' + volspace[1]; + ebi('spaceUsed_bar').style.width = (volspace[2] / 100) + '%'; }; @@ -4038,4 +4720,31 @@ }; + r.clear = function (e) { + ev(e); + + if(window.shr_target){ + window.shr_target = false; + caches.open('/media').then(function(cache){ + cache.match('/shared-file-count').then(function(r){ + return r && r.text() + }).then(function(count){ + if(count > 0){ + cache.delete('/shared-file-count'); + for(var i = 0; i < count; i++){ + cache.delete('/shared-file-' + i); + cache.delete('/shared-filename-' + i); + } + } + }); + }); + } + + var stamp = Date.now(); + msel.evsel(); + jwrite('fman_clip', [stamp]); + r.clip = []; + r.tx(stamp); + } + r.share = function (e) { ev(e); @@ -4227,4 +4936,19 @@ }; + r.link = function (e) { + var sel = msel.getsel(), + vps = []; + + for (var a = 0; a < sel.length; a++) + vps.push(ebi(sel[a].id).href); + + if (!sel.length) + return toast.err(3, L.fd_emore); + + ev(e); + + cliptxt(vps.join(' \n'), function () { toast.ok(2, L.clipped) }); + } + r.rename = function (e) { ev(e); @@ -4256,5 +4980,5 @@ var vsp = vsplit(vp); if (base != vsp[0]) - return toast.err(0, esc('bug:\n' + base + '\n' + vsp[0])); + return toast.err(0, esc('batch rename not allowed for differing file paths:\n' + base + '\n' + vsp[0])); var vars = ft2dict(ebi(sel[a].id).closest('tr')); @@ -4638,41 +5362,51 @@ r.cut = function (e) { + var useClip = r.clip.length > 0; + var sel = msel.getsel(), stamp = Date.now(), vps = [stamp]; - if (!sel.length) + if (!useClip && !sel.length) return toast.err(3, L.fc_emore); ev(e); - if (clgot(bcut, 'hide')) + if (!useClip && clgot(bcut, 'hide')) return toast.err(3, L.fc_eperm); - var els = [], griden = thegrid.en; - for (var a = 0; a < sel.length; a++) { - vps.push(sel[a].vp); - if (sel.length < 100) - try { - if (griden) - els.push(QS('#ggrid>a[ref="' + sel[a].id + '"]')); - else - els.push(ebi(sel[a].id).closest('tr')); + if(!useClip){ + var els = [], griden = thegrid.en; + for (var a = 0; a < sel.length; a++) { + vps.push(sel[a].vp); + if (sel.length < 100) + try { + if (griden) + els.push(QS('#ggrid>a[ref="' + sel[a].id + '"]')); + else + els.push(ebi(sel[a].id).closest('tr')); - clmod(els[a], 'fcut'); + clmod(els[a], 'fcut'); + } + catch (ex) { } + } + + setTimeout(function () { + try { + for (var a = 0; a < els.length; a++) + clmod(els[a], 'fcut', 1); } catch (ex) { } - } + }, 1); - setTimeout(function () { - try { - for (var a = 0; a < els.length; a++) - clmod(els[a], 'fcut', 1); + r.clip = vps.slice(1); + } + else { + for (var a = 0; a < r.clip.length; a++) { + vps.push(r.clip[a]); } - catch (ex) { } - }, 1); + } r.ccp = false; - r.clip = vps.slice(1); try { @@ -4689,4 +5423,7 @@ toast.warn(30, L.fc_warn.format(sel.length)); } + + if(thegrid.tempsel) + msel.evsel(); }; @@ -4749,4 +5486,7 @@ toast.warn(30, L.fcc_warn.format(sel.length)); } + + if(thegrid.tempsel) + msel.evsel(); }; @@ -4817,5 +5557,8 @@ }; - r.paste = function () { + r.paste = function (justgo) { + if (ebi('unsearch')) + return toast.err(3, L.fp_esrch) + if (!r.clip.length) return toast.err(5, L.fp_ecut); @@ -4840,5 +5583,5 @@ indir = [], srcdir = vsplit(r.clip[0])[0], - links = QSA('#files tbody td:nth-child(2) a'); + links = QSA('#files .flink'); r.f = f; @@ -4911,4 +5654,6 @@ src.push(f[a].src); + if (justgo) + return okgo(); return modal.confirm((r.ccp ? L.fcp_confirm : L.fp_confirm).format(f.length) + '
          ' + uricom_adec(src, true).join('') + '
        ', okgo, null); } @@ -5002,4 +5747,6 @@ } r.render(); + if(('' + msg).startsWith(window.location.origin)) + msg = msg.replace(window.location.origin, ""); if (msg == get_evpath()) treectl.goto(msg); @@ -5028,5 +5775,8 @@ bcpy.onclick = r.cpy; bpst.onclick = r.paste; + if(up2k) bsav.onclick = up2k.save_here; // just in case up2k ever inits first bshr.onclick = r.share; + bcpl.onclick = r.link; + bclr.onclick = r.clear; return r; @@ -5541,25 +6291,36 @@ -var thegrid = (function () { +window.thegrid = (function () { var lfiles = ebi('files'), gfiles = mknod('div', 'gfiles'); + var svg_list = svg_box + '' + var svg_grid = svg_box + '' + var svg_gallery = svg_box + '' + gfiles.style.display = 'none'; gfiles.innerHTML = ( - '
        ' + - ' ' + - '+ ' + L.gt_chop + ': ' + - ' ' + - '+ ' + L.gt_sort + ': ' + - '' + L.gt_name + ' ' + - '' + L.gt_sz + ' ' + - '' + L.gt_ts + ' ' + - '' + L.gt_ext + '' + - '
        ' + + '
        ' + + '' + svg_list + '' + + '' + svg_grid + '' + + '' + svg_gallery + '' + + ' ' + + '' + L.gt_zoom + ': ' + + ' ' + + '+' + + ' ' + L.gt_chop + ': ' + + ' ' + + '+' + + ' ' + L.gt_sort + ': ' + + '' + L.gt_name + ' ' + + '' + L.gt_sz + ' ' + + '' + L.gt_ts + ' ' + + '' + L.gt_ext + '' + + '' + + '
        ' + '
        ' ); @@ -5567,4 +6328,32 @@ var ggrid = ebi('ggrid'); + var ico1 = ebi('listicon_template').cloneNode(true); + var ico2 = ebi('gridicon_template').cloneNode(true); + clmod(ico1, 'on', true); + clmod(ico2, 'on', false); + + // file list header + ebi('wtc').innerHTML = ( + ico1.outerHTML + + ico2.outerHTML + + '
        ' + + '' + L.cl_hiddenc + '' + + '
        ' + + '' + L.cl_hidec + '' + + '' + L.cl_reset + '' + + '
        ' + + '
        ' + + '
        ' + + '
        ' + ); + + ebi('h_hidden').onclick = function () { + clmod(this.parentElement, 'open', 't'); + if(!clgot(this.parentElement, 'open') && filecols.picking){ + filecols.unpick(); + clmod(ebi('hcolsh'), 'on', r.picking) + } + } + var r = { 'sz': clamp(fcfg_get('gridsz', 10), 4, 80), @@ -5578,5 +6367,9 @@ var s = this.getAttribute('s'), z = this.getAttribute('z'), - l = this.getAttribute('l'); + l = this.getAttribute('l'), + grdbtn = clgot(this, 'grdbtn'); + + if(grdbtn) + return; if (z) @@ -5616,6 +6409,9 @@ gfiles.style.display = vis && r.en ? '' : 'none'; lfiles.style.display = vis && !r.en ? '' : 'none'; + ebi('wtc').style.display = lfiles.style.display; clmod(ggrid, 'crop', r.crop); clmod(ggrid, 'nocrop', !r.crop); + clmod(ggrid, 'gallery', r.gallery); + clmod(ggrid, 'noupscale', !r.upscale); ebi('pro').style.display = ebi('epi').style.display = ebi('lazy').style.display = ebi('treeul').style.display = ebi('treepar').style.display = ''; ebi('bdoc').style.display = 'none'; @@ -5625,8 +6421,10 @@ treectl.textmode(false); - if (filecols) - filecols.uivis(); + var imgs = QSA('#ggrid>a img'); + for (var a = 0, aa = imgs.length; a < aa; a++) { + set_loaded(imgs[a], r.thumbs && imgs[a].complete, true); + } - aligngriditems(); + setTimeout(aligngriditems, 1); restore_scroll(); }; @@ -5669,82 +6467,18 @@ } setcvar('--grid-sz', r.sz + 'em'); - aligngriditems(); + setTimeout(aligngriditems, 1); } setsz(); - function gclick1(e) { - if (ctrl(e) && !treectl.csel && !r.sel) - return true; - - return gclick.call(this, e, false); - } - function gclick2(e) { - if (ctrl(e) || !r.sel) + if (ctrl(e) || !r.sel && !r.tempsel) return true; - return gclick.call(this, e, true); - } - - function gclick(e, dbl) { - var oth = ebi(this.getAttribute('ref')), - qhref = this.getAttribute('href'), - href = qhref.split('?')[0], - fid = oth.getAttribute('id'), - aplay = ebi('a' + fid), - atext = ebi('t' + fid), - is_txt = atext && !/\.ts$/.test(href) && showfile.getlang(href), - is_img = img_re.test(href), - is_dir = href.endsWith('/'), - is_srch = !!ebi('unsearch'), - in_tree = is_dir && treectl.find(oth.textContent.slice(0, -1)), - have_sel = QS('#files tr.sel'), - td = oth.closest('td').nextSibling, - tr = td.parentNode; - - if (!is_srch && ((r.sel && !dbl && !ctrl(e)) || (treectl.csel && (e.shiftKey || ctrl(e))))) { - td.onclick.call(td, e); - if (e.shiftKey) - return r.loadsel(); - clmod(this, 'sel', clgot(tr, 'sel')); - } - else if (in_tree && !have_sel) - in_tree.click(); - - else if (oth.hasAttribute('download')) - oth.click(); - - else if (aplay && (r.vau || !is_img)) - aplay.click(); - - else if (is_dir && !have_sel) - treectl.reqls(qhref, true); - - else if (is_txt && !has(['md', 'htm', 'html'], is_txt)) - atext.click(); - - else if (!is_img && have_sel) - window.open(qhref, '_blank'); - - else { - if (!dbl) - return true; - - setTimeout(function () { - r.sel = true; - }, 1); - r.sel = false; - this.click(); - } - ev(e); + var td = ebi(this.getAttribute('ref')); + return fclick.call(td, e, true); } r.imshow = function (url) { - var sel = '#ggrid>a' - if (!thegrid.en) { - thegrid.bagit('#files'); - sel = '#files a[id]'; - } - var ims = QSA(sel); + var ims = QSA('#files a[id]'); for (var a = 0, aa = ims.length; a < aa; a++) { var iu = ims[a].getAttribute('href').split('?')[0].split('/').slice(-1)[0]; @@ -5755,4 +6489,5 @@ }; + r.tempsel = false; r.loadsel = function () { if (r.dirty) @@ -5760,4 +6495,5 @@ var ths = QSA('#ggrid>a'); + var anysel = false; for (var a = 0, aa = ths.length; a < aa; a++) { @@ -5766,9 +6502,11 @@ continue; var cl = ebi(ref).closest('tr').className || ''; - - if (noq_href(ths[a]).endsWith('/')) - cl += ' dir'; - ths[a].className = cl; + + var chk = ths[a].getElementsByTagName('input')[0]; + if(chk) { + chk.checked = cl.match(/\bsel\b/); + anysel = anysel || chk.checked; + } } @@ -5783,4 +6521,7 @@ }; })(a); + + r.tempsel = TOUCH && anysel; + clmod(ebi('gridsel'), 'temp', r.tempsel); }; @@ -5834,7 +6575,5 @@ svgs = new Set(), max_svgs = CHROME ? 500 : 5000, - need_ext = !r.thumbs || !!ext_th, - use_ext_th = r.thumbs && ext_th, - files = QSA('#files>tbody>tr>td:nth-child(2) a[id]'); + files = QSA('#files .flink'); for (var a = 0, aa = files.length; a < aa; a++) { @@ -5842,69 +6581,17 @@ ohref = esc(ao.getAttribute('href')), href = ohref.split('?')[0], - ext = '', - ext0 = '', name = uricom_dec(vsplit(href)[1]), ref = ao.getAttribute('id'), - isdir = href.endsWith('/'), - ac = isdir ? ' class="dir"' : '', - ihref = ohref; - - if (need_ext && href != "#") { - var ar = href.split('.'); - if (ar.length > 1) - ar.shift(); - - ar.reverse(); - ext0 = ar[0]; - for (var b = 0; b < Math.min(2, ar.length); b++) { - if (ar[b].length > 7) - break; - - ext = ext ? (ar[b] + '.' + ext) : ar[b]; - } - if (!ext) - ext = 'unk'; - } - - if (use_ext_th && (ext_th[ext] || ext_th[ext0])) { - ihref = ext_th[ext] || ext_th[ext0]; - } - else if (r.thumbs) { - ihref = addq(ihref, 'th=' + ( - have_jxl ? 'x' : - have_webp ? 'w' : - 'j' - )); - if (!r.crop) - ihref += 'f'; - if (r.x3) - ihref += '3'; - if (href == "#") - ihref = SR + '/.cpr/ico/' + (ref == 'moar' ? '++' : 'exit'); - } - else if (isdir) { - ihref = SR + '/.cpr/ico/folder'; - } - else { - if (!svgs.has(ext)) { - if (svgs.size < max_svgs) - svgs.add(ext); - else - ext = "unk"; - } - ihref = SR + '/.cpr/ico/' + ext; - } - ihref = addq(ihref, 'cache=i&_=' + ACB + TS); - if (CHROME) - ihref += "&raster"; + isdir = clgot(ao.parentElement.parentElement, 'dir'), + ac = ao.parentElement.parentElement.classList; html.push('' + ao.innerHTML + ''); + '" class="flink ' + ac + '" ttt="' + esc(name) + '">' + + get_thumb(ohref, ref, isdir, true) + + '' + ao.innerHTML + ''); } ggrid.innerHTML = html.join('\n'); - var ths = QSA('#ggrid>a>img'); + var ths = QSA('#ggrid .imgcontainer>img'); for (var a = 0, aa = ths.length; a < aa; a++) ths[a].onload = th_onload; @@ -5913,58 +6600,34 @@ clmod(ggrid, 'nocrop', !r.crop); - var srch = ebi('unsearch'), - gsel = ebi('gridsel'); - - gsel.style.display = srch ? 'none' : ''; - if (srch && r.sel) - gsel.click(); - - ths = QSA('#ggrid>a'); + var ths = QSA('#ggrid>a'); for (var a = 0, aa = ths.length; a < aa; a++) { ths[a].ondblclick = gclick2; - ths[a].onclick = gclick1; + ths[a].onclick = fclick1; + } + + var imgs = QSA('#ggrid>a img'); + for (var a = 0, aa = imgs.length; a < aa; a++) { + set_loaded(imgs[a], r.thumbs && imgs[a].complete, true); + } + + var chks = QSA('.gselchk'); + for (var a = 0, aa = chks.length; a < aa; a++) { + chks[a].onclick = function (e) { + var oth = ebi(this.closest('a').getAttribute('ref')), + td = oth.closest('td').nextSibling; + if(td && td.onclick) + td.onclick.call(td, e); + else + rcm.show(e); + } } r.dirty = false; - r.bagit('#ggrid'); r.loadsel(); - aligngriditems(); + setTimeout(aligngriditems, 1); setTimeout(r.tippen, 20); + drag.initgrid(); } - r.bagit = function (isrc) { - if (!window.baguetteBox) - return; - - if (r.bbox) - baguetteBox.destroy(); - - var br = baguetteBox.run(isrc, { - noScrollbars: true, - duringHide: r.onhide, - afterShow: function () { - r.bbox_opts.refocus = true; - }, - captions: function (g, idx) { - var h = '' + g; - - return '' + (idx + 1) + ' / ' + this.length + ' -- ' + - esc(uricom_dec(h.split('/').pop())) + ''; - }, - onChange: function (i, maxIdx) { - if (this[i].imageElement) { - var h = 'g' + this[i].imageElement.getAttribute('ref'); - if (this[i].page) - h += ',p' + this[i].page; - h += getsort(); - sethash(h); - } - } - }); - r.bbox = true; - r.bbox_opts = br[1]; - }; - r.onhide = function () { afilt.apply(); @@ -6037,10 +6700,22 @@ } - bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty); + bcfg_bind(r, 'thumbs', 'thumbs', true, function(e){ + r.setdirty(); + msel.render(); + }); bcfg_bind(r, 'ihop', 'ihop', true); bcfg_bind(r, 'vau', 'gridvau', false); bcfg_bind(r, 'crop', 'gridcrop', !dcrop.endsWith('n'), r.set_crop); + bcfg_bind(r, 'upscale', 'gridupscale', true, function (v) { + clmod(ggrid, 'noupscale', !v) + }); bcfg_bind(r, 'x3', 'grid3x', dth3x.endsWith('y'), r.set_x3); - bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel); + bcfg_bind(r, 'sel', 'gridsel', false, function (e) { + if (r.tempsel) { + r.tempsel = false; + clmod(ebi('gridsel'), 'temp', false); + } + r.loadsel(); + }); bcfg_bind(r, 'en', 'griden', dgrid, function (v) { v ? loadgrid() : r.setvis(true); @@ -6048,5 +6723,5 @@ vbar.onresize(); }); - bcfg_bind(r, 'gaen', 'gauto', !!dgauto, function(v) { + bcfg_bind(r, 'gaen', 'gauto', !!dgauto, function (v) { if (r.en && sread("griden") != 1) { r.en = false; @@ -6059,14 +6734,153 @@ swrite('ga_thresh', r.gathr = (isNum(n) ? n : 0) || 70); }; - ebi('wtgrid').onclick = ebi('griden').onclick; + + var gbtn = QSA('.gb_grd'); + for(var a = 0; a < gbtn.length; a++) + gbtn[a].onclick = function(){ + if(!thegrid.en) + ebi('griden').click(); + } + + var lsbtn = QSA('.gb_lst'); + for(var a = 0; a < lsbtn.length; a++) + lsbtn[a].onclick = function(){ + if(thegrid.en) + ebi('griden').click(); + } + + bcfg_bind(r, 'gallery', 'galleryicon_template', false, function (v) { + clmod(ebi('ggrid'), 'gallery', v); + }); return r; })(); +function get_thumb(ohref, ref, isdir, grid){ + var href = ohref.split('?')[0], + ext = '', + ext0 = '', + need_ext = !thegrid.thumbs || !!ext_th, + use_ext_th = thegrid.thumbs && ext_th, + ihref = ohref; + + if (need_ext && href != "#") { + var ar = href.split('.'); + if (ar.length > 1) + ar.shift(); + + ar.reverse(); + ext0 = ar[0]; + for (var b = 0; b < Math.min(2, ar.length); b++) { + ext = ext ? (ar[b] + '.' + ext) : ar[b]; + if (ar[b].length > 2) + break; + } + if (!ext) + ext = 'unk'; + } + + if (use_ext_th && (ext_th[ext] || ext_th[ext0])) { + ihref = ext_th[ext] || ext_th[ext0]; + } + else if (thegrid.thumbs) { + ihref = addq(ihref, 'th=' + ( + have_jxl ? 'x' : + have_webp ? 'w' : + 'j' + )); + if (grid && !thegrid.crop) + ihref += 'f'; + if (grid && thegrid.x3) + ihref += '3'; + if (href == "#") + ihref = SR + '/.cpr/ico/' + (ref == 'moar' ? '++' : 'exit'); + } + else { + ihref = ''; + } + + if(ihref){ + ihref = addq(ihref, 'cache=i&_=' + ACB + TS); + + var accent = getComputedStyle(document.body).getPropertyValue('--a'); + if (!accent) + accent = '#07c'; + ihref += '&a=' + parseColor(accent).replace(/ /g, ''); + } + + var svg = ''; + svg = '' + svg + ''; + + if (N3DS){ + // 3DS / unsupported: use fallback + svg = '' + (isdir ? '📁[FOLDER]' : '📄[FILE:' + ext + ']') + ''; + ext = 'unk'; + } + + var container = '
        ' + + (grid ? '' : '') + + svg + + (isdir || ext == 'unk' || ext.startsWith('/') ? '' : + '' + ext + '') + + '
        ' + // onload needs to be set afterwards due to nonces + return container; +} function th_onload() { - this.style.height = ''; + set_loaded(this, true, false) +} + +function set_loaded(el, state, dblcheck) { + if(state && dblcheck) { + testImage(el); + return; + } + if(el.parentElement == null || el.parentElement.parentElement == null) + return; + var p = el.parentElement.parentElement + clmod(p, 'thumbed', state) + var a = p.getAttribute('ref') + if(a){ + var b = ebi(a) + if(b){ + var c = b.closest('tr') + if(c) + clmod(c, 'thumbed', state) + } + } +} + +function testImage(el) { + var URL = el.src + var tester=new Image(); + tester.onload= + function imageFound() { + set_loaded(el, true, false) + }; + tester.onerror= + function imageFound() { + set_loaded(el, false, false) + }; + tester.src=URL; } +function hashCode (str) { + var hash = 0; + for(var i = 0; i < str.length; i++){ + hash = str.charCodeAt(i) + 130 * ((hash << 5) - hash); + } + return hash +} +function intToHSL(i){ + return 'hsl(' + i % 360 + 'deg 100% 50%)' +} function tree_scrollto(e) { @@ -6218,5 +7032,5 @@ selfun(); } -var konmai = 0, konmak = (function() { +var konmai = 0, konmak = (function () { var u = "arrowup", d = "arrowdown", @@ -6235,5 +7049,6 @@ sh = e.shiftKey, ae = document.activeElement, - aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : ''; + aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '', + aett = aet ? ae.type : ''; if (k.startsWith('Key')) @@ -6250,5 +7065,5 @@ noop(); else if (konmak[konmai] != kl) - konmai = konmai && kl == konmak[0] ? (konmai<3?konmai:1):0; + konmai = konmai && kl == konmak[0] ? (konmai < 3 ? konmai : 1) : 0; else if (++konmai >= konmak.length) { konmai = -1; @@ -6257,6 +7072,7 @@ start_actx(); sfx_nice(); + treectl.entree(); toast.inf(9, 'omega clearance granted', null, 'top'); - setTimeout(function() { + setTimeout(function () { apply_perms(treectl.lsc); fileman.render(); @@ -6284,4 +7100,10 @@ return ebi('sh_abrt').click(); + if (QS('#music.vis')) + return ebi('cl_mu').click(); + + if (!clgot(ebi('fclr'), 'hide')) + return fileman.clear(); + if (QS('.opview.act')) return QS('#ops>a').click(); @@ -6303,5 +7125,5 @@ } - if (aet == 'input') + if (aet == 'input' && aett != 'checkbox') return; @@ -6356,5 +7178,5 @@ return ev(e) && ae.click() || true; - if (aet && aet != 'a' && aet != 'tr' && aet != 'td' && aet != 'div' && aet != 'pre') + if (aet && aet != 'a' && aet != 'tr' && aet != 'td' && aet != 'div' && aet != 'pre' && aett != 'checkbox') return; @@ -6362,5 +7184,5 @@ return hkhelp(); - if (!sh && ctrl(e)) { + if (!sh && ctrl(e) || k.endsWith('Delete')) { var sel = window.getSelection && window.getSelection() || {}; sel = sel && !sel.isCollapsed && sel.direction != 'none'; @@ -6375,5 +7197,5 @@ return fileman.d_paste(e); - if (kl == 'k') + if (kl == 'k' || (k.endsWith('Delete') && !ctrl(e))) return fileman.delete(e); @@ -6469,33 +7291,33 @@ var sconf = [ [ - L.s_sz, - ["szl", "sz_min", L.s_s1, "14", ""], - ["szu", "sz_max", L.s_s2, "14", ""] + L.s_fn, + ["name", "name", L.s_f1, "td_s", ".exe$"] ], [ - L.s_dt, - ["dtl", "dt_min", L.s_d1, "14", "1997-08-15, 01:00"], - ["dtu", "dt_max", L.s_d2, "14", "2020"] + L.s_rd, + ["path", "path", L.s_r1, "td_w", "windows -system32"] ], [ - L.s_rd, - ["path", "path", L.s_r1, "30", "windows -system32"] + L.s_sz, + ["szl", "sz_min", L.s_s1, "td_s", ""], + ["szu", "sz_max", L.s_s2, "td_s", ""] ], [ - L.s_fn, - ["name", "name", L.s_f1, "30", ".exe$"] + L.s_dt, + ["dtl", "dt_min", L.s_d1, "td_s", "1997-08-15, 01:00", "date"], + ["dtu", "dt_max", L.s_d2, "td_s", "2020", "date"] ], [ L.s_ta, - ["tags", "tags", L.s_t1, "30", "^irui$"] + ["tags", "tags", L.s_t1, "td_w", "^irui$"] ], [ L.s_ad, - ["adv", "adv", L.s_a1, "30", "key>=1A key<=2B .bpm>165"] + ["adv", "adv", L.s_a1, "td_w", "key>=1A key<=2B .bpm>165"] ], [ L.s_ua, - ["utl", "ut_min", L.s_u1, "14", "2007-04-08"], - ["utu", "ut_max", L.s_u2, "14", "2038-01-19"] + ["utl", "ut_min", L.s_u1, "td_s", "2007-04-08", "date"], + ["utu", "ut_max", L.s_u2, "td_s", "2038-01-19", "date"] ] ]; @@ -6511,11 +7333,12 @@ for (var b = 1; b < 3; b++) { var hn = "srch_" + sconf[a][b][0], - csp = (sconf[a].length == 2) ? 2 : 1; + csp = (sconf[a].length == 2) ? 2 : 1, + type = sconf[a][b].length >= 6 ? sconf[a][b][5] : "text"; html.push( '
        '); + '
        '); if (csp == 2) break; @@ -6537,4 +7360,56 @@ } + var folderSearch = ebi('folder_search'); + folderSearch.placeholder = L.s_dir; + folderSearch.onfocus = function(){ + ebi('srch_pathc').checked = true; + var path = decodeURI(get_evpath()); + ebi('srch_pathv').value = path.slice(1) || '/'; + } + if(IE) + folderSearch.onclick = function() { + clmod(ebi('srch_quickopts'), 'act', 't'); + } + folderSearch.oninput = function(){ + var v = unsmart(this.value); + if(IE && !v) + return; + var nsearch = ebi('srch_namev'); + nsearch.value = v; + nsearch.oninput(); + } + folderSearch.onkeydown = ev_search_keydown; + + var expand = sread('s_ex') == 'true'; + exp_search(expand); + function exp_search (e) { + clmod(ebi('op_search'), 'vis', e); + + var ms = ebi('moresearch'); + if(e){ + window.scrollTo(0, 0); + ms.innerHTML = ''; + } + else{ + ms.innerHTML = ''; + } + } + ebi('moresearch').onclick = function () { + expand = !expand; + swrite('s_ex', expand); + exp_search(expand); + }; + + ebi('closesearch').onclick = function (e) { + ebi('opa_srch').click(); + folderSearch.value = ''; + if(ebi('unsearch')) + ebi('unsearch').click(); + } + + var recur_c = ebi('srch_recursivec'); + recur_c.oninput = ev_search_input; + ebi('srch_recursivelbl').innerHTML = L.s_rec; + function srch_msg(err, txt) { var o = ebi('srch_q'); @@ -6596,5 +7471,10 @@ } + function onlyUnique(value, index, array) { + return array.indexOf(value) == index && value.length > 0; + } + function encode_query() { + var recursive = ebi('srch_recursivec').checked; var q = ''; for (var a = 0; a < sconf.length; a++) { @@ -6605,6 +7485,21 @@ tvs = []; - if (a == 1) - vs = vs.trim().replace(/ +/, 'T'); + if(k == 'path' && vs.endsWith('/') && !vs.match('"')){ + vs = vs.slice(0, -1); + vs = '"' + vs + (recursive ? '*' : '') + '"'; + tvs.push(vs); + vs = ''; + } + + var match = vs.match(/\B("(?:[^"]|\\")*[^\\]")\B/g); + if(match != null){ + var immune = match.filter(onlyUnique) + for(var i = 0; i < immune.length; i++){ + if(immune[i].length > 0){ + tvs.push(immune[i]); + vs = vs.replace(immune[i], '') + } + } + } while (vs) { @@ -6625,5 +7520,6 @@ } else { - var vp = vs.split(/ +(.*)/); + // split at spaces + var vp = vs.split(/\s+(.*)/); v = vp[0].replace(/\\"/g, '"'); vs = vp[1] || ''; @@ -6635,4 +7531,8 @@ continue; + if(q.length > 0) + q += ' and '; + + q += ' ( '; for (var c = 0; c < tvs.length; c++) { var tv = tvs[c]; @@ -6640,5 +7540,6 @@ break; - q += ' and '; + if(c > 0) + q += ' or '; if (k == 'adv') { @@ -6662,5 +7563,5 @@ tv = tv.slice(1); } - else { + else if (!tv.match(' ') && !tv.startsWith('"')){ tv = '*' + tv; } @@ -6669,18 +7570,29 @@ tv = tv.slice(0, -1); } - else { + else if(!tv.match(' ') && !tv.endsWith('"')){ tv += '*'; } - if (tv.indexOf(' ') + 1) { + if (tv.match(' ') && !tv.match('"')) { tv = '"' + tv + '"'; } + var quote = tv.match('"') ? '"' : ''; + var match; + while ( (match = tv.match(/[^\*\"\s]\*[^\*\"\s]/)) && match[0]){ + console.log(match) + tv = tv.replace( + match[0], + match[0].replace('*', '*' + quote + ' and ' + k + ' like ' + quote + '*') + ) + } + q += not + k + ' like ' + tv; } } + q += ' ) '; } } - ebi('q_raw').value = q.slice(5); + ebi('q_raw').value = q.trim(); } @@ -6729,5 +7641,5 @@ var html = mk_files_header(tagord), seen = {}; html.push(''); - html.push(''); + html.push(''); for (var a = 0; a < res.hits.length; a++) { @@ -6747,6 +7659,6 @@ ext = '%'; - var links = linksplit(r.rp + '', null, id).join('/'), - nodes = ['', - '', + '', + '', '', '' @@ -8205,5 +9314,5 @@ } html = html.concat([ - '', + '', '', '', @@ -8246,9 +9355,4 @@ } - r.uivis = function () { - var hcols = ebi('hcols'); - hcols.previousSibling.style.display = hcols.style.display = ((!thegrid || !thegrid.en) && (hidden.length || MOBILE)) ? 'block' : 'none'; - }; - r.set_style = function (unhide) { hidden.sort(); @@ -8268,5 +9372,4 @@ hcols.innerHTML = html.join('\n'); hcols.onclick = hcols_click; - r.uivis(); r.add_btns(); @@ -8327,24 +9430,26 @@ }; - if (MOBILE) - ebi('hcolsh').onclick = function (e) { - ev(e); - if (r.picking) - return r.unpick(); + ebi('hcolsh').onclick = function (e) { + clmod(this, 'on', !r.picking) + if (r.picking) + return r.unpick(); - var lbs = QSA('#files>thead th'); - for (var a = 0; a < lbs.length; a++) { - lbs[a].onclick = function (e) { - ev(e); - if (toast.tag == 'pickhide') - toast.hide(); + var lbs = QSA('#files>thead th'); + for (var a = 0; a < lbs.length; a++) { + lbs[a].onclick = function (e) { + ev(e); + if (toast.tag == 'pickhide') + toast.hide(); + if(e.target.lastChild) + r.hide(e.target.lastChild.textContent) + else r.hide(e.target.textContent); - }; }; - r.picking = true; - clmod(ebi('files'), 'hhpick', 1); - toast.inf(0, L.cl_hpick, 'pickhide'); }; + r.picking = true; + clmod(ebi('files'), 'hhpick', 1); + toast.inf(0, L.cl_hpick, 'pickhide'); + }; r.unpick = function () { @@ -8503,5 +9608,5 @@ -var light, theme, themen; +var light, theme, themen, override_thm = false; var settheme = (function () { var r = {}, @@ -8511,16 +9616,32 @@ r.ldr = { - '4':['🌴'], - '5':['🌭', 'padding:0 0 .7em .7em;filter:saturate(3)'], - '6':['📞', 'padding:0;filter:brightness(2) sepia(1) saturate(3) hue-rotate(60deg)'], - '7':['▲', 'font-size:3em'], //cp437 + '4': ['🌴'], + '5': ['🌭', 'padding:0 0 .7em .7em;filter:saturate(3)'], + '6': ['📞', 'padding:0;filter:brightness(2) sepia(1) saturate(3) hue-rotate(60deg)'], + '7': ['▲', 'font-size:3em'], //cp437 }; - theme = sread('cpp_thm') || 'a'; - if (!/^[a-x][yz]/.exec(theme)) - theme = dtheme; + r.init = function(t){ + theme = sread('cpp_thm'); + if(!theme) + theme = 'a'; + else + override_thm = true; + if (!/^[a-x][yz]/.exec(theme)) + theme = dtheme; + if(t) + theme = t; - themen = theme.split(/ /)[0]; - light = !!(theme.indexOf('y') + 1); + console.log('theme: ' + theme) + + themen = theme.split(/ /)[0]; + light = !!(theme.indexOf('y') + 1); + + var m = /[?&]theme=([0-9]+)/.exec(sloc0); + if (m) + r.go(parseInt(m[1])); + else + freshen(); + } function freshen() { @@ -8538,10 +9659,10 @@ cb = ebi('themes'), itheme = ax.indexOf(theme[0]) * 2 + (light ? 1 : 0), - names = ['classic dark', 'classic light', 'pm-monokai', 'flat light', 'vice', 'hotdog stand', 'hacker', 'hi-con', 'phi95 dark', 'phi95']; + names = ['flat dark', 'flat light', 'frutiger dark', 'frutiger light', 'vice', 'hotdog stand', 'hacker', 'hi-con', 'phi95 dark', 'phi95', 'poison', 'wing', ]; for (var a = 0; a < themes; a++) html.push(''.format(a, names[a] || 'custom')); - ebi('themes').innerHTML = html.join(''); + cb.innerHTML = html.join(''); cb.value = itheme; cb.onchange = r.onsel; @@ -8557,5 +9678,8 @@ r.onsel = function () { - r.go(parseInt(ebi('themes').value)); + if(!override_thm) + ebi('override_thm').click(); + else + r.go(parseInt(ebi('themes').value)); }; @@ -8570,9 +9694,15 @@ }; - var m = /[?&]theme=([0-9]+)/.exec(sloc0); - if (m) - r.go(parseInt(m[1])); - else - freshen(); + r.init(); + + ebi('override_thm').onclick = function(){ + clmod(this, 'on', 't'); + override_thm = !override_thm; + if(override_thm) + r.onsel(); + else + sdrop('cpp_thm'); + } + clmod(ebi('override_thm'), 'on', override_thm) return r; @@ -8678,10 +9808,10 @@ o.textContent = fmt.split('_')[0]; } - ebi('selzip').textContent = fmt.split('_')[0]; + ebi('selzip').innerHTML = (fun_tgl ? '📦
        ' : '') + fmt.split('_')[0]; ebi('selzip').setAttribute('fmt', arg); QS('#zip1 span').textContent = fmt.split('_')[0]; ebi('zip1').setAttribute("href", - get_evpath() + (dk ? '?k=' + dk + '&': '?') + arg); + get_evpath() + (dk ? '?k=' + dk + '&' : '?') + arg); if (!have_zip) { @@ -8743,5 +9873,5 @@ r.all = []; - var links = QSA('#files tbody td:nth-child(2) a:last-child'), + var links = QSA('#files .flink'), is_srch = !!ebi('unsearch'), vbase = get_evpath(); @@ -8770,6 +9900,5 @@ r.sel.push(item); - if (!is_srch) - links[a].closest('tr').setAttribute('tabindex', '0'); + links[a].closest('tr').setAttribute('tabindex', '0'); } }; @@ -8811,4 +9940,5 @@ clmod(ebi('wtoggle'), 'sel', r.getsel().length); + clmod(ebi('wstack'), 'sel', r.getsel().length); thegrid.loadsel(); fileman.render(); @@ -8821,9 +9951,10 @@ }; r.seltgl = function (e) { - ev(e); + e.stopPropagation(); + var tr = this.parentNode, id = tr2id(tr); - if ((treectl.csel || !thegrid.en || thegrid.sel) && e.shiftKey && r.so && id && r.so != id) { + if ((treectl.csel || !thegrid.en || thegrid.sel || thegrid.tempsel) && e.shiftKey && r.so && id && r.so != id) { var o1 = -1, o2 = -1; for (a = 0; a < r.all.length; a++) { @@ -8861,4 +9992,9 @@ } r.selui(); + + if (thegrid.tempsel && msel.getsel().length == 0){ + thegrid.tempsel = false; + clmod(ebi('gridsel'), 'temp', false); + } }; r.origin_tr = function (tr) { @@ -8873,5 +10009,5 @@ ev(e); r.so = r.pr = null; - var trs = QSA('#files tbody tr'); + var trs = QSA('#files tbody tr:not(.srch_hdr)'); for (var a = 0, aa = trs.length; a < aa; a++) clmod(trs[a], 'sel', fun); @@ -8924,11 +10060,26 @@ }; r.render = function () { - var tds = QSA('#files tbody td+td+td'), + var tds = QSA('#files tbody td+td+td+td'), is_srch = !!ebi('unsearch'); - if (!is_srch) - for (var a = 0, aa = tds.length; a < aa; a++) - tds[a].onclick = r.seltgl; + for (var a = 0, aa = tds.length; a < aa; a++) + tds[a].onclick = r.seltgl; + + var links = QSA('#files .flink') + for (var a = 0, aa = links.length; a < aa; a++) + links[a].onclick = fclick; + + var thumbs = QSA('#files .imgcontainer') + for (var a = 0, aa = thumbs.length; a < aa; a++) + thumbs[a].onclick = fclick1; + + var imgs = QSA('#files img'); + for (var a = 0, aa = imgs.length; a < aa; a++) { + set_loaded(imgs[a], thegrid.thumbs && imgs[a].complete, true); + } + if(window.baguetteBox != undefined) + r.bagit(); + r.selui(true); arcfmt.render(); @@ -8938,8 +10089,117 @@ ebi('selzip').style.display = zipvis; ebi('zip1').style.display = zipvis; + + r.ready = true; } + + r.bagit = function (isrc) { + console.log('init image viewer'); + + if (r.bbox) + baguetteBox.destroy(); + + if(!isrc) + isrc = '#files' + + var br = baguetteBox.run(isrc, { + noScrollbars: true, + duringHide: r.onhide, + afterShow: function () { + r.bbox_opts.refocus = true; + }, + captions: function (g, idx) { + var h = '' + g; + + return '' + (idx + 1) + ' / ' + this.length + ' -- ' + + esc(uricom_dec(h.split('/').pop())) + ''; + }, + onChange: function (i, maxIdx) { + if (this[i].imageElement) { + sethash('g' + this[i].imageElement.getAttribute('id') + getsort()); + } + } + }); + r.bbox_opts = br[1]; + r.bbox = true; + }; + return r; })(); +function fclick1(e) { + e.preventDefault ? e.preventDefault() : e.returnValue = false; + + // pass copy of click event to list entry (can trigger bbox handler) + var td = ebi(this.getAttribute('ref')); + td.dispatchEvent(new MouseEvent('click', { + bubbles: true, + cancelable: true, + clientX: e.clientX, + clientY: e.clientY, + ctrlKey: e.ctrlKey, + shiftKey: e.shiftKey, + altKey: e.altKey, + metaKey: e.metaKey, + })); +} + +function fclick(e, dbl) { + if (e.altKey){ + ev(e); + return; + } + var qhref = this.getAttribute('href'), + href = qhref.split('?')[0], + fid = this.getAttribute('id'), + aplay = ebi('a' + fid), + atext = ebi('t' + fid), + is_txt = atext && !/\.ts$/.test(href) && showfile.getlang(href), + is_img = img_re.test(href), + is_dir = href.endsWith('/'), + is_srch = !!ebi('unsearch'), + in_tree = is_dir && treectl.find(this.textContent.slice(0, -1)), + have_sel = QS('#files tr.sel'), + td = this.closest('td').nextSibling, + tr = td.parentNode; + + if (thegrid.en && (thegrid.sel || thegrid.tempsel) && !dbl && !ctrl(e) || treectl.csel && (e.shiftKey || ctrl(e))) { + ev(e); + msel.seltgl.call(td, e); + if (e.shiftKey) + return thegrid.loadsel(); + clmod(this, 'sel', clgot(tr, 'sel')); + } + else if (in_tree) + in_tree.click(); + + else if (this.hasAttribute('download')) + this.click(); + + else if (aplay && (thegrid.vau || !is_img)) + aplay.click(); + + else if (is_dir) + treectl.reqls(qhref, true); + + else if (!is_img && is_txt && !has(['md', 'htm', 'html'], is_txt)) + atext.click(); + + else if (!is_img && !is_dir && have_sel) + window.open(qhref, '_blank'); + + else { + if (!dbl) + return true; + + setTimeout(function () { + thegrid.sel = true; + }, 1); + thegrid.sel = false; + this.click(); + } + ev(e); +} + (function () { @@ -9180,5 +10440,5 @@ clmod(div, 'raw', 1); div.textContent = "--[ " + name + " ]---------\r\n" + md; - return toast.warn(10, errmsg + (WebAssembly ? 'failed to load marked.js' : 'your browser is too old')); + return toast.warn(3, errmsg + (WebAssembly ? 'failed to load marked.js' : 'your browser is too old')); } @@ -9393,5 +10653,5 @@ ao = td.firstChild, href = noq_href(ao), - isdir = href.endsWith('/'), + isdir = clgot(tr[a], 'dir'), txt = ao.textContent; @@ -9655,4 +10915,32 @@ }; +function scroll_v_to_h (e, o) { + ev(e); + var delta = e.deltaY || e.deltaX; + var maxScrollLeft = o.scrollWidth - o.clientWidth; + + o.scrollLeft = Math.max(0, Math.min(maxScrollLeft, o.scrollLeft + delta)); +} +function keep_right (o) { + o.scrollLeft = o.scrollWidth - o.clientWidth; +} + +ebi('path').addEventListener('wheel', function (e) { + scroll_v_to_h(e, this); +}); + +ebi('ghead').addEventListener('wheel', function (e) { + scroll_v_to_h(e, this); +}); + +ebi('treeh').addEventListener('wheel', function (e) { + scroll_v_to_h(e, this); +}); + +if(FIREFOX && MOBILE){ + // disable glitchy ::after gradient (scrollhint) (2026-04-26) + clmod(ebi('ghead'), 'noa', true); + clmod(ebi('treeh'), 'noa', true); +} var scroll_y = -1; @@ -9721,6 +11009,7 @@ fun(); else - modal.confirm(L.f_bigtxt.format(f2f(sz / 1024 / 1024, 1)), fun, function() { - modal.confirm(L.f_bigtxt2, tfun, null)}); + modal.confirm(L.f_bigtxt.format(f2f(sz / 1024 / 1024, 1)), fun, function () { + modal.confirm(L.f_bigtxt2, tfun, null) + }); return ev(e); @@ -9738,10 +11027,7 @@ var rcm = (function () { - if (MOBILE) - return {enabled: false} - var r = {}; - bcfg_bind(r, 'enabled', 'rcm_en', drcm.charAt(0)=='y'); - bcfg_bind(r, 'double', 'rcm_db', drcm.charAt(1)=='y'); + bcfg_bind(r, 'enabled', 'rcm_en', drcm.charAt(0) == 'y'); + bcfg_bind(r, 'double', 'rcm_db', drcm.charAt(1) == 'y'); var menu = ebi('rcm'); @@ -9758,62 +11044,7 @@ var selFile = jcp(nsFile); - function mktemp(is_dir) { - qsr('#rcm_tmp'); - if (!thegrid.en) { - var row = mknod('tr', 'rcm_tmp', - ''); - QS("#files tbody").appendChild(row); - } - else { - var row = mknod('a', 'rcm_tmp', - ''); - if (is_dir) - row.className = 'dir'; - row.style.display = 'flex'; - QS("#ggrid").appendChild(row); - } - - function sendit(name) { - name = ('' + name).trim(); - if (!name) - return; - var data = new FormData(); - data.set("act", is_dir ? "mkdir" : "new_md"); - data.set("name", name); - - var req = new XHR(); - req.open("POST", get_evpath()); - req.onload = req.onerror = function() { - if (req.status == 405 || req.status == 500) - return toast.err(3, "a " + (is_dir ? "folder" : "file") + " with that name already exists."); - if (req.status < 200 || req.status > 399) - return toast.err(3, "couldn't create " + (is_dir ? "folder" : "file") + ":
        " + esc(req.responseText) + ''); - treectl.goto(); - }; - req.send(data); - } - - var input = ebi("tempname"); - input.onblur = function() { - sendit(input.value); - // Chrome blurs elements when calling remove for some reason - input.onblur = null; - row.remove(); - }; - input.onkeydown = function(e) { - if (e.key == "Enter") - sendit(input.value); - if (e.key == "Enter" || e.key == "Escape") { - input.onblur = null; - row.remove(); - ev(e); - } - }; - input.focus(); - } - var opts = QSA('#rcm a'); for (var i = 0; i < opts.length; i++) { - opts[i].onclick = function(e) { + opts[i].onclick = function (e) { ev(e); switch(e.target.id.slice(1)) { @@ -9828,5 +11059,5 @@ case 'txt': showfile.show(selFile.name); break; case 'md': location = selFile.path + (has(selFile.path, '?') ? '&v' : '?v'); break; - case 'cpl': cliptxt(selFile.url, function() {toast.ok(2, L.clipped)}); break; + case 'cpl': fileman.link(); break; case 'dl': ebi('seldl').click(); break; case 'zip': ebi('selzip').click(); break; @@ -9860,7 +11091,7 @@ file = ref && ref.closest('#files tbody tr'); } - var fa = file && file.children[1].querySelector('a[id]'); + var fa = file && file.querySelector('.flink'); if (fa && fa.id != 'unsearch') { - selFile.no_dsel = clgot(file, "sel"); + selFile.no_dsel = TOUCH || clgot(file, "sel"); clmod(file, "sel", true); selFile.elem = file; @@ -9910,14 +11141,42 @@ shr.innerHTML = has_sel ? L.rc_shs : L.rc_shf; - menu.style.left = x + 5 + 'px'; - menu.style.top = y + 5 + 'px'; + clmod(menu, 'large', TOUCH); + if (TOUCH && target && !forceopen && !thegrid.sel){ + thegrid.tempsel = true; + clmod(ebi('gridsel'), 'temp', true); + return; + } + var vh = document.documentElement.clientHeight; + var vw = document.documentElement.clientWidth; + + var maxh = Math.min(y, vh / 2); + menu.style.maxHeight = 'calc(100% - ' + maxh + 'px - 2em)'; + + if(y > vh / 2){ + // low click => menu upwards + menu.style.top = ''; + menu.style.bottom = (vh - y) + 'px'; + } + else{ + menu.style.top = y + 'px'; + menu.style.bottom = ''; + } + if(x > vw / 2){ + // far right click => menu leftwards + menu.style.left = ''; + menu.style.right = (vw - x) + 'px'; + } + else{ + menu.style.left = x + 'px'; + menu.style.right = ''; + } menu.style.display = 'block'; menu.focus(); } - r.hide = function(force) { + r.hide = function (force) { if (!menu.style.display || (!force && menu.contains(document.activeElement))) return; - if (selFile.elem && !selFile.no_dsel) { + if (selFile.elem && !selFile.no_dsel && !thegrid.tempsel) { clmod(selFile.elem, "sel", false); msel.selui(); @@ -9927,10 +11186,16 @@ } - ebi('wrap').oncontextmenu = function(e) { + var forceopen; + r.show = function (e) { if (!r.enabled || e.shiftKey || (r.double && menu.style.display) || /doc=/.exec(location.search)) { r.hide(true); return true; } + var gfile = thegrid.en && e.target && e.target.closest('#ggrid > a'); + var target = gfile || e.target; + r.hide(true); + if (TOUCH) + forceopen = !gfile || clgot(gfile, 'sel'); if (selFile.elem && !selFile.no_dsel) { clmod(selFile.elem, "sel", false); @@ -9938,9 +11203,154 @@ } ev(e); - var gfile = thegrid.en && e.target && e.target.closest('#ggrid > a'); - show(xscroll() + e.clientX, yscroll() + e.clientY, gfile || e.target, gfile); + show(e.clientX, e.clientY, target, gfile); return false; + } + + ebi('wrap').oncontextmenu = function (e) { + if (!e.target.closest('.normalrcm')) + r.show(e); }; - menu.onblur = function() {setTimeout(r.hide)}; + menu.onblur = function () { setTimeout(r.hide) }; + + return r; +})(); + +var drag = (function() { + var r = {is_dragging: false, no_warn: false}; + bcfg_bind(r, "enabled", "den", true, function() {setTimeout(reload_browser, 1)}); + var current = null, currLink = null; + + r.mktarget = function(elem) { + if (!r.enabled) return; + + elem.ondragenter = elem.ondragleave = elem.ondragover = function(e) { + if (!r.enabled) return; + var elemHref = basenames((elem.tagName == "A" ? elem : elem.querySelector(".flink")).href.split("?")[0]) + if (current == elem || elemHref == get_evpath() || currLink == elemHref) // Prevent folders being dragged into themselves + return; + ev(e); + clmod(elem, "dtarget", e.type != "dragleave"); + }; + + elem.ondrop = function(e) { + if (!r.enabled) return; + ev(e); + if (e.dataTransfer && e.dataTransfer.getData("text") && e.dataTransfer.getData("text").startsWith(window.location.origin)){ + currLink = e.dataTransfer.getData("text"); + console.log("elem.ondrop: " + currLink); + fileman.clip = currLink.split("\n"); + + fileman.cut(); + msel.evsel(); + + if (fileman.clip.length) { + r.is_dragging = true; + (elem.tagName == "TR" ? elem.querySelector("a[id]") : elem).click(); + } + } + else{ + // wrong source location + return + } + }; + } + + r.initfiles = function() { + if (!r.enabled) return; + + var files = QSA("#files tbody tr"); + for (var i = 0; i < files.length; i++) { + var f = files[i]; + f.draggable = true; + f.ondragstart = function(e) { + try{ + currLink = basenames(e.target.querySelector(".flink").href.split("?")); + current = e.target; + r.no_warn = true; + + if(!clgot(current, "sel")) + { + msel.so = msel.pr = null; + var trs = QSA('#files tbody tr'); + for (var a = 0, aa = trs.length; a < aa; a++) + clmod(trs[a], 'sel', false); + clmod(current, "sel", true); + msel.selui(); + } + r.setDataForTransfer(e); + } + catch(ex){ + console.log(e.target) + console.log(ex) + } + }; + f.ondragend = function() { + r.no_warn = false; + }; + + var links = f.querySelectorAll("a"); + for (var j = 0; j < links.length; j++) + links[j].draggable = false; + + if (clgot(f, 'dir')) + r.mktarget(f); + } + }; + + r.initgrid = function() { + if (!r.enabled) return; + + var files = QSA("#ggrid a"); + for (var i = 0; i < files.length; i++) { + var f = files[i]; + f.draggable = true; + f.ondragstart = function(e) { + try{ + var a = ebi(e.target.getAttribute("ref")); + current = a.closest("tr"); + currLink = basenames(a.href.split("?")); + r.no_warn = true; + + if(!clgot(current, "sel")) + { + msel.so = msel.pr = null; + var trs = QSA('#files tbody tr'); + for (var a = 0, aa = trs.length; a < aa; a++) + clmod(trs[a], 'sel', false); + clmod(current, "sel", true); + msel.selui(); + } + r.setDataForTransfer(e); + } + catch(ex){ + console.log(e.target) + console.log(ex) + } + } + f.ondragend = function() { + r.no_warn = false; + }; + + if (clgot(f, 'dir')) + r.mktarget(f); + } + }; + r.setDataForTransfer = function(e){ + var sel = msel.getsel(), + vps = []; + + for (var a = 0; a < sel.length; a++) { + var link = sel[a].vp; + if(!link.startsWith(window.location.origin)) + link = window.location.origin + link; + vps.push(link); + } + + var payload = vps.join('\n') + e.dataTransfer.setData("text", payload); + e.dataTransfer.setData("text/plain", payload); + e.dataTransfer.setData("text/uri-list", payload); + e.dataTransfer.setData("application/copyparty", true); + } return r; @@ -9979,10 +11389,8 @@ var parts = get_evpath().split('/'), - rm = ebi('entree'), ftab = ebi('files'), link = '', o; - while (rm.nextSibling) - rm.parentNode.removeChild(rm.nextSibling); + ebi('path').innerHTML = ""; for (var a = 0; a < parts.length - 1; a++) { @@ -9992,9 +11400,14 @@ o = mknod('a'); o.setAttribute('href', link2); - o.textContent = uricom_dec(parts[a]) || '/'; - ebi('path').appendChild(mknod('i')); + o.textContent = uricom_dec(parts[a]) || (fun_tgl && EMOJI ? '🏠' : 'home'); ebi('path').appendChild(o); + ebi('path').appendChild(mknod('i')); + drag.mktarget(o); } + setTimeout(function () { + ebi('path').scrollLeft = ebi('path').scrollWidth - ebi('path').clientWidth; + }, 100); + reload_mp(); try { showsort(ftab); } catch (ex) { } @@ -10010,6 +11423,6 @@ clmod(ebi(ns[a]), 'hidden', ebi('unsearch')); - if (up2k) - up2k.set_fsearch(); + if(up2k) + wait_set_fsearch(); thegrid.setdirty(); @@ -10017,5 +11430,5 @@ } -(function() { +(function () { var is_selma = false; var dragging = false; @@ -10026,5 +11439,5 @@ var ttimer = null; - var lpdelay = 250; + var lpdelay = 250; var mvthresh = 44; @@ -10033,10 +11446,10 @@ ebi('gfiles').style.removeProperty('pointer-events') ebi('wrap').style.removeProperty('user-select') - + if (selbox) { console.log(selbox) window.getSelection().removeAllRanges(); } - + is_selma = false; dragging = false; @@ -10059,4 +11472,7 @@ var tr = ehidden.closest('tr'); if (tr) clmod(tr, 'sel', m); + + var chk = el.getElementsByTagName('input')[0]; + if(chk) chk.checked = m; } } @@ -10068,8 +11484,24 @@ } + var resizing = false; function sel_start(e) { - if (e.button !== 0 && e.type !== 'touchstart') return; - if (!thegrid.en || !treectl.dsel) return; - if (e.target.closest('#widget,#ops,.opview,.doc')) return; + try{ + if(e.target.id == "reszbar"){ + if(sread('entreed') != "tree"){ + treectl.entree(); + return; + } + resizing = true; + clmod(ebi('tree'), 'resz', true); + return; + } + if (e.button !== 0 && e.type !== 'touchstart') return; + if (!thegrid.en || !treectl.dsel) return; + if (e.target.closest('#widget,#ops,.opview,.doc,#ggrid>a,.modal,.normalrcm,#tree')) return; + } + catch(ex){ + console.log(ex); + return; + } if (e.target.closest('#gfiles')) @@ -10081,7 +11513,7 @@ is_selma = true; ttimer = null; - + if (e.type === 'touchstart') { - ttimer = setTimeout(function() { + ttimer = setTimeout(function () { ttimer = null; start_drag(); @@ -10089,5 +11521,5 @@ } } - + function start_drag() { if (dragging) return; @@ -10100,6 +11532,11 @@ ebi('gfiles').style.pointerEvents = 'none'; } - + function sel_move(e) { + if(resizing){ + var em2px = parseFloat(getComputedStyle(document.body).fontSize); + treectl.settreesize(getpp(e).x / em2px); + return; + } if (!is_selma) return; var pos = getpp(e); @@ -10117,5 +11554,5 @@ if (e.target instanceof Element) fwrap = e.target.closest('#wrap'); - if (fwrap) + if (fwrap) fwrap.style.userSelect = 'none'; else return; @@ -10136,4 +11573,8 @@ function sel_end(e) { + if(resizing){ + resizing = false; + clmod(ebi('tree'), 'resz', false); + } clearTimeout(ttimer); if (dragging && selbox) { @@ -10159,5 +11600,5 @@ window.addEventListener('touchend', sel_end, { passive: true }); - window.addEventListener('dragstart', function(e) { + window.addEventListener('dragstart', function (e) { if (treectl.dsel && (is_selma || dragging)) { if (!QS('body.bbox-open')) @@ -10166,10 +11607,10 @@ }); } - + dsel_init(); })(); -var mpss = (function() { +var mpss = (function () { var r = {}, config, ssint, npaint = 0; @@ -10209,5 +11650,5 @@ var gain = afilt.ssg.gain; var duration = ae.duration || 0; - + var slimit = duration * (config.sthresh / 100); var elimit = duration * (1 - (config.etresh / 100)); @@ -10258,3 +11699,41 @@ treectl.hydrate(); +// https://stackoverflow.com/questions/49084718/how-exactly-add-service-worker-allowed-to-register-service-worker-scope-in-upp +if(PWA){ + import_mjs('sw'); + window.addEventListener('load', function() { + try { + var registration = navigator.serviceWorker.register("/.cpr/w/sw.js", { + scope: "/", + }); + if (registration.installing) { + console.log("Service worker installing"); + } else if (registration.waiting) { + console.log("Service worker installed"); + } else if (registration.active) { + console.log("Service worker active"); + } + } catch (error) { + console.error("Registration failed with " + error); + } + }); +} +else if (!PWA && navigator.serviceWorker && caches){ + var installPrompt = null; + window.addEventListener("beforeinstallprompt", function(e) { + e.preventDefault(); + installPrompt = e; + ebi('inst').removeAttribute("hidden"); + }); + ebi('inst').onclick = function(){ + if (!installPrompt) { + return; + } + var result = installPrompt.prompt(); + console.log("Install prompt was: " + result.outcome); + installPrompt = null; + ebi('inst').setAttribute("hidden", ""); + } +} + J_BRW = 2; diff -NarU2 a/copyparty/web/config.css b/copyparty/web/config.css --- a/copyparty/web/config.css 1970-01-01 00:00:00.000000000 +0000 +++ b/copyparty/web/config.css 2026-07-28 23:09:10.623480436 +0000 @@ -0,0 +1,334 @@ + +html{ + font-family: var(--font-main); +} +body{ + padding-top: 3em; + margin: 0; +} + +#disclaimer{ + background-color: rgb(240, 38, 28); + color: black; + padding: .5em; + text-align: center; +} + +button{ + margin: .1em; + font-size: 1em; + cursor: pointer; +} +button:not(.delBtn){ + min-height: 2.2em; + min-width: 2.2em; + margin: .3em; +} +#copyBtn{ + position: fixed; + font-size: 1.5em; + bottom: .6em; + right: .6em; + z-index: 10; + box-shadow: 1px 15px 20px #0009; +} +button, +.abtn{ + background: var(--a); + border: var(--bg) solid 1px; + border-radius: 7px; + color: var(--bg); + padding: 0 .5em; +} +input{ + font-size: 1em; + margin: .2em; + outline: none !important; + border-color: var(--a-gray); + border-width: 0 0 1px 0; + line-height: 2em; + border-radius: .3em; + max-width: calc(40vw - 2em); + padding-left: .5em; +} +input:focus-visible{ + border-color: var(--a); +} +input[type=number]{ + width: 5em; +} +input[type=checkbox]{ + width: 1.25em; + height: 1.25em; +} +input[type="checkbox"] + label { + vertical-align: top; + margin-top: .1em; + display: inline-block; +} +h2{ + background: var(--bg-u2); + padding: .5em; + font-size: 1.2em; + text-align: center; + border-radius: 5px; + border: var(--a) solid 1px; + margin-bottom: .3em; +} + +#header{ + display: flex; + height: 3em; + color: var(--bg); + background: var(--a); + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 10; +} +#header>*{ + display: inline-block; + font-size: 1.1em; + margin: .2em; + padding: .3em; +} +#header h1{ + font-size: 1.2em; + margin-right: auto; +} +#header a{ + padding: .4em; +} +a{ + border-radius: 5px; +} +a:not(:hover){ + text-decoration: none; +} +.abtn:hover, +button:hover{ + background: color-mix(in srgb, var(--a-dark), var(--a)); +} +a:hover{ + background: color-mix(in srgb, var(--a-dark), transparent); +} + +#wrap{ + padding-bottom: 100px; + max-width: 600px; + margin: auto; +} +.autocomplete:focus{ + position: fixed; + top: .2em; + z-index: 50; + width: 30vw; +} +.autocomplete-list{ + max-height: calc(100% - 5em); + max-width: 600px; + overflow-y: auto; + position: fixed; + top: 3.5em; + z-index: 50; + background: var(--bg); + border: currentColor solid 1px; + border-color: var(--bg-u5); + border-radius: 7px; + box-shadow: 0 5px 10px color-mix(in srgb, currentColor 30%, transparent); +} +#perm-list>div, +#volumes>div, +#flags>div, +.autocomplete-list>div{ + position: relative; + margin: .4em; + border: currentColor solid 1px; + border-color: var(--bg-u5); + border-radius: 5px; + padding: .2em .2em .2em .5em; +} +.autocomplete-list>div{ + cursor: pointer; +} +.autocomplete-active, +.autocomplete-list>div:hover{ + background: #8888; + border-color: var(--a-dark); +} +#flags a, +strong { + font-weight: bold; + color: var(--a); + margin-right: .5em; +} +#flags a { + padding: .6em 0; +} +h3>input{ + display: block; +} + +#users{ + padding: 0 .2em; +} +#uopt>*{ + display: inline-block; + margin-bottom: .5em; + margin-right: .5em; + vertical-align: baseline; +} +#uopt>input[type=checkbox]{ + margin-right: auto; + margin-bottom: 0; + vertical-align: center; +} +.uinp { + margin-top: 1em; + display: inline-block; +} +.inplbl{ + position: absolute; + font-size: .8em; + margin-left: .3em; + opacity: .7; +} +.inlinePrompt{ + display: inline-block; + margin-right: 5px; + min-width: 170px; +} +#users form{ + border: transparent solid 1px; + border-radius: 5px; +} +.dhandle{ + cursor: move; + display: inline-block; + margin: auto 0; + padding: .5em; +} +#users form:has(.dhandle:hover){ + background: var(--bg-u5); +} + +#flags>div{ + cursor: default; + max-width: max-content; +} +strong, +#flags a{ + min-width: 100px; + display: inline-block; +} +.delBtn{ + padding: .05em .1em; + line-height: .8em; + vertical-align: bottom; + font-size: 2.5em; + min-width: .9em; + font-family: initial; +} +.binary input{ + opacity: 0; +} + +#volumes h3{ + display: inline-block; + font-size: .9em; + margin: 1em 0 0 0; +} +#volumes .h_vcfg{ + display: block; +} +#volumes>div>.delBtn{ + position: absolute; + top: 2px; + right: 2px; +} +#volumes p{ + margin: 5px 0; +} + +#perms-modal{ + display: none; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 20; + + padding: 20px; + background-color: #8885; +} +#perms-content{ + max-width: 500px; + margin: auto; + overflow-y: auto; + background: var(--bg); + border: var(--bg-u5) solid 1px; + border-radius: 5px; + position: relative; + padding: 10px; + max-height: calc(100% - 40px); +} +#perms-content p, +#perms-content h2{ + margin-top: 1px; + font-weight: bold; +} +#perms-modal .delBtn{ + /* display: none; */ + position: absolute; + top: 12px; + right: 12px; +} +#m_accept{ + display: none; + float: right; +} +.vis{ + display: block !important; +} +#m_closeArea{ + position: absolute; + inset: 0; +} +#perms-content label{ + margin-right: 15px; +} + + +/* mobile layout */ +@media (max-width: 650px){ + html{ + font-size: 1.2em; + } + #wrap{ + margin: 0 1em; + } + .autocomplete-list{ + max-width: calc(100% - 2.9em); + left: 1.5em; + } + strong{ + padding: .6em 0; + } + .autocomplete:focus{ + left: 10em; + width: calc(100% - 13em); + } + .dhandle{ + display: none; + } +} +@media (min-width: 950px){ + .autocomplete:focus{ + width: 585px; + left: calc(50% - 300px); + right: auto; + max-width: none; + } +} diff -NarU2 a/copyparty/web/config.html b/copyparty/web/config.html --- a/copyparty/web/config.html 1970-01-01 00:00:00.000000000 +0000 +++ b/copyparty/web/config.html 2026-07-28 23:09:10.623565066 +0000 @@ -0,0 +1,158 @@ + + + + + + {{ s_doctitle }} + + + + + + + +{{ html_head }} + + + + + + + +
        + ⚠️ this page is a preview feature! this is only a config builder and doesn't use your actual config atm. feedback is welcome in the copyparty discord. ⚠️ +
        +
        + +
        +

        run prefix

        + +
        + +
        +

        server name

        + +
        + +
        +

        port

        + +
        + +
        +

        global settings

        +
        +

        + add launch argument

        + +
        + +

        shared volumes

        +
        + + +

        users

        +
        + + + + + + +
        + + +
        +
        +
        + + +

        groups

        +
        + + +

        display defaults

        +
        + + + +
        + + +
        + +
        + + + +
        + +
        + + +
        +
        + + +
        +
        + + +
        +
        + +
        +

        database options

        + + +
        + + +

        you can find more related options and explanations by searching for "e2" in the global settings

        +
        + +

        save / load

        +

        your changes are saved automatically

        +

        saving / loading the real server config is currently not implemented, but you can import an existing config command below

        + + +
        + + +
        +
        +
        +
        +

        permissions for volume

        +
        + + +
        +
        + + + + + {%- if js %} + + {%- endif %} + + + + diff -NarU2 a/copyparty/web/config.js b/copyparty/web/config.js --- a/copyparty/web/config.js 1970-01-01 00:00:00.000000000 +0000 +++ b/copyparty/web/config.js 2026-07-28 23:09:10.623907873 +0000 @@ -0,0 +1,1794 @@ +/* ToDo: +get searchable commands on demand by searching via xhr, like file search, +OR hand over this list generated from within httpcli.py or something +(remove the hard-coded arg lists in this file afterwards) + +read current config and hand over from httpcli.py on page load or on button press + +import from command string(?) + +export: better indication / user guidance + +add config for user groups + +indicate that users without pw are usable for external identity providers, possibly linking it to the setting that enables idps + +require usernames checkbox setting + +finally, remove the disclaimer banner +*/ + +"use strict"; + +var J_CFG = 1; + +class Argument { + constructor(cmd, help) { + this.cmd = cmd; + this.help = help; + } +} + +/* FLAGS update instructions + +to update manually from the documentation: +copy what makes sense from the cli help page +https://copyparty.eu/cli/ + +open VS code search +filter for file: flags* + +replace +^[a-z].* +with nothing + +replace (careful there are spaces after the \n that you need to copy too) +\n +with nothing + +replace (optional) +\n\n +with +\n +(twice) + +replace +\\ +with +\\\\ + +replace +\$ +with +\\\$ + +replace +" +with +\\" + +replace +^\s*(-\S+).*?\s{2,}(.*)$ +with + new Argument("$1", "$2"), + +*/ + +var flags = [ + new Argument("-c", "REPEATABLE: add config file (default: [])"), + new Argument("-nc", "max num clients (default: 1024)"), + new Argument("-a", "REPEATABLE: add account, USER:PASS; example [ed:wark] (default: None)"), + new Argument("-v", "REPEATABLE: add volume, SRC:DST:FLAG; examples [.::r], [/mnt/nas/music:/music:r:aed], see --help-accounts (default: None)"), + new Argument("--grp", "REPEATABLE: add group, NAME:USER1,USER2,...; example [admins:ed,foo,bar] (default: None)"), + new Argument("--usernames", "require username and password for login; default is just password (default: False)"), + new Argument("--chdir", "change working-directory to PATH before mapping volumes (default: None)"), + new Argument("-ed", "enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files (volflag=dots) (default: False)"), + new Argument("--urlform", "how to handle url-form POSTs; see --help-urlform (default: print,xm)"), + new Argument("--wintitle", "server terminal title, for example [\$ip-10.1.2.] or [\$ip-] (default: cpp @ \$pub)"), + new Argument("--name", "server name (displayed topleft in browser and in mDNS) (default: hostname)"), + new Argument("--name-url", "URL for server name hyperlink (displayed topleft in browser) (default: None)"), + new Argument("--site", "public URL to assume when creating links; example: [https://example.com/] (default: )"), + new Argument("--env-expand", "expand environment-variables in config-files? [0]=no, [1]=\$VAR (old scary syntax), [2]=\${VAR} (new recommended syntax); default is new-syntax with panic if old-syntax is seen (default: -1)"), + new Argument("--mime", "REPEATABLE: map file EXTension to MIMEtype, for example [jpg=image/jpeg] (default: None)"), + new Argument("--mimes", "list default mimetype mapping and exit (default: False)"), + new Argument("--rmagic", "do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic) (default: False)"), + new Argument("-j", "num cpu-cores for uploads/downloads (0=all); keeping the default is almost always best (default: 1)"), + new Argument("--reload-sig", "reload server config when unix-signal S is received; examples: [SIGUSR1], [USR1], [10] (default: USR1)"), + new Argument("--vc-url", "URL to check for vulnerable versions (default-disabled) (default: )"), + new Argument("--vc-age", "how many hours to wait between vulnerability checks (default: 3)"), + new Argument("--vc-sev", "minimum severity to care about; one of these: low medium high critical (default: low)"), + new Argument("--vc-exit", "panic and exit if current version is vulnerable (default: False)"), + new Argument("--license", "show licenses and exit (default: False)"), + new Argument("--version", "show versions and exit (default: False)"), + new Argument("--versionb", "show version and exit (default: False)"), + new Argument("-i", "IPs and/or unix-sockets to listen on (comma-separated list; see --help-bind). Default: all IPv4 and IPv6 (default: ::)"), + new Argument("-p", "ports to listen on (comma/range); ignored for unix-sockets (default: 3923)"), + new Argument("--ll", "include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients) (default: False)"), + new Argument("--rproxy", "which ip to associate clients with; [0]=tcp, [1]=origin (first x-fwd, unsafe), [-1]=closest-proxy, [-2]=second-hop, [-3]=third-hop (default: 9999999)"), + new Argument("--xff-hdr", "if reverse-proxied, which http header to read the client's real ip from (default: x-forwarded-for)"), + new Argument("--xf-host", "if reverse-proxied, which http header to read the correct Host value from; this header must contain the server's external domain name (default: x-forwarded-host)"), + new Argument("--xf-proto", "if reverse-proxied, which http header to read the correct protocol value from; this header must contain either 'http' or 'https' (default: x-forwarded-proto)"), + new Argument("--xf-proto-fb", "protocol to assume if the X-Forwarded-Proto header (--xf-proto) is not provided by the reverseproxy; either 'http' or 'https' (default: )"), + new Argument("--xff-src", "list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (--xff-hdr) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [lan] to allow all LAN / private / non-internet IPs. Can be disabled with [any] if you are behind cloudflare (or similar) and are using --xff-hdr=cf-connecting-ip (or similar) (default: 127.0.0.0/8, ::1/128)"), + new Argument("--ipa", "only accept connections from IP-addresses inside CIDR (comma-separated); examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] └─for performance and security, this only looks at the TCP/Network-level IP, and will NOT work behind a reverseproxy (default: )"), + new Argument("--ipar", "only accept connections from IP-addresses inside CIDR (comma-separated). └─this is reverseproxy-compatible; reads client-IP from 'X-Forwarded-For' if possible, with TCP/Network IP as fallback (default: )"), + new Argument("--rp-loc", "if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [/foo/bar] (default: )"), + new Argument("--cachectl", "default-value of the 'Cache-Control' response-header (controls caching in webbrowsers). Default prevents repeated downloading of the same file unless necessary (browser will ask copyparty if the file has changed). Examples: [max-age=604869] will cache for 7 days, [no-store, max-age=0] will always redownload. (volflag=cachectl) (default: no-cache)"), + new Argument("--http-vary", "value of the 'Vary' response-header; a hint for caching proxies (default: Origin, PW, Cookie)"), + new Argument("--http-no-tcp", "do not listen on TCP/IP for http/https; only listen on unix-domain-sockets (default: False)"), + new Argument("--freebind", "allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind) (default: False)"), + new Argument("--wr-h-eps", "write list of listening-on ip:port to textfile at PATH when http-servers have started (default: )"), + new Argument("--wr-h-aon", "write list of accessible-on ip:port to textfile at PATH when http-servers have started (default: )"), + new Argument("--s-thead", "socket timeout (read request header) (default: 120)"), + new Argument("--s-tbody", "socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2% speed boost (default: 128.0)"), + new Argument("--s-rd-sz", "socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than --iobuf) (default: 262144)"), + new Argument("--s-wr-sz", "socket write size in bytes (default: 262144)"), + new Argument("--s-wr-slp", "debug: socket write delay in seconds (default: 0.0)"), + new Argument("--rsp-slp", "debug: response delay in seconds (default: 0.0)"), + new Argument("--rsp-jtr", "debug: response delay, random duration 0..SEC (default: 0.0)"), + new Argument("--list-nics", "debug: list detected network adapters (default: False)"), + new Argument("--list-ips", "debug: list detected LAN IPs (default: False)"), + new Argument("--http-only", "disable ssl/tls -- force plaintext (default: False)"), + new Argument("--https-only", "disable plaintext -- force tls (default: False)"), + new Argument("--cert", "path to file containing a concatenation of TLS key and certificate chain (if --certkey is not set), or just the certificate chain (if --certkey is set) (default: ~/.config/copyparty/cert.pem)"), + new Argument("--certkey", "path to file containing just the certificate key; if this is set, then --cert should only contain the certificate chain (default: )"), + new Argument("--ssl-ver", "set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe (default: )"), + new Argument("--ciphers", "set allowed ssl/tls ciphers; [help] shows available ciphers (default: )"), + new Argument("--ssl-dbg", "dump some tls info (default: False)"), + new Argument("--ssl-log", "log master secrets for later decryption in wireshark (default: )"), + new Argument("--no-crt", "disable automatic certificate creation (default: False)"), + new Argument("--crt-ns", "comma-separated list of FQDNs (domains) to add into the certificate (default: )"), + new Argument("--crt-exact", "do not add wildcard entries for each --crt-ns (default: False)"), + new Argument("--crt-noip", "do not add autodetected IP addresses into cert (default: False)"), + new Argument("--crt-nolo", "do not add 127.0.0.1 / localhost into cert (default: False)"), + new Argument("--crt-nohn", "do not add mDNS names / hostname into cert (default: False)"), + new Argument("--crt-dir", "where to save the CA cert (default: ~/.config/copyparty)"), + new Argument("--crt-cdays", "ca-certificate expiration time in days (default: 3650.0)"), + new Argument("--crt-sdays", "server-cert expiration time in days (default: 365.0)"), + new Argument("--crt-cn", "CA/server-cert common-name (default: partyco)"), + new Argument("--crt-cnc", "override CA name (default: --crt-cn)"), + new Argument("--crt-cns", "override server-cert name (default: --crt-cn cpp)"), + new Argument("--crt-back", "backdate in hours (default: 72.0)"), + new Argument("--crt-alg", "algorithm and keysize; one of these: ecdsa-256 rsa-4096 rsa-2048 (default: ecdsa-256)"), + new Argument("--idp-h-usr", "REPEATABLE: bypass the copyparty authentication checks if the request-header HN contains a username to associate the request with (for use with authentik/oauth/...)WARNING: if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy (default: None)"), + new Argument("--idp-hm-usr", "REPEATABLE: bypass the copyparty authentication checks if the request-header T is provided, and its value exists in a mapping defined by this option; see --help-idp (default: None)"), + new Argument("--idp-h-grp", "assume the request-header HN contains the groupname of the requesting user; can be referenced in config files for group-based access control (default: )"), + new Argument("--idp-h-key", "optional but recommended safeguard; your reverse-proxy will insert a secret header named HN into all requests, and the other IdP headers will be ignored if this header is not present (default: )"), + new Argument("--idp-gsep", "if there are multiple groups in --idp-h-grp, they are separated by one of the characters in RE (default: |:;+,)"), + new Argument("--idp-chsub", "characters to replace in usernames/groupnames; a list of pairs of characters separated by | so for example | _| will replace spaces with _ to make configuration easier, or |%_|^_|@_| will replace %/^/@ with _ (default: )"), + new Argument("--idp-db", "where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs) (default: ~/.config/copyparty/idp.db)"), + new Argument("--idp-store", "how to use --idp-db; [0] = entirely disable, [1] = write-only (effectively disabled), [2] = remember users, [3] = remember users and groups.NOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP (default: 1)"), + new Argument("--idp-adm", "comma-separated list of users allowed to use /?idp (the cache management UI) (default: )"), + new Argument("--idp-cookie", "generate a session-token for IdP users which is written to cookie cppws (or cppwd if plaintext), to reduce the load on the IdP server, lifetime S seconds. └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with --ses-db wiped) (default: 0)"), + new Argument("--idp-login", "replace all login-buttons with a link to URL L (unless pw is in --auth-ord then both will be shown); [{dst}] expands to url of current page (default: )"), + new Argument("--idp-login-t", "the label/text for the idp-login button (default: Login with SSO)"), + new Argument("--idp-logout", "replace all logout-buttons with a link to URL L (default: )"), + new Argument("--auth-ord", "controls auth precedence; examples: [pw,idp,ipu], [ipu,pw,idp], see --help-auth-ord (default: idp,ipu)"), + new Argument("--pw-hdr", "lowercase name of password-header (NAME: foo); WARNING: Changing this will break support for many clients (default: pw)"), + new Argument("--pw-urlp", "lowercase name of password url-param (?NAME=foo); WARNING: Changing this will break support for many clients (default: pw)"), + new Argument("--no-bauth", "disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app (default: False)"), + new Argument("--bauth-last", "keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins (default: False)"), + new Argument("--ses-db", "where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs) (default: ~/.config/copyparty/sessions.db)"), + new Argument("--ses-len", "session key length; default is 120 bits ((20//4)*4*6) (default: 20)"), + new Argument("--no-ses", "disable sessions; use plaintext passwords in cookies (default: False)"), + new Argument("--grp-all", "the name of the auto-generated group which contains every username which is known (default: acct)"), + new Argument("--ipu", "REPEATABLE: users with IP matching CIDR are auto-authenticated as username USR; example: [172.16.24.0/24=dave] (default: None)"), + new Argument("--ipr", "REPEATABLE: username USR can only connect from an IP matching one or more CIDR (comma-sep.); example: [192.168.123.0/24,172.16.0.0/16=dave] (default: None)"), + new Argument("--chpw", "allow users to change their own passwords (default: False)"), + new Argument("--chpw-no", "REPEATABLE: do not allow password-changes for this comma-separated list of usernames (default: None)"), + new Argument("--chpw-db", "where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs) (default: ~/.config/copyparty/chpw.json)"), + new Argument("--chpw-len", "minimum password length (default: 8)"), + new Argument("--chpw-v", "verbosity of summary on config load [0] = nothing at all, [1] = number of users, [2] = list users with default-pw, [3] = list all users (default: 2)"), + new Argument("--qr", "show QR-code on startup (default: False)"), + new Argument("--qrs", "change the QR-code URL to https:// (default: False)"), + new Argument("--qrl", "location to include in the url, for example [priv/?pw=hunter2] (default: )"), + new Argument("--qri", "select IP which starts with PREFIX; [.] to force default IP when mDNS URL would have been used instead (default: )"), + new Argument("--qr-fg", "foreground; try [0] or [-1] if the qr-code is unreadable (default: 16)"), + new Argument("--qr-bg", "background (white=255) (default: 229)"), + new Argument("--qrp", "padding (spec says 4 or more, but 1 is usually fine) (default: 4)"), + new Argument("--qrz", "[1]=1x, [2]=2x, [0]=auto (try [2] on broken fonts) (default: 0)"), + new Argument("--qr-pin", "sticky/pin the qr-code to always stay on-screen; [0]=disabled, [1]=with-url, [2]=just-qr (default: 0)"), + new Argument("--qr-wait", "wait SEC before printing the qr-code to the log (default: 0)"), + new Argument("--qr-every", "print the qr-code every SEC (try this with/without --qr-pin in case of issues) (default: 0)"), + new Argument("--qr-winch", "when --qr-pin is enabled, check for terminal size change every SEC (default: 0)"), + new Argument("--qr-file", "REPEATABLE: write qr-code to file. └─To create txt or svg, TXT is Filepath:Zoom:Pad, for example [qr.txt:1:2] └─To create png or gif, TXT is Filepath:Zoom:Pad:Foreground:Background, for example [qr.png:8:2:333333:ffcc55], or [qr.png:8:2::ffcc55] for transparent (default: None)"), + new Argument("--qr-stdout", "always display the QR-code on STDOUT in the terminal, even if -q (default: False)"), + new Argument("--qr-stderr", "always display the QR-code on STDERR in the terminal, even if -q (default: False)"), + new Argument("-z", "enable all zeroconf backends (mdns, ssdp) (default: False)"), + new Argument("--z-on", "enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes └─example: eth0, wlo1, virhost0, 192.168.123.0/24, fd00:fda::/96 (default: )"), + new Argument("--z-off", "disable zeroconf on the comma-separated list of subnets and/or interface names/indexes (default: )"), + new Argument("--z-chk", "check for network changes every SEC seconds (0=disable) (default: 10)"), + new Argument("-zv", "verbose all zeroconf backends (default: False)"), + new Argument("--mc-hop", "rejoin multicast groups every SEC seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [300] or [180] └─note: can be due to firewalls; make sure UDP port 5353 is open in both directions (on clients too) (default: 0)"), + new Argument("--zm", "announce the enabled protocols over mDNS (multicast DNS-SD) -- compatible with KDE, gnome, macOS, ... (default: False)"), + new Argument("--zm-on", "enable mDNS ONLY on the comma-separated list of subnets and/or interface names/indexes (default: )"), + new Argument("--zm-off", "disable mDNS on the comma-separated list of subnets and/or interface names/indexes (default: )"), + new Argument("--zm4", "IPv4 only -- try this if some clients can't connect (default: False)"), + new Argument("--zm6", "IPv6 only (default: False)"), + new Argument("--zmv", "verbose mdns (default: False)"), + new Argument("--zmvv", "verboser mdns (default: False)"), + new Argument("--zm-http", "port to announce for http/webdav; [-1] = auto, [0] = disabled, [4649] = port 4649 (default: -1)"), + new Argument("--zm-https", "port to announce for https/webdavs; [-1] = auto, [0] = disabled, [4649] = port 4649 (default: -1)"), + new Argument("--zm-no-pe", "mute parser errors (invalid incoming MDNS packets) (default: False)"), + new Argument("--zm-nwa-1", "disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature) (default: False)"), + new Argument("--zms", "list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (Ddfs if --ftp and --smb is set, Dd otherwise) (default: )"), + new Argument("--zm-ld", "link a specific folder for webdav shares (default: )"), + new Argument("--zm-lh", "link a specific folder for http shares (default: )"), + new Argument("--zm-lf", "link a specific folder for ftp shares (default: )"), + new Argument("--zm-ls", "link a specific folder for smb shares (default: )"), + new Argument("--zm-fqdn", "the domain to announce; NOTE: using anything other than .local is nonstandard and could cause problems (default: --name.local)"), + new Argument("--zm-mnic", "merge NICs which share subnets; assume that same subnet means same network (default: False)"), + new Argument("--zm-msub", "merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working, and clients cannot be in subnets that the server is not (default: False)"), + new Argument("--zm-noneg", "disable NSEC replies -- try this if some clients don't see copyparty (default: False)"), + new Argument("--zm-spam", "send unsolicited announce every SEC; useful if clients have IPs in a subnet which doesn't overlap with the server, or to avoid some firewall issues (default: 0.0)"), + new Argument("--zs", "announce the enabled protocols over SSDP -- compatible with Windows (default: False)"), + new Argument("--zs-on", "enable SSDP ONLY on the comma-separated list of subnets and/or interface names/indexes (default: )"), + new Argument("--zs-off", "disable SSDP on the comma-separated list of subnets and/or interface names/indexes (default: )"), + new Argument("--zsv", "verbose SSDP (default: False)"), + new Argument("--zsl", "location to include in the url (or a complete external URL), for example [priv/?pw=hunter2] (goes directly to /priv/ with password hunter2) or [?hc=priv&pw=hunter2] (shows mounting options for /priv/ with password) (default: /?hc)"), + new Argument("--zsid", "USN (device identifier) to announce (default: autogenerated)"), + new Argument("--casechk", "detect and prevent CI (case-insensitive) behavior if the underlying filesystem is CI? [y] = detect and prevent, [n] = ignore and allow, [auto] = y if CI fs detected. NOTE: y is very slow but necessary for correct WebDAV behavior on Windows/Macos (volflag=casechk) (default: auto)"), + new Argument("--fsnt", "which characters to allow in file/folder names; [win] = windows (not <>:|?*\"\\/), [mac] = macos (not :), [lin] = linux (anything goes) (volflag=fsnt) (default: auto)"), + new Argument("--rm-retry", "if a file cannot be deleted because it is busy, continue trying for T seconds, retry every R seconds; disable with 0/0 (volflag=rm_retry) (default: 0/0)"), + new Argument("--mv-retry", "if a file cannot be renamed because it is busy, continue trying for T seconds, retry every R seconds; disable with 0/0 (volflag=mv_retry) (default: 0/0)"), + new Argument("--iobuf", "file I/O buffer-size; if your volumes are on a network drive, try increasing to 524288 or even 4194304 (and let me know if that improves your performance) (default: 262144)"), + new Argument("--mtab-age", "rebuild mountpoint cache every SEC to keep track of sparse-files support; keep low on servers with removable media (default: 60)"), + new Argument("--shr", "toplevel virtual folder for shared files/folders, for example [/share] (default: )"), + new Argument("--shr-db", "database to store shares in (default: ~/.config/copyparty/shares.db)"), + new Argument("--shr-who", "who can create a share? [no]=nobody, [a]=admin-permission, [auth]=authenticated (volflag=shr_who) (default: auth)"), + new Argument("--shr-adm", "comma-separated list of users allowed to view/delete any share (default: )"), + new Argument("--shr-rt", "shares can be revived by their owner if they expired less than MIN minutes ago; [60]=hour, [1440]=day, [10080]=week (default: 1440)"), + new Argument("--shr-site", "public URL to assume when creating share-links; example: [https://example.com/] (default: --site)"), + new Argument("--shr-v", "debug (default: False)"), + new Argument("--dotpart", "dotfile incomplete uploads, hiding them from clients unless -ed (default: False)"), + new Argument("--plain-ip", "when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip (default: False)"), + new Argument("--up-site", "public URL to assume when creating links to uploaded files; example: [https://example.com/] (default: --site)"), + new Argument("--put-name", "filename for nameless uploads (when uploader doesn't provide a name); default is [put-UNIXTIME-IP.bin] (the .6f means six decimal places) (volflag=put_name) (default: put-{now.6f}-{cip}.bin)"), + new Argument("--put-ck", "default checksum-hasher for PUT/WebDAV uploads: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=put_ck) (default: sha512)"), + new Argument("--bup-ck", "default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=bup_ck) (default: sha512)"), + new Argument("--unpost", "grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h (default: 43200)"), + new Argument("--unp-who", "clients can undo recent uploads by using the unpost tab (requires -e2d). [0] = never allowed (disable feature), [1] = allow if client has the same IP as the upload AND is using the same account, [2] = just check the IP, [3] = just check account-name (volflag=unp_who) (default: 1)"), + new Argument("--apnd-who", "who can append to existing files? [no]=nobody, [aw]=admin+write, [dw]=delete+write, [w]=write (volflag=apnd_who) (default: dw)"), + new Argument("--u2abort", "clients can abort incomplete uploads by using the unpost tab (requires -e2d). [0] = never allowed (disable feature), [1] = allow if client has the same IP as the upload AND is using the same account, [2] = just check the IP, [3] = just check account-name (volflag=u2abort) (default: 1)"), + new Argument("--blank-wt", "file write grace period (any client can write to a blank file last-modified more recently than SEC seconds ago) (default: 300)"), + new Argument("--reg-cap", "max number of uploads to keep in memory when running without -e2d; roughly 1 MiB RAM per 600 (default: 38400)"), + new Argument("--no-fpool", "disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems) (default: False)"), + new Argument("--use-fpool", "force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...) (default: False)"), + new Argument("--chmod-f", "unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [644] = owner-RW + all-R, [755] = owner-RWX + all-RX, [777] = full-yolo (volflag=chmod_f) (default: )"), + new Argument("--chmod-d", "unix file permissions to use when creating directories; see --help-chmod. Examples: [755] = owner-RW + all-R, [2750] = setgid + owner-RW + group-R, [777] = full-yolo (volflag=chmod_d) (default: 755)"), + new Argument("--uid", "unix user-id to chown new files/folders to; default = -1 = do-not-change (volflag=uid) (default: -1)"), + new Argument("--gid", "unix group-id to chown new files/folders to; default = -1 = do-not-change (volflag=gid) (default: -1)"), + new Argument("--wram", "allow uploading even if a volume is inside a ramdisk, meaning that all data will be lost on the next server reboot (volflag=wram) (default: False)"), + new Argument("--dedup", "enable symlink-based upload deduplication (volflag=dedup) (default: False)"), + new Argument("--safe-dedup", "how careful to be when deduplicating files; [1] = just verify the filesize, [50] = verify file contents have not been altered (volflag=safededup) (default: 50)"), + new Argument("--hardlink", "enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink) (default: False)"), + new Argument("--hardlink-only", "do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly) (default: False)"), + new Argument("--reflink", "enable reflink-based dedup; will fallback on full copies when that is impossible (non-CoW filesystem) (volflag=reflink) (default: False)"), + new Argument("--no-dupe", "reject duplicate files during upload; only matches within the same volume (volflag=nodupe) (default: False)"), + new Argument("--no-dupe-m", "also reject dupes when moving a file into another volume (volflag=nodupem) (default: False)"), + new Argument("--no-clone", "do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load (volflag=noclone) (default: False)"), + new Argument("--no-snap", "disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually (default: False)"), + new Argument("--snap-wri", "write upload state to ./hist/up2k.snap every SEC seconds; allows resuming incomplete uploads after a server crash (default: 300)"), + new Argument("--snap-drop", "forget unfinished uploads after MIN minutes; impossible to resume them after that (360=6h, 1440=24h) (default: 1440.0)"), + new Argument("--rm-partial", "delete the .PARTIAL file when an unfinished upload expires after --snap-drop (volflag=rm_partial) (default: False)"), + new Argument("--u2ts", "how to timestamp uploaded files; [c]=client-last-modified, [u]=upload-time, [fc]=force-c, [fu]=force-u (volflag=u2ts) (default: c)"), + new Argument("--rotf-tz", "default timezone for the rotf upload rule; examples: [Europe/Oslo], [America/Toronto], [Antarctica/South_Pole] (volflag=rotf_tz) (default: UTC)"), + new Argument("--rand", "force randomized filenames, --nrand chars long (volflag=rand) (default: False)"), + new Argument("--nrand", "randomized filenames length (volflag=nrand) (default: 9)"), + new Argument("--magic", "enable filetype detection on nameless uploads (volflag=magic) (default: False)"), + new Argument("--df", "ensure GiB free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [256m], [4], [2T] (volflag=df) (default: 0)"), + new Argument("--sparse", "windows-only: minimum size of incoming uploads through up2k before they are made into sparse files (default: 4)"), + new Argument("--turbo", "configure turbo-mode in up2k client; [-1] = forbidden/always-off, [0] = default-off and warn if enabled, [1] = default-off, [2] = on, [3] = on and disable datecheck (default: 0)"), + new Argument("--nosubtle", "when to use a wasm-hasher instead of the browser's builtin; faster on chrome, but buggy in older chrome versions. [0] = only when necessary (non-https), [1] = always (all browsers), [2] = always on chrome/firefox, [3] = always on chrome, [N] = chrome-version N and newer (recommendation: 137) (default: 0)"), + new Argument("--u2j", "web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed (default: 2)"), + new Argument("--u2sz", "web-client: default upload chunksize (MiB); sets min,default,max in the settings gui. Each HTTP POST will aim for default, and never exceed max. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [1,1,1] (default: 1,64,96)"), + new Argument("--u2ow", "web-client: default setting for when to replace/overwrite existing files; [0]=never, [1]=if-client-newer, [2]=always (volflag=u2ow) (default: 0)"), + new Argument("--u2sort", "upload order; [s]=smallest-first, [n]=alphabetical, [fs]=force-s, [fn]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine (default: s)"), + new Argument("--write-uplog", "write POST reports to textfiles in working-directory (default: False)"), + new Argument("-e2d", "enable up2k database; this enables file search, upload-undo, improves deduplication (default: False)"), + new Argument("-e2ds", "scan writable folders for new files on startup; sets -e2d (default: False)"), + new Argument("-e2dsa", "scans all folders on startup; sets -e2ds (default: False)"), + new Argument("-e2v", "verify file integrity; rehash all files and compare with db (default: False)"), + new Argument("-e2vu", "on hash mismatch: update the database with the new hash (default: False)"), + new Argument("-e2vp", "on hash mismatch: panic and quit copyparty (default: False)"), + new Argument("--hist", "where to store volume data (db, thumbs); default is a folder named \".hist\" inside each volume (volflag=hist) (default: )"), + new Argument("--dbpath", "override where the volume databases are to be placed; default is the same as --hist (volflag=dbpath) (default: )"), + new Argument("--fika", "list of user-actions to allow while filesystem-indexer is still busy; set blank to never interrupt indexing (old default). [u]=uploads, [c]=filecopy, [m]=move/rename, [d]=delete. NOTE: [m] is untested/scary, and blank is recommended if dedup enabled (default: ucd)"), + new Argument("--no-hash", "regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (must be specified as one big regex, not multiple times) (volflag=nohash) (default: )"), + new Argument("--no-idx", "regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scan (must be specified as one big regex, not multiple times) (volflag=noidx) (default: )"), + new Argument("--no-dirsz", "do not show total recursive size of folders in listings, show inode size instead; slightly faster (volflag=nodirsz) (default: False)"), + new Argument("--re-dirsz", "if the directory-sizes in the UI are bonkers, use this along with -e2dsa to rebuild the index from scratch (default: False)"), + new Argument("--no-dhash", "disable rescan acceleration; do full database integrity check -- makes the db ~5% smaller and bootup/rescans 3~10x slower (default: False)"), + new Argument("--re-dhash", "force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary) (default: False)"), + new Argument("--no-forget", "never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget) (default: False)"), + new Argument("--forget-ip", "remove uploader-IP from database (and make unpost impossible) MIN minutes after upload, for GDPR reasons. Default [0] is never-forget. [1440]=day, [10080]=week, [43200]=month. (volflag=forget_ip) (default: 0)"), + new Argument("--dbd", "database durability profile; sets the tradeoff between robustness and speed, see --help-dbd (volflag=dbd) (default: wal)"), + new Argument("--xlink", "on upload: check all volumes for dupes, not just the target volume (probably buggy, not recommended) (volflag=xlink) (default: False)"), + new Argument("--hash-mt", "num cpu cores to use for file hashing; set 0 or 1 for single-core hashing (default: numCores if 5 or less)"), + new Argument("--re-maxage", "rescan filesystem for changes every SEC seconds; 0=off (volflag=scan) (default: 0)"), + new Argument("--db-act", "defer any scheduled volume reindexing until SEC seconds after last db write (uploads, renames, ...) (default: 10.0)"), + new Argument("--srch-icase", "case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 4x), and NOTE: only applies to filenames/paths, not tags (default: False)"), + new Argument("--srch-time", "search deadline -- terminate searches running for more than SEC seconds (default: 45)"), + new Argument("--srch-hits", "max search results to allow clients to fetch; 125 results will be shown initially (default: 7999)"), + new Argument("--srch-excl", "regex: exclude files from search results if the file-URL matches PTN (case-sensitive). Example: [password|logs/[0-9]] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl) (default: )"), + new Argument("--dotsrch", "show dotfiles in search results (volflags: dotsrch | nodotsrch) (default: False)"), + new Argument("-e2t", "enable metadata indexing; makes it possible to search for artist/title/codec/resolution/... (default: False)"), + new Argument("-e2ts", "scan newly discovered files for metadata on startup; sets -e2t (default: False)"), + new Argument("-e2tsr", "delete all metadata from DB and do a full rescan; sets -e2ts (default: False)"), + new Argument("--no-mutagen", "use FFprobe for tags instead; will detect more tags (default: False)"), + new Argument("--no-mtag-ff", "never use FFprobe as tag reader; is probably safer (default: False)"), + new Argument("--mtag-to", "timeout for FFprobe tag-scan (default: 60)"), + new Argument("--mtag-mt", "num cpu cores to use for tag scanning (default: numCores)"), + new Argument("--mtag-v", "verbose tag scanning; print errors from mtp subprocesses and such (default: False)"), + new Argument("--mtag-vv", "debug mtp settings and mutagen/FFprobe parsers (default: False)"), + new Argument("--db-xattr", "read file xattrs as metadata tags; [a,b] reads keys a and b as tags a and b, [a=foo,b=bar] reads keys a and b as tags foo and bar, [~~a,b] does everything except a and b, [~~] does everything. NOTE: Each tag must also be enabled with -mte (volflag=db_xattr) (default: )"), + new Argument("-mtm", "REPEATABLE: add/replace metadata mapping (default: None)"), + new Argument("-mte", "tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar (default: .files,circle,album,.tn,artist,title,tdate,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash, vhash)"), + new Argument("-mth", "tags to hide by default (comma-sep.); assign/add/remove same as -mte (default: tdate,.vq,.aq,vc,ac,fmt,res,.fps)"), + new Argument("-mtp", "REPEATABLE: read tag M using program BIN to parse the file (default: None)"), + new Argument("--no-thumb", "disable all thumbnails (volflag=dthumb) (default: False)"), + new Argument("--no-vthumb", "disable video thumbnails (volflag=dvthumb) (default: False)"), + new Argument("--no-athumb", "disable audio thumbnails (spectrograms) (volflag=dathumb) (default: False)"), + new Argument("--th-size", "thumbnail res (volflag=thsize) (default: 320x256)"), + new Argument("--th-mt", "num cpu cores to use for generating thumbnails (default: numCores)"), + new Argument("--th-convt", "convert-to-image timeout in seconds (volflag=convt) (default: 60.0)"), + new Argument("--ac-convt", "convert-to-audio timeout in seconds (volflag=aconvt) (default: 150.0)"), + new Argument("--th-ram-max", "max memory usage (GiB) permitted by thumbnailer; not very accurate (default: dynamic)"), + new Argument("--th-crop", "crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [y]=crop, [n]=nocrop, [fy]=force-y, [fn]=force-n (volflag=crop) (default: y)"), + new Argument("--th-x3", "show thumbs at 3x resolution; client can override in UI unless force. [y]=yes, [n]=no, [fy]=force-yes, [fn]=force-no (volflag=th3x) (default: n)"), + new Argument("--th-qv", "webp/jpg thumbnail quality (10~90); higher is larger filesize and better quality (volflag=th_qv) (default: 40)"), + new Argument("--th-qvx", "jxl thumbnail quality (10~90); higher is larger filesize and better quality (volflag=th_qvx) (default: 64)"), + new Argument("--th-dec", "image decoders, in order of preference (default: vips,raw,pil,ff)"), + new Argument("--th-no-jpg", "disable jpg output (default: False)"), + new Argument("--th-no-webp", "disable webp output (default: False)"), + new Argument("--th-no-jxl", "disable jpeg-xl output (default: False)"), + new Argument("--th-ff-jpg", "force jpg output for video thumbs (avoids issues on some FFmpeg builds) (default: False)"), + new Argument("--th-ff-swr", "use swresample instead of soxr for audio thumbs (faster, lower accuracy, avoids issues on some FFmpeg builds) (default: False)"), + new Argument("--th-vips-jxl", "when to allow generating jxl thumbnails with libvips; 0=never, 1=musl-mallocng, 2=always (default: 1)"), + new Argument("--th-poke", "activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than SEC seconds (default: 300)"), + new Argument("--th-clean", "cleanup interval; 0=disabled (default: 43200)"), + new Argument("--th-maxage", "max folder age -- folders which haven't been poked for longer than --th-poke seconds will get deleted every --th-clean seconds (default: 604800)"), + new Argument("--th-pregen", "pregenerate thumbnails on startup; F,F is comma-separated list of formats; example: [j,jf,w,w3,wf,wf3,x,xf] NOTE: remember to set --th-maxage 123456789 (volflag=th_pregen) (default: )"), + new Argument("--th-pre-rl", "while pregen is running, ratelimit the thumbnailer logger to one message every SEC seconds (only works with -j1); set 0 to disable ratelimit (default: 30)"), + new Argument("--th-covers", "folder thumbnails to stat/look for; enabling -e2d will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern (default: folder.png,folder.jpg,cover.png,cover.jpg)"), + new Argument("--th-spec-p", "for music, do spectrograms or embedded coverart? [0]=only-art, [1]=prefer-art, [2]=only-spec (default: 1)"), + new Argument("--th-spec-fl", "generate spectrograms with logarithmic frequency scale instead of linear (default: False)"), + new Argument("--th-r-pil", "image formats to decode using pillow (default: avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif, heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff, webp,wmf,xbm,xpm)"), + new Argument("--th-r-vips", "image formats to decode using pyvips (default: 3fr,arw,avif,cr2,cr3,crw,dcr,dng,erf,exr,fit,fits,fts,gif,hdr,heic, heics,heif,heifs,jp2,jpeg,jpg,jpx,jxl,k25,kdc,mdc,mef,mrw,nef,nii,nrw,orf,pfm,pgm,png,ppm,raf,raw,rw2,sr2,srf,srw,svg, tif,tiff,webp,x3f)"), + new Argument("--th-r-raw", "image formats to decode using rawpy (if available) or libraw's dcraw_emu (default: 3fr,arw,cr2,cr3,crw,dcr,dng,erf,k25, kdc,mdc,mef,mos,mrw,nef,nrw,orf,pef,raf,raw,rw2,sr2,srf,srw,x3f)"), + new Argument("--th-r-ffi", "image formats to decode using ffmpeg (default: apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics, heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm)"), + new Argument("--th-r-ffv", "video formats to decode using ffmpeg (default: 3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg, mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv)"), + new Argument("--th-r-ffa", "audio formats to decode using ffmpeg (default: aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bcstm,bfstm,brstm,bonk,dfpwm, dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,m4b,m4r,mdgz,mdxz,mdz,mka,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus, ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk)"), + new Argument("--th-spec-cnv", "audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms) (default: it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk)"), + new Argument("--au-unpk", "audio/image formats to decompress before passing to ffmpeg (default: mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub)"), + new Argument("--q-opus", "target bitrate for transcoding to opus; set 0 to disable (default: 128)"), + new Argument("--q-mp3", "target quality for transcoding to mp3, for example [192k] (CBR) or [q0] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable (default: q2)"), + new Argument("--allow-wav", "allow transcoding to wav (lossless, uncompressed) (default: False)"), + new Argument("--allow-flac", "allow transcoding to flac (lossless, compressed) (default: False)"), + new Argument("--no-caf", "disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead (default: False)"), + new Argument("--no-owa", "disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead (default: False)"), + new Argument("--no-acode", "disable audio transcoding (default: False)"), + new Argument("--no-bacode", "disable batch audio transcoding by folder download (zip/tar) (default: False)"), + new Argument("--ac-maxage", "delete cached transcode output after SEC seconds (default: 86400)"), + new Argument("--rss", "enable RSS output (experimental) (volflag=rss) (default: False)"), + new Argument("--rss-nf", "default number of files to return (url-param 'nf') (default: 250)"), + new Argument("--rss-fext", "default list of file extensions to include (url-param 'fext'); blank=all (default: )"), + new Argument("--rss-sort", "default sort order (url-param 'sort'); [m]=last-modified [u]=upload-time [n]=filename [s]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files (volflag=rss_sort) (default: m)"), + new Argument("--rss-fmt-t", "title format (url-param 'rss_fmt_t') (volflag=rss_fmt_t) (default: {fname})"), + new Argument("--rss-fmt-d", "description format (url-param 'rss_fmt_d') (volflag=rss_fmt_d) (default: {artist} - {title})"), + new Argument("--sftp", "enable SFTP server on PORT, for example 3922 (default: 0)"), + new Argument("--sftpv", "verbose (default: False)"), + new Argument("--sftpvv", "verboser (default: False)"), + new Argument("--sftp-i", "IPs to listen on (comma-separated list). Set this to override -i for this protocol (default: -i)"), + new Argument("--sftp4", "only listen on IPv4 (default: False)"), + new Argument("--sftp-key", "REPEATABLE: add ssh-key K for user U (username, space, key-type, space, base64); if user has multiple keys, then repeat this option for each key └─commandline example: --sftp-key 'david ssh-ed25519 AAAAC3NzaC...' └─config-file example: sftp-key: david ssh-ed25519 AAAAC3NzaC... (default: None)"), + new Argument("--sftp-pw", "allow password-authentication with sftp (not just ssh-keys) (default: False)"), + new Argument("--sftp-anon", "allow anonymous/unauthenticated connections with TXT as username (default: )"), + new Argument("--sftp-hostk", "path to folder with hostkeys, for example 'ssh_host_rsa_key'; missing keys will be generated (default: ~/.config/copyparty)"), + new Argument("--sftp-banner", "bannertext to send when someone connects; can be @filepath (default: )"), + new Argument("--sftp-ipa", "only accept connections from IP-addresses inside CIDR (comma-separated); specify [any] to disable inheriting --ipa / --ipar. Examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] (default: )"), + new Argument("--ftp", "enable FTP server on PORT, for example 3921 (default: 0)"), + new Argument("--ftps", "enable FTPS server on PORT, for example 3990 (default: 0)"), + new Argument("--ftpv", "verbose (default: False)"), + new Argument("--ftp-i", "IPs to listen on (comma-separated list). Set this to override -i for this protocol (default: -i)"), + new Argument("--ftp4", "only listen on IPv4 (default: False)"), + new Argument("--ftp-ipa", "only accept connections from IP-addresses inside CIDR (comma-separated); specify [any] to disable inheriting --ipa / --ipar. Examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] (default: )"), + new Argument("--ftp-no-ow", "if target file exists, reject upload instead of overwrite (default: False)"), + new Argument("--ftp-wt", "grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than SEC seconds ago) (default: 7)"), + new Argument("--ftp-nat", "the NAT address to use for passive connections (default: )"), + new Argument("--ftp-pr", "the range of TCP ports to use for passive connections, for example 12000-13000 (default: )"), + new Argument("--daw", "enable full write support, even if client may not be webdav. Some webdav clients need this option for editing existing files; not necessary for clients that send the 'x-oc-mtime' header. Regardless, the delete-permission must always be given. WARNING: This has side-effects -- PUT-operations will now OVERWRITE existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client) (default: False)"), + new Argument("--dav-inf", "allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this (default: False)"), + new Argument("--dav-mac", "disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS) (default: False)"), + new Argument("--dav-rt", "show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt) (default: False)"), + new Argument("--dav-auth", "force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth) (default: False)"), + new Argument("--dav-ua1", "regex of user-agents which ARE webdav-clients, and expect 401 from GET requests; disable with [no] or blank (default: kioworker/)"), + new Argument("--dav-port", "additional port to listen on for misbehaving webdav clients which pretend they are graphical browsers; an alternative/supplement to dav-ua1 (default: 0)"), + new Argument("--ua-nodav", "regex of user-agents which are NOT webdav-clients (default: ^(Mozilla/|NetworkingExtension/|com\\.apple\\.WebKit))"), + new Argument("--p-nodav", "server-ports (comma-sep.) which are NOT webdav-clients; an alternative/supplement to ua-nodav (default: )"), + new Argument("--tftp", "enable TFTP server on PORT, for example 69 or 3969 (default: 0)"), + new Argument("--tftp-i", "IPs to listen on (comma-separated list). Set this to override -i for this protocol (default: -i)"), + new Argument("--tftp4", "only listen on IPv4 (default: False)"), + new Argument("--tftpv", "verbose (default: False)"), + new Argument("--tftpvv", "verboser (default: False)"), + new Argument("--tftp-no-fast", "debug: disable optimizations (default: False)"), + new Argument("--tftp-lsf", "return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ... (default: \\.?(dir|ls)(\\.txt)?)"), + new Argument("--tftp-nols", "if someone tries to download a directory, return an error instead of showing its directory listing (default: False)"), + new Argument("--tftp-ipa", "only accept connections from IP-addresses inside CIDR (comma-separated); specify [any] to disable inheriting --ipa / --ipar. Examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] (default: )"), + new Argument("--tftp-pr", "the range of UDP ports to use for data transfer, for example 12000-13000 (default: )"), + new Argument("--smb", "enable smb (read-only) -- this requires running copyparty as root on linux and macos unless --smb-port is set above 1024 and your OS does port-forwarding from 445 to that.WARNING: this protocol is DANGEROUS and buggy! Never expose to the internet! (default: False)"), + new Argument("--smb-i", "IPs to listen on (comma-separated list). Set this to override -i for this protocol (default: -i)"), + new Argument("--smbw", "enable write support (please dont) (default: False)"), + new Argument("--smb1", "disable SMBv2, only enable SMBv1 (CIFS) (default: False)"), + new Argument("--smb-port", "port to listen on -- if you change this value, you must NAT from TCP:445 to this port using iptables or similar (default: 445)"), + new Argument("--smb-nwa-1", "truncate directory listings to 64kB (~400 files); avoids impacket-0.11 bug, fixes impacket-0.12 performance (default: False)"), + new Argument("--smb-nwa-2", "disable impacket workaround for filecopy globs (default: False)"), + new Argument("--smba", "small performance boost: disable per-account permissions, enables account coalescing instead (if one user has write/delete-access, then everyone does) (default: False)"), + new Argument("--smb6", "enable IPv6 (default: False)"), + new Argument("--smbv", "verbose (default: False)"), + new Argument("--smbvv", "verboser (default: False)"), + new Argument("--smbvvv", "verbosest (default: False)"), + new Argument("--opds", "enable opds -- allows e-book readers to browse and download files (volflag=opds) (default: False)"), + new Argument("--opds-exts", "file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_exts) (default: epub,cbz,pdf)"), + new Argument("-s", "increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers. └─Alias of --dotpart --no-thumb --no-mtag-ff --no-robots --force-js (default: 0)"), + new Argument("-ss", "further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s. └─Alias of -s --no-html --no-readme --no-logues --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih (default: False)"), + new Argument("-sss", "further increase safety: Enable logging to disk, scan for dangerous symlinks. └─Alias of -ss --no-dav --no-logues --no-readme -lo=cpp-%Y-%m%d-%H%M%S.txt.xz --ls=**,*,ln,p,r (default: False)"), + new Argument("--ls", "do a sanity/safety check of all volumes on startup; new Arguments USER,VOL,FLAGS (see --help-ls); example [**,*,ln,p,r] (default: )"), + new Argument("--xvol", "never follow symlinks leaving the volume root, unless the link is into another volume where the user also has access (keeps permissions from the outer/initial volume) (volflag=xvol) (default: False)"), + new Argument("--xdev", "stay within the filesystem of the volume root; do not descend into other devices (symlink or bind-mount to another HDD, ...) (volflag=xdev) (default: False)"), + new Argument("--vol-nospawn", "if a volume's folder does not exist on the HDD, then do not create it (continue with warning) (volflag=nospawn) (default: False)"), + new Argument("--vol-or-crash", "if a volume's folder does not exist on the HDD, then burst into flames (volflag=assert_root) (default: False)"), + new Argument("--no-dot-mv", "disallow moving dotfiles; makes it impossible to move folders containing dotfiles (default: False)"), + new Argument("--no-dot-ren", "disallow renaming dotfiles; makes it impossible to turn something into a dotfile (default: False)"), + new Argument("--no-logues", "disable rendering .prologue/.epilogue.html into directory listings (default: False)"), + new Argument("--no-readme", "disable rendering readme/preadme.md into directory listings (default: False)"), + new Argument("--no-script", "disables javascript in html files; helps prevent XSS but kills interactive websites (volflag=noscript) (default: False)"), + new Argument("--no-html", "show html-files as plain text; helps prevent XSS but kills websites/blogs, also enables --no-script (volflag=nohtml) (default: False)"), + new Argument("--vague-403", "send 404 instead of 403 (security through ambiguity, very enterprise). WARNING: Not compatible with WebDAV (default: False)"), + new Argument("--force-js", "don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore --no-robots (default: False)"), + new Argument("--no-robots", "adds http and html headers asking search engines to not index anything (volflag=norobots) (default: False)"), + new Argument("--logout", "logout clients after H hours of inactivity; [0.0028]=10sec, [0.1]=6min, [24]=day, [168]=week, [720]=month, [8760]=year) (default: 8086.0)"), + new Argument("--dont-ban", "anyone at this accesslevel or above will not get banned: [av]=admin-in-volume, [aa]=has-admin-anywhere, [rw]=read-write, [auth]=authenticated, [any]=disable-all-bans, [no]=anyone-can-get-banned (default: no)"), + new Argument("--banmsg", "the response to send to banned users; can be @ban.html to send the contents of ban.html (default: thank you for playing (see fileserver log and readme))"), + new Argument("--ban-pw", "more than N wrong passwords in W minutes = ban for B minutes; disable with [no] (default: 9,60,1440)"), + new Argument("--ban-pwc", "more than N password-changes in W minutes = ban for B minutes; disable with [no] (default: 5,60,1440)"), + new Argument("--ban-404", "hitting more than N 404's in W minutes = ban for B minutes; only affects users who cannot see directory listings because their access is either g/G/h (default: 50,60,1440)"), + new Argument("--ban-403", "hitting more than N 403's in W minutes = ban for B minutes; [1440]=day, [10080]=week, [43200]=month (default: 9,2,1440)"), + new Argument("--ban-422", "hitting more than N 422's in W minutes = ban for B minutes (invalid requests, attempted exploits ++) (default: 9,2, 1440)"), + new Argument("--ban-url", "hitting more than N sus URL's in W minutes = ban for B minutes; applies only to permissions g/G/h (decent replacement for --ban-404 if that can't be used) (default: 9,2,1440)"), + new Argument("--sus-urls", "URLs which are considered sus / eligible for banning; disable with blank or [no] (default: \\.php\\$|(^|/)wp-(admin|content|includes)/)"), + new Argument("--nonsus-urls", "harmless URLs ignored from 403/404-bans; disable with blank or [no] (default: ^(favicon\\..{3}|robots\\.txt)\\$|^apple-touch-icon|^\\.well-known)"), + new Argument("--early-ban", "if a client is banned, reject its connection as soon as possible; not a good idea to enable when proxied behind cloudflare since it could ban your reverse-proxy (default: False)"), + new Argument("--cookie-nmax", "reject HTTP-request from client if they send more than N cookies (default: 50)"), + new Argument("--cookie-cmax", "reject HTTP-request from client if more than N characters in Cookie header (default: 8192)"), + new Argument("--aclose", "if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for MIN minutes (and also kill its active connections) -- disable with 0 (default: 10)"), + new Argument("--loris", "if a client maxes out the server connection limit without sending headers, ban it for B minutes; disable with [0] (default: 60)"), + new Argument("--acao", "Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [https://1.2.3.4]. Default [*] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives (default: *)"), + new Argument("--acam", "Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like --acao's description) (default: GET,HEAD)"), + new Argument("--ah-alg", "account-pw hashing algorithm; one of these, best to worst: argon2 scrypt sha2 none (each optionally followed by alg-specific comma-sep. config) (default: none)"), + new Argument("--ah-salt", "account-pw salt; ignored if --ah-alg is none (default) (default: 24-character-autogenerated)"), + new Argument("--ah-gen", "generate hashed password for PW, or read passwords from STDIN if PW is [-] (default: )"), + new Argument("--ah-cli", "launch an interactive shell which hashes passwords without ever storing or displaying the original passwords (default: False)"), + new Argument("--fk-salt", "per-file accesskey salt; used to generate unpredictable URLs for hidden files (default: 24-character-autogenerated)"), + new Argument("--dk-salt", "per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission (default: 40-character-autogenerated)"), + new Argument("--warksalt", "up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do) (default: hunter2)"), + new Argument("--show-ah-salt", "on startup, print the effective value of --ah-salt (the autogenerated value in \$XDG_CONFIG_HOME unless otherwise specified) (default: False)"), + new Argument("--show-fk-salt", "on startup, print the effective value of --fk-salt (the autogenerated value in \$XDG_CONFIG_HOME unless otherwise specified) (default: False)"), + new Argument("--show-dk-salt", "on startup, print the effective value of --dk-salt (the autogenerated value in \$XDG_CONFIG_HOME unless otherwise specified) (default: False)"), + new Argument("-nw", "never write anything to disk (debug/benchmark) (default: False)"), + new Argument("--keep-qem", "do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection in the terminal window, as this would pause execution) (default: False)"), + new Argument("--no-dav", "disable webdav support (default: False)"), + new Argument("--no-del", "disable delete operations (default: False)"), + new Argument("--no-mv", "disable move/rename operations (default: False)"), + new Argument("--no-cp", "disable copy operations (default: False)"), + new Argument("--no-fs-abrt", "disable ability to abort ongoing copy/move (default: False)"), + new Argument("-nih", "no info hostname -- removes it from the UI corner, but the value of --bname still shows in the browsertab title (default: False)"), + new Argument("-nid", "no info disk-usage -- don't show in UI. This is the same as --du-who no (default: False)"), + new Argument("-nb", "no powered-by-copyparty branding in UI (default: False)"), + new Argument("--smsg", "HTTP-methods to allow ?smsg for; will execute xm hooks like urlform / message-to-serverlog; dangerous example: [GET, POST]. WARNING: The default (POST) is safe, but GET is dangerous; security/CSRF hazard (default: POST)"), + new Argument("--zipmaxn", "reject download-as-zip if more than N files in total; optionally takes a unit suffix: [256], [9K], [4G] (volflag=zipmaxn) (default: 0)"), + new Argument("--zipmaxs", "reject download-as-zip if total download size exceeds SZ bytes; optionally takes a unit suffix: [256M], [4G], [2T] (volflag=zipmaxs) (default: 0)"), + new Argument("--zipmaxt", "custom errormessage when download size exceeds max (volflag=zipmaxt) (default: )"), + new Argument("--zipmaxu", "authenticated users bypass the zip size limit (volflag=zipmaxu) (default: False)"), + new Argument("--zip-who", "who can download as zip/tar? [0]=nobody, [1]=admins, [2]=authenticated-with-read-access, [3]=everyone-with-read-access (volflag=zip_who)WARNING: if a nested volume has a more restrictive value than a parent volume, then this will be ignored if the download is initiated from the parent, more lenient volume (default: 3)"), + new Argument("--ua-nozip", "regex of user-agents to reject from download-as-zip/tar; disable with [no] or blank (default: Barkrowler|bingbot|BLEXBot|Googlebot|GoogleOther|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot)"), + new Argument("--no-zip", "disable download as zip/tar; same as --zip-who=0 (default: False)"), + new Argument("--no-tarcmp", "disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...) (default: False)"), + new Argument("--no-lifetime", "do not allow clients (or server config) to schedule an upload to be deleted after a given time (default: False)"), + new Argument("--no-pipe", "disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe) (default: False)"), + new Argument("--no-tail", "disable streaming a growing files with ?tail (volflag=notail) (default: False)"), + new Argument("--no-db-ip", "do not write uploader-IP into the database; will also disable unpost, you may want --forget-ip instead (volflag=no_db_ip) (default: False)"), + new Argument("--no-zls", "disable browsing the contents of zip/cbz files, does not affect thumbnails (default: False)"), + new Argument("--ign-ebind", "continue running even if it's impossible to listen on some of the requested endpoints (default: False)"), + new Argument("--ign-ebind-all", "continue running even if it's impossible to receive connections at all (default: False)"), + new Argument("--exit", "shutdown after WHEN has finished; [cfg] config parsing, [idx] volscan + multimedia indexing, [thgen] thumbnail-pregen (default: )"), + new Argument("--allow-csrf", "disable csrf protections; let other domains/sites impersonate you through cross-site requests; DANGEROUS / LAN-only (default: False)"), + new Argument("--cookie-lax", "allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF (default: False)"), + new Argument("--no-fnugg", "disable the smoketest for caching-related issues in the web-UI (default: False)"), + new Argument("--getmod", "permit ?move=[...] and ?delete as GET -- DANGEROUS, removes csrf protection (default: False)"), + new Argument("--wo-up-readme", "allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme) (default: False)"), + new Argument("--unsafe-state", "when one of the emergency fallback locations are used for runtime state (\$TMPDIR, /tmp), certain features will be force-disabled for security reasons by default. This option overrides that safeguard and allows unsafe storage of secrets (default: False)"), + new Argument("--on404", "REPEATABLE: handle 404s by executing PY file (default: None)"), + new Argument("--on403", "REPEATABLE: handle 403s by executing PY file (default: None)"), + new Argument("--hot-handlers", "recompile handlers on each request -- expensive but convenient when hacking on stuff (default: False)"), + new Argument("--xbu", "REPEATABLE: execute CMD before a file upload starts (default: None)"), + new Argument("--xau", "REPEATABLE: execute CMD after a file upload finishes (default: None)"), + new Argument("--xiu", "REPEATABLE: execute CMD after all uploads finish and volume is idle (default: None)"), + new Argument("--xbc", "REPEATABLE: execute CMD before a file copy (default: None)"), + new Argument("--xac", "REPEATABLE: execute CMD after a file copy (default: None)"), + new Argument("--xbr", "REPEATABLE: execute CMD before a file move/rename (default: None)"), + new Argument("--xar", "REPEATABLE: execute CMD after a file move/rename (default: None)"), + new Argument("--xbd", "REPEATABLE: execute CMD before a file delete (default: None)"), + new Argument("--xad", "REPEATABLE: execute CMD after a file delete (default: None)"), + new Argument("--xm", "REPEATABLE: execute CMD on message (default: None)"), + new Argument("--xban", "REPEATABLE: execute CMD if someone gets banned (pw/404/403/url) (default: None)"), + new Argument("--hook-v", "verbose hooks (default: False)"), + new Argument("--stats", "enable openmetrics at /.cpr/metrics for admin accounts (default: False)"), + new Argument("--stats-u", "comma-separated list of users allowed to access /.cpr/metrics even if they aren't admin (default: )"), + new Argument("--nos-hdd", "disable disk-space metrics (used/free space) (default: False)"), + new Argument("--nos-vol", "disable volume size metrics (num files, total bytes, vmaxb/vmaxn) (default: False)"), + new Argument("--nos-vst", "disable volume state metrics (indexing, analyzing, activity) (default: False)"), + new Argument("--nos-dup", "disable dupe-files metrics (good idea; very slow) (default: False)"), + new Argument("--nos-unf", "disable unfinished-uploads metrics (default: False)"), + new Argument("--rw-edit", "comma-sep. list of file-extensions to allow editing with permissions read+write; all others require read+write+delete (volflag=rw_edit) (default: md)"), + new Argument("--md-no-br", "markdown: disable newline-is-newline; will only render a newline into the html given two trailing spaces or a double-newline (volflag=md_no_br) (default: False)"), + new Argument("--md-hist", "where to store old version of markdown files; [s]=subfolder, [v]=volume-histpath, [n]=nope/disabled (volflag=md_hist) (default: s)"), + new Argument("--txt-eol", "enable EOL conversion when writing documents; supported: CRLF, LF (volflag=txt_eol) (default: )"), + new Argument("-mcr", "the textfile editor will check for serverside changes every SEC seconds (default: 60)"), + new Argument("-emp", "enable markdown plugins -- neat but dangerous, big XSS risk (default: False)"), + new Argument("--exp", "enable textfile expansion -- replace {{self.ip}} and such; see --help-exp (volflag=exp) (default: False)"), + new Argument("--exp-md", "comma/space-separated list of placeholders to expand in markdown files; add/remove stuff on the default list with +hdr_foo or /vf.scan (volflag=exp_md) (default: self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf-ipcountry srv.itime srv.htime)"), + new Argument("--exp-lg", "comma/space-separated list of placeholders to expand in prologue/epilogue files (volflag=exp_lg) (default: self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf-ipcountry srv.itime srv.htime)"), + new Argument("--ua-nodoc", "regex of user-agents to reject from viewing documents through ?doc=[...]; disable with [no] or blank (default: Barkrowler|bingbot|BLEXBot|Googlebot|GoogleOther|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot)"), + new Argument("--tail-who", "who can tail? [0]=nobody, [1]=admins, [2]=authenticated-with-read-access, [3]=everyone-with-read-access (volflag=tail_who) (default: 2)"), + new Argument("--tail-cmax", "do not allow starting a new tail if more than N active downloads (default: 64)"), + new Argument("--tail-tmax", "terminate connection after SEC seconds; [0]=never (volflag=tail_tmax) (default: 0)"), + new Argument("--tail-rate", "check for new data every SEC seconds (volflag=tail_rate) (default: 0.2)"), + new Argument("--tail-ka", "send a zerobyte if connection is idle for SEC seconds to prevent disconnect (default: 3.0)"), + new Argument("--tail-fd", "check if file was replaced (new fd) if idle for SEC seconds (volflag=tail_fd) (default: 1.0)"), + new Argument("--og", "disable hotlinking and return an html document instead; this is required by open-graph, but can also be useful on its own (volflag=og) (default: False)"), + new Argument("--og-ua", "only disable hotlinking / engage OG behavior if the useragent matches regex RE (volflag=og_ua) (default: )"), + new Argument("--og-tpl", "do not return the regular copyparty html, but instead load the jinja2 template at PATH (if path contains 'EXT' then EXT will be replaced with the requested file's extension) (volflag=og_tpl) (default: )"), + new Argument("--og-no-head", "do not automatically add OG entries into (useful if you're doing this yourself in a template or such) (volflag=og_no_head) (default: False)"), + new Argument("--og-th", "thumbnail format; j=jpeg, jf=jpeg-uncropped, jf3=jpeg-uncropped-large, w=webm, ... (volflag=og_th) (default: jf3)"), + new Argument("--og-title", "fallback title if there is nothing in the -e2t database (volflag=og_title) (default: )"), + new Argument("--og-title-a", "audio title format; takes any metadata key (volflag=og_title_a) (default: 🎵 {{ artist }} - {{ title }})"), + new Argument("--og-title-v", "video title format; takes any metadata key (volflag=og_title_v) (default: {{ title }})"), + new Argument("--og-title-i", "image title format; takes any metadata key (volflag=og_title_i) (default: {{ title }})"), + new Argument("--og-s-title", "force default title; do not read from tags (volflag=og_s_title) (default: False)"), + new Argument("--og-desc", "description text; same for all files, disable with [-] (volflag=og_desc) (default: )"), + new Argument("--og-site", "sitename; defaults to --name, disable with [-] (volflag=og_site) (default: )"), + new Argument("--tcolor", "accent color (3 or 6 hex digits); may also affect safari and/or android-chrome (volflag=tcolor) (default: 333)"), + new Argument("--uqe", "query-string parceling; translate a request for /foo/.uqe/BASE64 into /foo?TEXT, or /foo/?TEXT if the first character in TEXT is a slash. Automatically enabled for --og (default: False)"), + new Argument("--grid", "show grid/thumbnails by default (volflag=grid) (default: False)"), + new Argument("--gsel", "select files in grid by ctrl-click (volflag=gsel) (default: False)"), + new Argument("--localtime", "default to local timezone instead of UTC (default: False)"), + new Argument("--ui-filesz", "default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, 6, 6c, 7, 7c, fuzzy (see UI) (default: 1)"), + new Argument("--gauto", "switch to gridview if more than PERCENT of files are pics/vids; 0=disabled (default: 0)"), + new Argument("--rcm", "rightclick-menu; two yes/no options: 1st y/n is enable-custom-menu, 2nd y/n is enable-double (default: yy)"), + new Argument("--lang", "language, for example eng / nor / ... (default: eng)"), + new Argument("--glang", "guess the browser's default language, otherwise fall back to --lang (default: False)"), + new Argument("--theme", "default theme to use (0..9) (default: 0)"), + new Argument("--themes", "number of themes installed (default: 10)"), + new Argument("--au-vol", "default audio/video volume percent (default: 50)"), + new Argument("--sort", "default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: href -href ext sz ts tags/Album tags/.tn (volflag=sort) (default: href)"), + new Argument("--nsort", "default-enable natural sort of filenames with leading numbers (volflag=nsort) (default: False)"), + new Argument("--hsortn", "number of sorting rules to include in media URLs by default (volflag=hsortn) (default: 2)"), + new Argument("--see-dots", "default-enable seeing dotfiles; only takes effect if user has the necessary permissions (default: False)"), + new Argument("--qdel", "number of confirmations to show when deleting files (2/1/0) (default: 2)"), + new Argument("--dlni", "force download (don't show inline) when files are clicked (volflag:dlni) (default: False)"), + new Argument("--unlist", "don't show files/folders matching REGEX in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\\.(js|css)\\$] (volflag=unlist) (default: )"), + new Argument("--dothidden", "hide specific files in a folder by listing them in a file named .hidden -- WARNING: Mostly cosmetic! Download-as-zip/tar will still download them. Do not rely on this for security (volflag=dothidden) (default: False)"), + new Argument("--favico", "favicon-text [ foreground [ background ] ], set blank to disable (default: 🎉 000 none)"), + new Argument("--ufavico", "URL to .ico/png/gif/svg file; --favico takes precedence unless disabled (volflag=ufavico) (default: )"), + new Argument("--bg-img", "URL to .jpg/png/gif/svg file (volflag=bg_img)"), + new Argument("--ext-th", "REPEATABLE: use thumbnail-image VP for file-extension E, example: [exe=/.res/exe.png] (volflag=ext_th) (default: None)"), + new Argument("--notooltips", "tooltips disabled as default (default: False)"), + new Argument("--spinner", "emoji or emoji,css Example: [🥖,padding:0] (default: 🌲)"), + new Argument("--css-browser", "URL to additional CSS to include in the filebrowser html (default: )"), + new Argument("--js-browser", "URL to additional JS to include in the filebrowser html (default: )"), + new Argument("--js-other", "URL to additional JS to include in all other pages (default: )"), + new Argument("--html-head", "text to append to the of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with % to render as jinja2 template (volflag=html_head) (default: )"), + new Argument("--html-head-s", "text to append to the of all HTML pages (except for basic-browser); similar to (and can be combined with) --html-head but only accepts static text (volflag=html_head_s) (default: )"), + new Argument("--ih", "if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override) (default: False)"), + new Argument("--textfiles", "file extensions to present as plaintext (default: txt,nfo,diz,cue,readme)"), + new Argument("--txt-max", "max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS) (default: 64)"), + new Argument("--prologues", "comma-sep. list of filenames to scan for and use as prologues (embed above/before directory listing) (volflag=prologues) (default: .prologue.html)"), + new Argument("--epilogues", "comma-sep. list of filenames to scan for and use as epilogues (embed below/after directory listing) (volflag=epilogues) (default: .epilogue.html)"), + new Argument("--preadmes", "comma-sep. list of filenames to scan for and use as preadmes (embed above/before directory listing) (volflag=preadmes) (default: preadme.md,PREADME.md)"), + new Argument("--readmes", "comma-sep. list of filenames to scan for and use as readmes (embed below/after directory listing) (volflag=readmes) (default: readme.md,README.md)"), + new Argument("--doctitle", "title / service-name to show in html documents (default: copyparty @ --name)"), + new Argument("--bname", "server name (displayed in filebrowser document title) (default: --name)"), + new Argument("--pb-url", "powered-by link; disable with -nb (default: https://github.com/9001/copyparty)"), + new Argument("--ver", "show version on the control panel (incompatible with -nb). This is the same as --ver-who all (default: False)"), + new Argument("--ver-who", "only show version for: [a]=admin-permission-anywhere, [auth]=authenticated, [all]=anyone (default: no)"), + new Argument("--du-who", "only show disk usage for: [no]=nobody, [a]=admin-permission, [rw]=read-write, [w]=write, [auth]=authenticated, [all]=anyone (volflag=du_who) (default: all)"), + new Argument("--k304", "configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on (default: 0)"), + new Argument("--no304", "configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on (default: 0)"), + new Argument("--ctl-re", "the controlpanel Refresh-button will autorefresh every SEC; [0] = just once (default: 1)"), + new Argument("--md-sbf", "list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox (default: downloads forms popups scripts top-navigation-by-user-activation)"), + new Argument("--lg-sbf", "list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs (volflag=lg_sbf) (default: downloads forms popups scripts top-navigation-by-user-activation)"), + new Argument("--md-sba", "the value of the iframe 'allow' attribute for README.md docs, for example [fullscreen] (volflag=md_sba) (default: )"), + new Argument("--lg-sba", "the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes (default: )"), + new Argument("--no-sb-md", "don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md) (default: False)"), + new Argument("--no-sb-lg", "don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support (default: False)"), + new Argument("--ui-nombar", "hide top-menu in the UI (volflag=ui_nombar) (default: False)"), + new Argument("--ui-noacci", "hide account-info in the UI (volflag=ui_noacci) (default: False)"), + new Argument("--ui-nosrvi", "hide server-info in the UI (volflag=ui_nosrvi) (default: False)"), + new Argument("--ui-nonav", "hide navpane+breadcrumbs (volflag=ui_nonav) (default: False)"), + new Argument("--ui-notree", "hide navpane in the UI (volflag=ui_notree) (default: False)"), + new Argument("--ui-nocpla", "hide cpanel-link in the UI (volflag=ui_nocpla) (default: False)"), + new Argument("--ui-nolbar", "hide link-bar in the UI (volflag=ui_nolbar) (default: False)"), + new Argument("--ui-noctxb", "hide context-buttons in the UI (volflag=ui_noctxb) (default: False)"), + new Argument("--ui-norepl", "hide repl-button in the UI (volflag=ui_norepl) (default: False)"), + new Argument("--unfun", "hides many of the emojis in the UI (default: False)"), + new Argument("--no-reload", "disable ?reload=cfg (reload users/volumes/volflags from config file) (default: False)"), + new Argument("--no-rescan", "disable ?scan (volume reindexing) (default: False)"), + new Argument("--no-stack", "disable ?stack (list all stacks); same as --stack-who=no (default: False)"), + new Argument("--no-ups-page", "disable ?ru (list of recent uploads) (default: False)"), + new Argument("--no-up-list", "don't show list of incoming files in controlpanel (default: False)"), + new Argument("--dl-list", "who can see active downloads in the controlpanel? [0]=nobody, [1]=admins, [2]=everyone (default: 2)"), + new Argument("--ups-who", "who can see recent uploads on the ?ru page? [0]=nobody, [1]=admins, [2]=everyone (volflag=ups_who) (default: 2)"), + new Argument("--ups-when", "let everyone see upload timestamps on the ?ru page, not just admins (default: False)"), + new Argument("--stack-who", "who can see the ?stack page (list of threads)? [no]=nobody, [a]=admins, [rw]=read+write, [all]=everyone (default: a)"), + new Argument("--stack-v", "verbose ?stack (default: False)"), + new Argument("-q", "quiet; disable most STDOUT messages (default: False)"), + new Argument("-lo", "logfile; use .txt for plaintext or .xz for compressed. Example: cpp-%Y-%m%d-%H%M%S.txt.xz (NB: some errors may appear on STDOUT only) (default: )"), + new Argument("--flo", "log format for -lo; [1]=classic/colors, [2]=no-color (default: 1)"), + new Argument("--rlo", "logrotate counter format; see --help-rlo (default: .1)"), + new Argument("--logrot-sig", "immediately logrotate when unix-signal S is received; examples: [SIGHUP], [HUP], [1] (default: )"), + new Argument("--no-ansi", "disable colors; same as environment-variable NO_COLOR (default: False)"), + new Argument("--ansi", "force colors; overrides environment-variable NO_COLOR (default: False)"), + new Argument("--no-logflush", "don't flush the logfile after each write; tiny bit faster (default: False)"), + new Argument("--no-voldump", "do not list volumes and permissions on startup (default: False)"), + new Argument("--log-utc", "do not use local timezone; assume the TZ env-var is UTC (tiny bit faster) (default: False)"), + new Argument("--log-tdec", "timestamp resolution / number of timestamp decimals (default: 3)"), + new Argument("--log-date", "date-format, for example [%Y-%m-%d] (default is disabled; no date, just HH:MM:SS) (default: )"), + new Argument("--log-badpwd", "log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed (default: 2)"), + new Argument("--log-badxml", "log any invalid XML received from a client (default: False)"), + new Argument("--log-conn", "debug: print tcp-server msgs (default: False)"), + new Argument("--log-htp", "debug: print http-server threadpool scaling (default: False)"), + new Argument("--ihead", "print request HEADER; [*]=all (default: None)"), + new Argument("--ohead", "print response HEADER; [*]=all (default: None)"), + new Argument("--lf-url", "dont log URLs matching regex RE (default: ^/\\.cpr/|[?&]th=[xwjp]|/\\.(_|ql_|DS_Store\\$|localized\\$))"), + new Argument("--scan-st-r", "fs-indexing: wait SEC between each status-message (default: 0.1)"), + new Argument("--scan-pr-r", "fs-indexing: wait SEC between each 'progress:' message (default: 10)"), + new Argument("--scan-pr-s", "fs-indexing: say 'file: ' when a file larger than MiB is about to be hashed (default: 1)"), + new Argument("--vc", "verbose config file parser (explain config) (default: False)"), + new Argument("--cgen", "generate config file from current config (best-effort; probably buggy) (default: False)"), + new Argument("--deps", "list information about detected optional dependencies (default: False)"), + new Argument("--no-poll", "kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700) (default: False)"), + new Argument("--no-sendfile", "kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead (default: False)"), + new Argument("--no-scandir", "kernel-bug workaround: disable scandir; do a listdir + stat on each file instead (default: False)"), + new Argument("--no-fastboot", "wait for initial filesystem indexing before accepting client requests (default: False)"), + new Argument("--no-htp", "disable httpserver threadpool, create threads as-needed instead (default: False)"), + new Argument("--sig-thr", "start separate thread for OS-signals (try this if CTRL-C is busted) (default: False)"), + new Argument("--rm-sck", "when listening on unix-sockets, do a basic delete+bind instead of the default atomic bind (default: False)"), + new Argument("--srch-dbg", "explain search processing, and do some extra expensive sanity checks (default: False)"), + new Argument("--rclone-mdns", "use mdns-domain instead of server-ip on /?hc (default: False)"), + new Argument("--stackmon", "write stacktrace to Path every S second, for example --stackmon=./st/%Y-%m/%d/%H%M.xz,60 (default: )"), + new Argument("--stack-sig", "show stacktrace when unix-signal S is received; examples: [SIGUSR2], [USR2], [12] (default: )"), + new Argument("--log-thrs", "list active threads every SEC (default: 0.0)"), + new Argument("--log-fk", "log filekey params for files where path matches REGEX; [.] (a single dot) = all files (default: )"), + new Argument("--bak-flips", "[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to --bf-nc and --bf-dir (default: False)"), + new Argument("--bf-nc", "bak-flips: stop if there's more than NUM files at --kf-dir already; default: 6.3 GiB max (200*32M) (default: 200)"), + new Argument("--bf-dir", "bak-flips: store corrupted chunks at PATH; default: folder named 'bf' wherever copyparty was started (default: bf)"), + new Argument("--bf-log", "bak-flips: log corruption info to a textfile at PATH (default: )"), + new Argument("--help-bind", "configure listening (default: False)"), + new Argument("--help-accounts", "accounts and volumes (default: False)"), + new Argument("--help-auth", "how to login from a client (default: False)"), + new Argument("--help-auth-ord", "authentication precedence (default: False)"), + new Argument("--help-flags", "list of volflags (default: False)"), + new Argument("--help-handlers", "use plugins to handle certain events (default: False)"), + new Argument("--help-hooks", "execute commands before/after various events (default: False)"), + new Argument("--help-idp", "replacing the login system with fancy middleware (default: False)"), + new Argument("--help-urlform", "how to handle url-form POSTs (default: False)"), + new Argument("--help-exp", "text expansion (default: False)"), + new Argument("--help-rlo", "logrotate format (default: False)"), + new Argument("--help-ls", "volume inspection (default: False)"), + new Argument("--help-dbd", "database durability profiles (default: False)"), + new Argument("--help-chmod", "file/folder permissions (default: False)"), + new Argument("--help-pwhash", "password hashing (default: False)"), + new Argument("--help-zm", "mDNS debugging (default: False)"), +]; + +// ^\s*([^\s=]+)(?:\s|=)(.*)$ +var volflags = [ + new Argument("dedup", "enable symlink-based file deduplication"), + new Argument("hardlink", "enable hardlink-based file deduplication,with fallback on symlinks when that is impossible"), + new Argument("hardlinkonly", "dedup with hardlink only, never symlink;make a full copy if hardlink is impossible"), + new Argument("reflink", "enable reflink-based file deduplication,with fallback on full copy when that is impossible"), + new Argument("safededup", "verify on-disk data before using it for dedup"), + new Argument("noclone", "take dupe data from clients, even if available on HDD"), + new Argument("nodupe", "rejects existing files (instead of linking/cloning them)"), + new Argument("nodupem", "rejects existing files during moves as well"), + new Argument("casechk", "auto actively prevent case-insensitive filesystem? y/n"), + new Argument("chmod_d", "755 unix-permission for new dirs/folders"), + new Argument("chmod_f", "644 unix-permission for new files"), + new Argument("uid", "573 change owner of new files/folders to unix-user 573"), + new Argument("gid", "999 change owner of new files/folders to unix-group 999"), + new Argument("fsnt", "auto filesystem filename traits (lin/win/mac/auto)"), + new Argument("wram", "allow uploading into ramdisks"), + new Argument("sparse", "force use of sparse files, mainly for s3-backed storage"), + new Argument("nosparse", "deny use of sparse files, mainly for slow storage"), + new Argument("rm_partial", "delete unfinished uploads from HDD when they timeout"), + new Argument("daw", "enable full WebDAV write support (dangerous);PUT-operations will now OVERWRITE existing files"), + new Argument("nosub", "forces all uploads into the top folder of the vfs"), + new Argument("magic", "enables filetype detection for nameless uploads"), + new Argument("put_name", "fallback filename for nameless uploads"), + new Argument("put_ck", "default checksum-hasher for PUT/WebDAV uploads"), + new Argument("bup_ck", "default checksum-hasher for bup/basic uploads"), + new Argument("gz", "allows server-side gzip compression of uploads with ?gz"), + new Argument("xz", "allows server-side lzma compression of uploads with ?xz"), + new Argument("pk", "forces server-side compression, optional arg: xz,9"), + new Argument("apnd_who", "dw who can append? (aw/dw/w/no)"), + new Argument("maxn", "250,600 max 250 uploads over 15min"), + new Argument("maxb", "1g,300 max 1 GiB over 5min (suffixes: b, k, m, g, t)"), + new Argument("vmaxb", "1g total volume size max 1 GiB (suffixes: b, k, m, g, t)"), + new Argument("vmaxn", "4k max 4096 files in volume (suffixes: b, k, m, g, t)"), + new Argument("medialinks", "return medialinks for non-up2k uploads (not hotlinks)"), + new Argument("wo_up_readme", "write-only users can upload logues without getting renamed"), + new Argument("rand", "force randomized filenames, 9 chars long by default"), + new Argument("nrand", "N randomized filenames are N chars long"), + new Argument("u2ow", "N overwrite existing files? 0=no 1=if-older 2=always"), + new Argument("u2ts", "fc [f]orce [c]lient-last-modified or [u]pload-time"), + new Argument("u2abort", "1 allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk"), + new Argument("sz", "1k-3m allow filesizes between 1 KiB and 3MiB"), + new Argument("df", "1g ensure 1 GiB free disk space"), + new Argument("rotn", "100,3 3 levels of subfolders with 100 entries in each"), + new Argument("rotf", "%Y-%m/%d-%H date-formatted organizing"), + new Argument("rotf_tz", "Europe/Oslo timezone (default=UTC)"), + new Argument("lifetime", "3600 uploads are deleted after 1 hour"), + new Argument("e2d", "enable database; makes files searchable + enables upload-undo"), + new Argument("e2ds", "scan writable folders for new files on startup; also sets -e2d"), + new Argument("e2dsa", "scans all folders for new files on startup; also sets -e2d"), + new Argument("e2t", "enable multimedia indexing; makes it possible to search for tags"), + new Argument("e2ts", "scan existing files for tags on startup; also sets -e2t"), + new Argument("e2tsr", "delete all metadata from DB (full rescan); also sets -e2ts"), + new Argument("d2ts", "disables metadata collection for existing files"), + new Argument("e2v", "verify integrity on startup by hashing files and comparing to db"), + new Argument("e2vu", "when e2v fails, update the db (assume on-disk files are good)"), + new Argument("e2vp", "when e2v fails, panic and quit copyparty"), + new Argument("d2ds", "disables onboot indexing, overrides -e2ds*"), + new Argument("d2t", "disables metadata collection, overrides -e2t*"), + new Argument("d2v", "disables file verification, overrides -e2v*"), + new Argument("d2d", "disables all database stuff, overrides -e2*"), + new Argument("hist", "/tmp/cdb puts thumbnails and indexes at that location"), + new Argument("dbpath", "/tmp/cdb puts indexes at that location"), + new Argument("landmark", "foo disable db if file foo doesn't exist"), + new Argument("scan", "60 scan for new files every 60sec, same as --re-maxage"), + new Argument("nohash", "\\.iso\$ skips hashing file contents if path matches *.iso"), + new Argument("noidx", "\\.iso\$ fully ignores the contents at paths matching *.iso"), + new Argument("noforget", "don't forget files when deleted from disk"), + new Argument("forget_ip", "43200 forget uploader-IP after 30 days (GDPR)"), + new Argument("no_db_ip", "never store uploader-IP in the db; disables unpost"), + new Argument("fat32", "avoid excessive reindexing on android sdcardfs"), + new Argument("dbd", "[acid|swal|wal|yolo] database speed-durability tradeoff"), + new Argument("xlink", "cross-volume dupe detection / linking (dangerous)"), + new Argument("xdev", "do not descend into other filesystems"), + new Argument("xvol", "do not follow symlinks leaving the volume root"), + new Argument("dotsrch", "show dotfiles in search results"), + new Argument("nodotsrch", "hide dotfiles in search results (default)"), + new Argument("srch_excl", "exclude search results with URL matching this regex"), + new Argument("db_xattr", "user.foo,user.bar index file xattrs as media-tags"), + new Argument("mte", "artist,title media-tags to index/display"), + new Argument("mth", "fmt,res,ac media-tags to hide by default"), + new Argument("mtp", ".bpm=f,audio-bpm.py uses the \"audio-bpm.py\" program togenerate \".bpm\" tags from uploads (f = overwrite tags)"), + new Argument("mtp", "ahash,vhash=media-hash.py collects two tags at once"), + new Argument("dthumb", "disables all thumbnails"), + new Argument("dvthumb", "disables video thumbnails"), + new Argument("dathumb", "disables audio thumbnails (spectrograms)"), + new Argument("dithumb", "disables image thumbnails"), + new Argument("pngquant", "compress audio waveforms 33% better"), + new Argument("thsize", "thumbnail res; WxH"), + new Argument("crop", "center-cropping (y/n/fy/fn)"), + new Argument("th3x", "3x resolution (y/n/fy/fn)"), + new Argument("th_qv", "40 webp/jpg thumbnail quality (10~90)"), + new Argument("th_qvx", "40 jxl thumbnail quality (10~90)"), + new Argument("convt", "convert-to-image timeout in seconds"), + new Argument("aconvt", "convert-to-audio timeout in seconds"), + new Argument("th_spec_p", "1 make spectrograms? 0=never 1=fallback 2=always"), + new Argument("ext_th", "s=/b.png use /b.png as thumbnail for file-extension s"), + new Argument("th_pregen", "w,wf pregenerate thumbs for these formats"), + new Argument("on404", "PY handle 404s by executing PY file"), + new Argument("on403", "PY handle 403s by executing PY file"), + new Argument("xbu", "CMD execute CMD before a file upload starts"), + new Argument("xau", "CMD execute CMD after a file upload finishes"), + new Argument("xiu", "CMD execute CMD after all uploads finish and volume is idle"), + new Argument("xbc", "CMD execute CMD before a file copy"), + new Argument("xac", "CMD execute CMD after a file copy"), + new Argument("xbr", "CMD execute CMD before a file rename/move"), + new Argument("xar", "CMD execute CMD after a file rename/move"), + new Argument("xbd", "CMD execute CMD before a file delete"), + new Argument("xad", "CMD execute CMD after a file delete"), + new Argument("xm", "CMD execute CMD on message"), + new Argument("xban", "CMD execute CMD if someone gets banned"), + new Argument("grid", "show grid/thumbnails by default"), + new Argument("gsel", "select files in grid by ctrl-click"), + new Argument("sort", "default sort order"), + new Argument("nsort", "natural-sort of leading digits in filenames"), + new Argument("hsortn", "number of sort-rules to add to media URLs"), + new Argument("ufavico", "URL per-volume favicon (.ico/png/gif/svg)"), + new Argument("bg_img", "background image url"), + new Argument("theme", "default theme"), + new Argument("unlist", "dont list files matching REGEX"), + new Argument("dothidden", "enable support for .hidden files"), + new Argument("dlni", "force-download (no-inline) files on click"), + new Argument("html_head", "TXT includes TXT in the , or @PATH for file at PATH"), + new Argument("html_head_s", "TXT additional static text in the html "), + new Argument("tcolor", "#fc0 theme color (a hint for webbrowsers, discord, etc.)"), + new Argument("nodirsz", "don't show total folder size"), + new Argument("du_who", "all show disk-usage info to everyone"), + new Argument("robots", "allows indexing by search engines (default)"), + new Argument("norobots", "kindly asks search engines to leave"), + new Argument("unlistcr", "don't list read-access in controlpanel"), + new Argument("unlistcw", "don't list write-access in controlpanel"), + new Argument("prologues", ".prologue.html files to embed above/before files"), + new Argument("epilogues", ".epilogue.html files to embed below/after files"), + new Argument("readmes", "readme.md,README.md files to embed as readmes"), + new Argument("preadmes", "preadme.md,PREADME.md files to embed as preadmes"), + new Argument("no_sb_md", "disable js sandbox for markdown files"), + new Argument("no_sb_lg", "disable js sandbox for prologue/epilogue"), + new Argument("sb_md", "enable js sandbox for markdown files (default)"), + new Argument("sb_lg", "enable js sandbox for prologue/epilogue (default)"), + new Argument("md_sbf", "list of markdown-sandbox safeguards to disable"), + new Argument("lg_sbf", "list of *logue-sandbox safeguards to disable"), + new Argument("md_sba", "value of iframe allow-prop for markdown-sandbox"), + new Argument("lg_sba", "value of iframe allow-prop for *logue-sandbox"), + new Argument("nohtml", "return html and markdown as text/html"), + new Argument("noscript", "disable most javascript by use of CSP"), + new Argument("ui_noacci", "hide account-info in the UI"), + new Argument("ui_nocpla", "hide cpanel-link in the UI"), + new Argument("ui_nolbar", "hide link-bar in the UI"), + new Argument("ui_nombar", "hide top-menu in the UI"), + new Argument("ui_nonav", "hide navpane+breadcrumbs in the UI"), + new Argument("ui_notree", "hide navpane in the UI"), + new Argument("ui_norepl", "hide repl-button in the UI"), + new Argument("ui_nosrvi", "hide server-info in the UI"), + new Argument("ui_noctxb", "hide context-buttons in the UI"), + new Argument("og", "enable OG (disables hotlinking)"), + new Argument("og_site", "sitename; defaults to --name, disable with '-'"), + new Argument("og_desc", "description text for all files; disable with '-'"), + new Argument("og_th", "jf thumbnail format; j / jf / jf3 / w / w3 / ..."), + new Argument("og_title_a", "audio title format; default: {{ artist }} - {{ title }}"), + new Argument("og_title_v", "video title format; default: {{ title }}"), + new Argument("og_title_i", "image title format; default: {{ title }}"), + new Argument("og_title", "foo fallback title if there's nothing in the db"), + new Argument("og_s_title", "force default title; do not read from tags"), + new Argument("og_tpl", "custom html; see --og-tpl in --help"), + new Argument("og_no_head", "you want to add tags manually with og_tpl"), + new Argument("og_ua", "if defined: only send OG html if useragent matches this regex"), + new Argument("opds", "enable OPDS"), + new Argument("opds_exts", "file formats to list in OPDS feeds; leave empty to show everything"), + new Argument("rw_edit", "md,txt only require read+write to edit .md and .txt"), + new Argument("md_no_br", "newline only on double-newline or two tailing spaces"), + new Argument("md_hist", "where to put markdown backups; s=subfolder, v=volHist, n=nope"), + new Argument("exp", "enable textfile expansion; see --help-exp"), + new Argument("exp_md", "placeholders to expand in markdown files; see --help"), + new Argument("exp_lg", "placeholders to expand in prologue/epilogue; see --help"), + new Argument("txt_eol", "lf enable EOL conversion when writing docs (LF or CRLF)"), + new Argument("notail", "disable ?tail (download a growing file continuously)"), + new Argument("tail_fd", "1 check if file was replaced (new fd) every 1 sec"), + new Argument("tail_rate", "0.2 check for new data every 0.2 sec"), + new Argument("tail_tmax", "30 kill connection after 30 sec"), + new Argument("tail_who", "2 restrict ?tail access (1=admins,2=authed,3=everyone)"), + new Argument("rss", "allow '?rss' URL suffix (experimental)"), + new Argument("rss_sort", "m default sort-order (m/u/n/s)"), + new Argument("rss_fmt_t", "{fname} default title-format"), + new Argument("rss_fmt_d", "{album},{.tn} default description-format"), + new Argument("dots", "allow all users with read-access toenable the option to show dotfiles in listings"), + new Argument("fk", "8 generates per-file accesskeys,which are then required at the \"g\" permission;keys are invalidated if filesize or inode changes"), + new Argument("fka", "8 generates slightly weaker per-file accesskeys,which are then required at the \"g\" permission;not affected by filesize or inode numbers"), + new Argument("dk", "8 generates per-directory accesskeys,which are then required at the \"g\" permission;keys are invalidated if filesize or inode changes"), + new Argument("dks", "per-directory accesskeys allow browsing into subdirs"), + new Argument("dky", "allow seeing files (not folders) inside a specific folderwith \"g\" perm, and does not require a valid dirkey to do so"), + new Argument("rmagic", "expensive analysis for mimetype accuracy"), + new Argument("shr_who", "auth who can create shares? no/auth/a"), + new Argument("unp_who", "2 unpost only if same... 1=ip+name, 2=ip, 3=name"), + new Argument("ups_who", "2 restrict viewing the list of recent uploads"), + new Argument("zip_who", "2 restrict access to download-as-zip/tar"), + new Argument("zipmaxn", "9k reject download-as-zip if more than 9000 files"), + new Argument("zipmaxs", "2g reject download-as-zip if size over 2 GiB"), + new Argument("zipmaxt", "no reply with 'no' if download-as-zip exceeds max"), + new Argument("zipmaxu", "zip-size-limit does not apply to authenticated users"), + new Argument("nopipe", "disable race-the-beam (download unfinished uploads)"), + new Argument("cachectl", "no-cache controls caching in webbrowsers"), + new Argument("mv_retry", "ms-windows: timeout for renaming busy files"), + new Argument("rm_retry", "ms-windows: timeout for deleting busy files"), + new Argument("nospawn", "don't create volume's folder if not exist"), + new Argument("assert_root", "crash on startup if volume's folder not exist"), + new Argument("davauth", "ask webdav clients to login for all folders"), + new Argument("davrt", "show lastmod time of symlink destination, not the link itself(note: this option is always enabled for recursive listings)"), +] + + +// main code ________________________________________________________________________________________________ + + +ebi('rpre').value = sread('rprefix') ?? 'python -m copyparty' +ebi('rpre').oninput = function(){ + swrite('rprefix', this.value) +} + +// get flags +var flagsConf = jread("flagsConf", []); + +// these flags have a better, feature-complete way to be configured +var uiOnlyFlags = QSA('input[data-flag]') +function loadUiFlags(){ + for(var i = 0; i < uiOnlyFlags.length; i++){ + var elem = uiOnlyFlags[i] + var cmd = elem.getAttribute('data-flag') + var val = flagsConf.filter((f) => f.cmd == cmd)[0]?.value; + if(elem){ + var n = cmd + '+' + var o = QS('input[data-flag="' + n + '"]'); + if(o){ + var arr = val ? val.split(',') : val + elem.value = arr ? arr[0] : ''; + var j = 1 + while(o){ + var val2 = arr ? arr[j] : '' + if(val2){ + var prefix = o.getAttribute('data-prefix'); + if(prefix) val2 = val2.replace(prefix, '') + o.value = val2 + } + else o.value = '' + n += '+' + j++; + o = QS('input[data-flag="' + n + '"]'); + } + } + else { + if (elem.getAttribute('type') == 'checkbox' || elem.getAttribute('type') == 'radio') + elem.checked = val ? true : false; + else + elem.value = val != undefined ? val : '' + } + } + elem.oninput = uiOptInput; + } +}; +var synching = false; +var uiOptInput = function(){ + if(synching) return; + + // https://bugzilla.mozilla.org/show_bug.cgi?id=2035033 + if(FIREFOX && this.value == '#000000') return; + + var cmd = this.getAttribute('data-flag'); + if(!cmd) return; + var e1 = this; + if(cmd.endsWith('+')){ + cmd = cmd.replaceAll('+', ''); + e1 = QS('input[data-flag="' + cmd + '"]') + } + var val = e1.value + + // collect all parts of cmd + var n = cmd + '+' + var o = QS('input[data-flag="' + n + '"]'); + if(o){ + while(o){ + if(o.value){ + var prefix = o.getAttribute('data-prefix'); + val += ',' + (prefix ? prefix : '') + o.value; + } + n += '+' + o = QS('input[data-flag="' + n + '"]'); + } + } + console.log(val) + if (e1.getAttribute('type') == 'checkbox' || e1.getAttribute('type') == 'radio') + val = e1.checked ? 'on' : ''; + if(val){ + var f = flagsConf.filter((f) => f.cmd == cmd)[0] + if(!f) + flagsConf.push({cmd: cmd, value: val}); + else + f.value = val; + } + else{ + var f = flagsConf.filter((f) => f.cmd == cmd)[0] + if(!f) return; + var index = flagsConf.indexOf(f) + console.log("removing arg at index " + index) + flagsConf.splice(index, 1) + } + jwrite("flagsConf", flagsConf); + + n = this.id.replaceAll('+', '') + if(!n) return; + o = ebi(n) + synching = true; + while(o){ + if(o.value != this.value) + o.value = this.value; + n += '+' + o = ebi(n) + } + synching = false; +} +loadUiFlags(); + +function loadFlags(){ + var container = ebi('flags'); + container.innerHTML = "" + for (var i = 0; i < flagsConf.length; i++) { + if(QS('input[data-flag="' + flagsConf[i].cmd + '"]')) continue; + var a = mknod("div"); + var flag = flags.filter((f) => f.cmd == flagsConf[i].cmd)[0]; + a.setAttribute("tt", flag?.help); + a.setAttribute("ref", i); + a.innerHTML = '' + flagsConf[i].cmd + "\n"; + var inp = mknod("input"); + inp.setAttribute("value", flagsConf[i].value); + if(flag?.help.match(/\(default:/)){ + var ph = flag.help.match(/(?:\(default:)(.*)(?:\))/)[1] + ph = ph.trim() + inp.setAttribute("placeholder", ph); + if(ph.toLowerCase() == "false" || ph.toLowerCase() == "true"){ + inp.setAttribute("disabled", "") + clmod(a, "binary", true) + } + } + inp.oninput = function(e){ + flagsConf[this.parentNode.getAttribute("ref")].value = this.value; + jwrite("flagsConf", flagsConf); + } + a.appendChild(inp); + var b = mknod("button", "", "×"); + clmod(b, "delBtn", true); + b.value = i + b.onclick = function(){ + console.log("removing arg at index " + this.value) + flagsConf.splice(this.value, 1) + loadFlags(); + jwrite("flagsConf", flagsConf); + } + a.appendChild(b) + container.appendChild(a) + } +} +console.log(flagsConf); +loadFlags(); + +// based on https://www.w3schools.com/howto/howto_js_autocomplete.asp +var currentFocus; +function addActive(x) { + if (!x) return false; + removeActive(x); + if (currentFocus >= x.length) currentFocus = 0; + if (currentFocus < 0) currentFocus = (x.length - 1); + x[currentFocus].classList.add("autocomplete-active"); +} +function removeActive(x) { + for (var i = 0; i < x.length; i++) { + x[i].classList.remove("autocomplete-active"); + } +} +function closeAllLists(elmnt) { + var x = document.getElementsByClassName("autocomplete-list"); + for (var i = 0; i < x.length; i++) { + if (elmnt != x[i]) { + x[i].parentNode.removeChild(x[i]); + } + } +} +document.addEventListener("click", function (e) { + if(clgot(e.target, 'autocomplete')) + closeAllLists(ebi(e.target.id + '-list')) + else + closeAllLists(e.target); +}); +document.onkeyup = function(e){ + if(e.key.match("Esc")){ + closeAllLists(); + var o = QS('.autocomplete:focus') + if(o) o.blur(); + } +} +function autocompleteFlags(inp, arr) { + inp.onclick = + inp.oninput = function(e) { + var a, b, i, val = this.value.trim().split(" ")[this.value.split(" ").length - 1]; + closeAllLists(); + currentFocus = -1; + if(!val) return; + a = mknod("DIV", this.id + "-list"); + clmod(a, "autocomplete-list", true) + this.parentNode.appendChild(a); + + function iClick(e){ + var val = this.getElementsByTagName("input")[0].value; + inp.value = ""; + closeAllLists(); + var exel = QS('input[data-flag="' + val + '"]') + if (exel) { + exel.focus(); + return + } + flagsConf.push({cmd: val, value: ""}); + + console.log(flagsConf); + loadFlags(); + jwrite("flagsConf", flagsConf); + + inp.focus(); + } + + var leftovers = []; + for (i = 0; i < arr.length; i++) { + // check if cmd contains query + if (arr[i].cmd.toUpperCase().match(val.toUpperCase())) { + b = mknod("DIV"); + b.innerHTML = "" + arr[i].cmd + "\n"; + b.innerHTML += arr[i].help; + b.innerHTML += ""; + b.onclick = iClick; + a.appendChild(b); + } + else{ + leftovers.push(arr[i]) + } + } + // achieves basic sorting prio by appending help text matches to the dropdown later + for (i = 0; i < leftovers.length; i++) { + // check if help text contains query + if (leftovers[i].help.toUpperCase().match(val.toUpperCase())) { + b = mknod("DIV"); + b.innerHTML = "" + leftovers[i].cmd + "\n"; + b.innerHTML += leftovers[i].help; + b.innerHTML += ""; + b.onclick = iClick; + a.appendChild(b); + } + } + + }; + inp.addEventListener("keydown", function(e) { + var x = ebi(this.id + "-list"); + if (x) x = x.getElementsByTagName("div"); + console.log(e.key) + if (e.key === 'ArrowDown' || e.key === 'Tab') { + ev(e); + currentFocus++; + addActive(x); + } else if (e.key === 'ArrowUp') { + ev(e); + currentFocus--; + addActive(x); + } else if (e.key === 'Enter') { + ev(e); + if (currentFocus > -1) { + if (x) x[currentFocus].click(); + } + else + if(x && x.length > 0) x[0].click(); + } + }); +} +function autocompleteVolFlags(inp, arr, index) { + inp.onclick = + inp.oninput = function(e) { + var a, b, i, val = this.value.trim().split(" ")[this.value.split(" ").length - 1]; + closeAllLists(); + currentFocus = -1; + if(!val) return; + a = mknod("DIV", this.id + "-list"); + clmod(a, "autocomplete-list", true) + this.parentNode.appendChild(a); + + function iClick(e){ + var val = this.getElementsByTagName("input")[0].value; + inp.value = ""; + closeAllLists(); + volsConf[index].flags.push({cmd: val, value: ""}); + + console.log(volsConf[index].flags); + loadVolumes(); + jwrite("volsConf", volsConf); + + inp.focus(); + } + + var leftovers = []; + for (i = 0; i < arr.length; i++) { + // check if cmd contains query + if (arr[i].cmd.toUpperCase().match(val.toUpperCase())) { + b = mknod("DIV"); + b.innerHTML = "" + arr[i].cmd + "\n"; + b.innerHTML += arr[i].help; + b.innerHTML += ""; + b.onclick = iClick; + a.appendChild(b); + } + else{ + leftovers.push(arr[i]) + } + } + // achieves basic sorting prio by appending help text matches to the dropdown later + for (i = 0; i < leftovers.length; i++) { + // check if help text contains query + if (leftovers[i].help.toUpperCase().match(val.toUpperCase())) { + b = mknod("DIV"); + b.innerHTML = "" + leftovers[i].cmd + "\n"; + b.innerHTML += leftovers[i].help; + b.innerHTML += ""; + b.onclick = iClick; + a.appendChild(b); + } + } + + }; + inp.addEventListener("keydown", function(e) { + var x = ebi(this.id + "-list"); + if (x) x = x.getElementsByTagName("div"); + if (e.key === 'ArrowDown' || e.key === 'Tab') { + ev(e); + currentFocus++; + addActive(x); + } else if (e.key === 'Up') { + ev(e); + currentFocus--; + addActive(x); + } else if (e.key === 'Enter') { + ev(e); + if (currentFocus > -1) { + if (x) x[currentFocus].click(); + } + else + if(x && x.length > 0) x[0].click(); + } + }); +} +autocompleteFlags(ebi('flagSearch'), flags); + +// alternative call that hides flags that have a better UI method +//autocompleteFlags(ebi('flagSearch'), flags.filter((arg) => !QS('input[data-flag="' + arg.cmd + '"]'))); + + +// users +var usrsConf = jread("usrsConf", []); +function loadUsers(){ + var container = ebi('users'); + container.innerHTML = "" + for (var i = 0; i < usrsConf.length; i++) { + var a = mknod("form"); + a.setAttribute("ref", i); + + var handle = mknod('p', '', '::::') + clmod(handle, 'dhandle', true) + handle.setAttribute('draggable', true) + handle.ondragstart = function(e){ + e.dataTransfer.setData("text", ', ' + usrsConf[this.parentNode.getAttribute("ref")].name); + } + a.appendChild(handle) + + var inp = mknod("input"); + inp.setAttribute("value", usrsConf[i].name) + inp.setAttribute("placeholder", "username") + inp.setAttribute("autocomplete", "new-password") + clmod(inp, "name", true) + inp.oninput = function(e){ + usrsConf[this.parentNode.getAttribute("ref")].name = this.value; + jwrite("usrsConf", usrsConf); + } + a.appendChild(inp); + + var inp2 = mknod("input"); + inp2.setAttribute("value", usrsConf[i].pw) + inp2.setAttribute("type", "password") + inp2.setAttribute("placeholder", "password") + inp2.setAttribute("autocomplete", "new-password") + clmod(inp2, "pw", true) + inp2.oninput = function(e){ + usrsConf[this.parentNode.getAttribute("ref")].pw = this.value; + jwrite("usrsConf", usrsConf); + } + a.appendChild(inp2); + + var b = mknod("button", "", "×"); + clmod(b, "delBtn", true); + b.value = i + b.onclick = function(){ + console.log("removing user at index " + this.value) + usrsConf.splice(this.value, 1) + loadUsers(); + jwrite("usrsConf", usrsConf); + } + a.appendChild(b) + container.appendChild(a) + } +} +loadUsers(); + +ebi('addUser').onclick = function(){ + usrsConf.push({name: "", pw: "", grp: ""}); + + console.log(usrsConf); + loadUsers(); + + var ins = QSA('#users .name') + ins[ins.length - 1].focus(); +} + +var groups = jread("groupsConf", []); +function loadGroups(){ + var container = ebi('groups'); + container.innerHTML = "" + for (var i = 0; i < groups.length; i++) { + var a = mknod("div"); + a.setAttribute("ref", i); + + var inp = mknod("input"); + inp.setAttribute("value", groups[i].name) + inp.setAttribute("placeholder", "group name") + clmod(inp, "name", true) + inp.oninput = function(e){ + groups[this.parentNode.getAttribute("ref")].name = this.value; + jwrite("groupsConf", groups); + } + a.appendChild(inp); + + var inp2 = mknod("input"); + inp2.setAttribute("value", groups[i].users) + inp2.setAttribute("placeholder", "(drag users here, or type)") + clmod(inp2, "users", true) + inp2.oninput = function(e){ + while(this.value.startsWith(',') || this.value.startsWith(' ')) this.value = this.value.slice(1); + groups[this.parentNode.getAttribute("ref")].users = this.value; + jwrite("groupsConf", groups); + } + a.appendChild(inp2); + + var b = mknod("button", "", "×"); + clmod(b, "delBtn", true); + b.value = i + b.onclick = function(){ + console.log("removing group at index " + this.value) + groups.splice(this.value, 1) + loadGroups(); + jwrite("groupsConf", groups); + } + a.appendChild(b) + container.appendChild(a) + } +} +loadGroups(); +ebi('addGrp').onclick = function(){ + groups.push({name: "", users: ""}); + + loadGroups(); + + var ins = QSA('#groups .name') + ins[ins.length - 1].focus(); +} + +// volumes +var volsConf = jread("volsConf", []); +function loadVolumes(){ + var container = ebi('volumes'); + container.innerHTML = "" + + var srcInput = function(){ + volsConf[this.parentNode.parentNode.getAttribute("ref")].src = this.value; + jwrite("volsConf", volsConf); + } + var dstInput = function(){ + volsConf[this.parentNode.parentNode.getAttribute("ref")].dst = this.value; + jwrite("volsConf", volsConf); + } + for (var i = 0; i < volsConf.length; i++) { + var a = mknod("div"); + a.setAttribute("ref", i); + + var h = mknod("p", "", "Volume #" + i) + a.appendChild(h); + + var h3_1 = mknod("h3", "", "real path"); + var inp = mknod("input", "rp" + i, volsConf[i].src); + inp.setAttribute("value", volsConf[i].src); + inp.setAttribute("placeholder", "/storage/emulated/0/files"); + clmod(inp, "src", true); + inp.oninput = srcInput; + h3_1.appendChild(inp); + a.appendChild(h3_1); + + a.appendChild(document.createTextNode("➡️")); + + var h3_2 = mknod("h3", "", "virtual path"); + var inp2 = mknod("input", "vp" + i, volsConf[i].dst); + inp2.setAttribute("value", volsConf[i].dst); + inp2.setAttribute("placeholder", "/"); + clmod(inp2, "dst", true); + inp2.oninput = dstInput; + h3_2.appendChild(inp2); + a.appendChild(h3_2); + + var pbtn = mknod("button", "", "⚙️ permissions") + pbtn.setAttribute("value", i) + pbtn.onclick = showPermConf; + a.appendChild(pbtn) + + var h3_3 = mknod("h3", "", "volume settings") + clmod(h3_3, "h_vcfg", true) + a.appendChild(h3_3) + + if(volsConf[i].flags == undefined) + volsConf[i].flags = [] + + var vfc = mknod("div", "vfc" + i) + for(var j = 0; j < volsConf[i].flags.length; j++){ + var a2 = mknod("div"); + var flag = volflags.filter((f) => f.cmd == volsConf[i].flags[j].cmd)[0]; + a2.setAttribute("tt", flag?.help); + a2.setAttribute("vol", i); + a2.setAttribute("ref", j); + a2.innerHTML = ( + "" + volsConf[i].flags[j].cmd + "" + ); + var inp3 = mknod("input"); + inp3.setAttribute("value", volsConf[i].flags[j].value); + if(flag?.help.match(/\(default:/)){ + var ph = flag.help.match(/(?:\(default:)(.*)(?:\))/)[1] + ph = ph.trim() + inp3.setAttribute("placeholder", ph); + if(ph.toLowerCase() == "false" || ph.toLowerCase() == "true"){ + inp.setAttribute("disabled", "") + clmod(a, "binary", true) + } + } + inp3.oninput = function(e){ + volsConf[this.parentNode.getAttribute("vol")] + .flags[this.parentNode.getAttribute("ref")] + .value = this.value; + jwrite("volsConf", volsConf); + if(this.value.match(':')) + toast.err(30, 'volflags cannot contain ":" in commandline arguments. if you really need to use ":", consider making a configuration file.') + } + a2.appendChild(inp3); + var b2 = mknod("button", "", "×"); + clmod(b2, "delBtn", true); + b2.value = j + b2.onclick = function(){ + console.log("removing volflag at index " + this.value) + volsConf[this.parentNode.getAttribute("vol")] + .flags.splice(this.value, 1) + loadVolumes(); + jwrite("volsConf", volsConf); + } + a2.appendChild(b2) + vfc.appendChild(a2) + } + a.appendChild(vfc) + + var pr = mknod("p", "", "+ add volume setting") + clmod(pr, "inlinePrompt", true) + a.appendChild(pr) + + var vf = mknod("input", "vf" + i) + clmod(vf, "autocomplete", true) + vf.setAttribute("placeholder", "🔎 volflags"); + autocompleteVolFlags(vf, volflags, i) + a.appendChild(vf) + + var b = mknod("button", "", "×"); + clmod(b, "delBtn", true); + b.value = i + b.onclick = function(){ + console.log("removing volume at index " + this.value) + volsConf.splice(this.value, 1) + loadVolumes(); + jwrite("volsConf", volsConf); + } + a.appendChild(b) + container.appendChild(a) + } +} +loadVolumes(); + +var perms = [ + {letter: "r", desc: "read: list folder contents, download files"}, + {letter: "w", desc: 'write: upload files; need "r" to see the uploads'}, + {letter: "m", desc: 'move: move files and folders; need "w" at destination'}, + {letter: "d", desc: "delete: permanently delete files and folders"}, + {letter: "g", desc: "get: download files, but cannot see folder contents"}, + {letter: "G", desc: 'upget: "get", but can see filekeys of their own uploads'}, + {letter: "h", desc: 'html: "get", but folders return their index.html'}, + {letter: ".", desc: "dots: user can ask to show dotfiles in listings"}, + {letter: "a", desc: "admin: can see uploader IPs, config-reload"}, + {letter: "A", desc: 'all: same as "rwmda." (read/write/move/delete/admin/dotfiles)'}, +] +function loadPerms(vol){ + var container = ebi('perm-list') + container.innerHTML = "" + var volume = volsConf[vol] + ebi('h_perms').innerHTML = "permissions for " + (volume.dst.length > 0 ? volume.dst : "home") + var users = [] + for(var i = 0; i < groups.length; i++){ + users.push({name: '@' + groups[i].name}) + } + users = users.concat(usrsConf) + for(var i = -1; i < users.length; i++){ + var usr = i > -1 ? users[i] : {name: "everyone"}; + var aperms = volume.perms ?? [] + var uperms = aperms.filter((o) => o.user == usr.name)[0] + var a = mknod("div") + a.setAttribute("ref", i); + + var h = mknod("p", "", usr.name) + a.appendChild(h) + + var b = mknod("div") + for(var j = 0; j < perms.length; j++){ + var cid = "upc" + i.toString() + j.toString() + var c = mknod("input", cid) + c.setAttribute("type", "checkbox") + c.setAttribute("uid", i); + c.setAttribute("p", perms[j].letter); + if(uperms?.perms.includes(perms[j].letter)) + c.setAttribute("checked", "") + c.setAttribute("tt", perms[j].desc) + c.onclick = function(){ + var p = this.getAttribute("p") + var uid = this.getAttribute("uid") + var usr = uid > -1 ? users[uid] : {name: "everyone"} + var o = aperms.filter((o) => o.user == usr.name)[0] + if(!o){ + o = {user: usr.name, perms: ""} + aperms.push(o) + } + if(!o.perms.includes(p)) + o.perms += p + else + o.perms = o.perms.replace(p, "") + if(o.perms.length == 0) + aperms.splice(o, 1) + volume.perms = aperms + } + b.appendChild(c) + + var l = mknod("label", "", perms[j].letter) + l.setAttribute("for", cid) + l.setAttribute("tt", perms[j].desc) + b.appendChild(l) + } + a.appendChild(b) + container.appendChild(a) + } + + tt.init(); +} +function showPermConf(){ + var index = this.value + if(index != undefined && index !== ""){ + loadPerms(index) + } + else{ + jwrite("volsConf", volsConf); + console.log(volsConf) + } + clmod(ebi('perms-modal'), 'vis', 't'); +} +ebi('m_close').onclick = +ebi('m_closeArea').onclick = + showPermConf; + +ebi('addVol').onclick = function(){ + volsConf.push({src: "", dst: "", perms: []}); + + console.log(volsConf); + loadVolumes(); + + var ins = QSA('#volumes .src') + ins[ins.length - 1].focus(); +} + + +// config generation +function esc(s){ + if(s.match(' ') && !s.startsWith('"')) + s = '"' + s + '"'; + return s; +} +function getConfig(){ + var conf = ebi('rpre').value + ' '; + + for(var i = 0; i < flagsConf.length; i++){ + var val = flagsConf[i].value; + // i think none of the options want a leading # for colors + if(val.startsWith('#')) val = val.slice(1); + // checkboxes set text value to 'on' + conf += flagsConf[i].cmd + ((val !== '' && val !== 'on') ? ('=' + esc(val)) : '') + " "; + } + + for(var i = 0; i < usrsConf.length; i++){ + if(usrsConf[i].pw != ""){ + conf += "-a=" + esc(usrsConf[i].name) + ':' + esc(usrsConf[i].pw) + " " + } + } + + for(var i = 0; i < groups.length; i++){ + if(groups[i].name != "" && groups[i].users != "") + conf += "--grp=" + esc(groups[i].name) + ':' + groups[i].users.replaceAll(' ', ',').replaceAll(',,', ',') + " " + } + + for(var i = 0; i < volsConf.length; i++){ + conf += "-v=" + esc(volsConf[i].src) + ":" + esc(volsConf[i].dst) + var a = volsConf[i].perms.filter((u) => u.user === 'everyone') + var b = volsConf[i].perms.filter((u) => u.user !== 'everyone') + volsConf[i].perms = [...a, ...b] + for(var j = 0; j < volsConf[i].perms.length; j++){ + var uname = volsConf[i].perms[j].user + if(uname) + conf += ":" + volsConf[i].perms[j].perms + + (uname != "everyone" ? "," + uname : "") + } + for(var j = 0; j < volsConf[i].flags.length; j++){ + conf += ":c," + volsConf[i].flags[j].cmd + (volsConf[i].flags[j].value ? '="' + volsConf[i].flags[j].value + '"' : '') + } + conf += ": " + } + + return conf; +} +ebi('copyBtn').onclick = function(){ + cliptxt(getConfig(), function(){ + var a = ebi('copyBtn').innerHTML; + ebi('copyBtn').innerHTML = "✅"; + setTimeout(function(){ebi('copyBtn').innerHTML = a;}, 1000); + }); +} + +ebi('resetBtn').onclick = function(){ + modal.confirm("this will delete your entire configuration on this page! (not your actual server config) continue?", reset, null); +} +function reset(){ + flagsConf = [] + volsConf = [] + usrsConf = [] + groups = [] + + swrite('rprefix', 'python -m copyparty') + jwrite('flagsConf', flagsConf) + jwrite('volsConf', volsConf) + jwrite('usrsConf', usrsConf) + jwrite('groups', groups) + + ebi('rpre').value = 'python -m copyparty' + loadUiFlags() + loadFlags() + loadVolumes() + loadUsers() + loadGroups() +} +ebi('importBtn').onclick = function(){ + modal.confirm("this will overwrite your configuration on this page! (not your actual server config) continue?", importConfig, null); +} +function importConfig(txt){ + if(!txt || txt === true) + txt = ebi('importTxt').value.trim() + + flagsConf = [] + volsConf = [] + usrsConf = [] + groups = [] + + var first = true; + var buffer = ""; + while(txt.length > 0){ + var cmd = txt.match(/^.*?\s-/) + if(!cmd) + cmd = txt + else + cmd = cmd[0] + txt = txt.slice(cmd.length) + if(cmd.match(/"/g)?.length % 2 > 1){ + var m = txt.match(/"\S*\s-/)[0] + cmd += m + txt.slice(m.length) + } + var c = (first ? '' : '-') + cmd.split(/(\s|=)/)[0] + first = false + var val = cmd.slice(c.length + first) + val = val.replace(/(^|\s)-$/, '') + if(val === '') val = 'on' + console.log(c) + console.log(val) + var flag = flags.filter((f) => f.cmd == c)[0]; + if(!flag){ + buffer += cmd + continue + } + else if(buffer){ + buffer = buffer.slice(0, buffer.length - 2) + swrite('rprefix', buffer) + ebi('rpre').value = buffer + } + buffer = '' + + switch(c){ + case "-v": + volsConf.push(getVolFromStr(val)) + break; + case "-a": + var name = val.split(':')[0] + var pw = val.slice(name.length + 1) + var user = usrsConf.filter((u) => u.name == name)[0] + if(!user) + usrsConf.push({name: name, pw: pw}) + else + user.pw = pw + break; + case "--grp": + var name = val.split(':')[0] + var u = val.slice(name.length + 1) + groups.push({name: name, users: u}) + + var n = u.split(',') + for(var j = 0; j < n.length; j++){ + if(n[j] && !usrsConf.filter((u) => u.name === n[j])[0]) + usrsConf.push({name: n[j], pw: ''}) + } + break; + default: + if(val.startsWith('"') && val.endsWith('"')) + val = val.slice(1, val.length - 1) + flagsConf.push({cmd: c, value: val}); + break; + } + } + + console.log(flagsConf) + console.log(volsConf) + console.log(usrsConf) + console.log(groups) + + jwrite('flagsConf', flagsConf) + jwrite('volsConf', volsConf) + jwrite('usrsConf', usrsConf) + jwrite('groups', groups) + + loadUiFlags() + loadFlags() + loadVolumes() + loadUsers() + loadGroups() +} +function getVolFromStr(s){ + var parts = s.split(':') + var src = parts[0] + var dst = parts[1] + var rest = parts.slice(2) + var perms = [] + var flags = [] + for(var i = 0; i < rest.length; i++){ + var o = rest[i] + if(o.startsWith('c,')){ + o = o.slice(2) + var c = o.split(/(\s|=)/)[0] + var val = o.slice(c.length + 1) + if(val.startsWith('"') && val.endsWith('"')) + val = val.slice(1, val.length - 1) + flags.push({cmd: c, value: val}) + } + else{ + var p = o.split(',')[0] + var n = o.slice(p.length + 1) + for(var j = 0; j < n.length; j++){ + console.log('user: ' + n[j] + ' gets perms: ' + p) + perms.push({user: n[j], perms: p}) + + if(n[j] && !usrsConf.filter((u) => u.name === n[j])[0]) + usrsConf.push({name: n[j], pw: ''}) + } + } + } + return {src: src, dst: dst, perms: perms, flags: flags} +} + +tt.init(); + +J_CFG = 2; diff -NarU2 a/copyparty/web/copyparty.svg b/copyparty/web/copyparty.svg --- a/copyparty/web/copyparty.svg 1970-01-01 00:00:00.000000000 +0000 +++ b/copyparty/web/copyparty.svg 2026-07-08 15:11:40.609175034 +0000 @@ -0,0 +1 @@ + \ No newline at end of file diff -NarU2 a/copyparty/web/idp.html b/copyparty/web/idp.html --- a/copyparty/web/idp.html 2026-07-08 15:13:32.822003171 +0000 +++ b/copyparty/web/idp.html 2026-07-08 15:11:40.609175034 +0000 @@ -9,4 +9,5 @@ + diff -NarU2 a/copyparty/web/manifest.json b/copyparty/web/manifest.json --- a/copyparty/web/manifest.json 1970-01-01 00:00:00.000000000 +0000 +++ b/copyparty/web/manifest.json 2026-07-08 15:11:40.609175034 +0000 @@ -0,0 +1,32 @@ +{ + "short_name": "copyparty", + "name": "copyparty", + "description": "the all in one file server", + "icons": [ + { + "src": "/.cpr/copyparty.svg", + "sizes": "512x512", + "type": "image/svg+xml" + } + ], + "start_url": "/?utm_medium=PWA&utm_source=launcher", + "display": "minimal-ui", + "theme_color": "#222", + "background_color": "#222", + "share_target": { + "action": "/?utm_medium=PWA&utm_source=share-target&share-target", + "method": "POST", + "enctype": "multipart/form-data", + "params": { + "title": "share", + "text": "upload", + "url": "/", + "files": [ + { + "name": "files", + "accept": ["*/*"] + } + ] + } + } + } \ No newline at end of file diff -NarU2 a/copyparty/web/md.css b/copyparty/web/md.css --- a/copyparty/web/md.css 2026-07-08 15:13:32.822003171 +0000 +++ b/copyparty/web/md.css 2026-07-08 15:11:40.609175034 +0000 @@ -141,5 +141,5 @@ } #mn { - padding: 1.3em 0 .7em 1em; + padding: .8em 0 .2em 1em; border-bottom: 1px solid #ccc; background: #eee; @@ -148,8 +148,9 @@ } #mn a { + display: inline-block; color: #444; background: none; margin: 0 0 0 -.2em; - padding: .3em 0 .3em .4em; + padding: .5em .4em .5em 0; text-decoration: none; border: none; @@ -321,5 +322,5 @@ html.z #toc, html.z #mw { - scrollbar-color: #b80 #282828; + scrollbar-color: rgba(203, 203, 203, 0.634) #282828; } html.z #toc::-webkit-scrollbar-track { diff -NarU2 a/copyparty/web/mde.css b/copyparty/web/mde.css --- a/copyparty/web/mde.css 2026-07-08 15:13:32.822003171 +0000 +++ b/copyparty/web/mde.css 2026-07-08 15:11:40.610174059 +0000 @@ -10,4 +10,7 @@ +:root { + background: none; +} html { line-height: 1.5em; @@ -40,10 +43,11 @@ #mn { font-weight: normal; - margin: 1.3em 0 .7em 1em; + margin: .8em 0 .2em 1em; } #mn a { + display: inline-block; color: #444; margin: 0 0 0 -.2em; - padding: 0 0 0 .4em; + padding: .5em .4em .5em 0; text-decoration: none; /* ie: */ diff -NarU2 a/copyparty/web/mde.html b/copyparty/web/mde.html --- a/copyparty/web/mde.html 2026-07-08 15:13:32.822003171 +0000 +++ b/copyparty/web/mde.html 2026-07-08 15:11:40.610174059 +0000 @@ -8,4 +8,5 @@ + diff -NarU2 a/copyparty/web/mde.js b/copyparty/web/mde.js --- a/copyparty/web/mde.js 2026-07-08 15:13:32.822003171 +0000 +++ b/copyparty/web/mde.js 2026-05-22 21:41:25.295039355 +0000 @@ -10,5 +10,5 @@ var n = location + ''; n = (n.slice(n.indexOf('//') + 2).split('?')[0] + '?v').split('/'); - n[0] = 'top'; + n[0] = '🏠'; var loc = []; var nav = []; diff -NarU2 a/copyparty/web/md.html b/copyparty/web/md.html --- a/copyparty/web/md.html 2026-07-08 15:13:32.822003171 +0000 +++ b/copyparty/web/md.html 2026-07-08 15:11:40.609175034 +0000 @@ -8,4 +8,5 @@ + diff -NarU2 a/copyparty/web/md.js b/copyparty/web/md.js --- a/copyparty/web/md.js 2026-07-08 15:13:32.822003171 +0000 +++ b/copyparty/web/md.js 2026-05-22 21:41:25.294836617 +0000 @@ -42,5 +42,5 @@ o = mknod('a'); o.setAttribute('href', link); - o.textContent = uricom_dec(parts[a].split('?')[0]) || 'top'; + o.textContent = uricom_dec(parts[a].split('?')[0]) || '🏠'; dom_nav.appendChild(o); } diff -NarU2 a/copyparty/web/msg.html b/copyparty/web/msg.html --- a/copyparty/web/msg.html 2026-07-08 15:13:32.822003171 +0000 +++ b/copyparty/web/msg.html 2026-07-08 15:11:40.610174059 +0000 @@ -8,4 +8,5 @@ +
        \n' + '\n' + - '
        -[❌] ' + L.sl_close + ' -- ' + L.sl_hits.format(res.hits.length) + (res.trunc ? ' -- ' + L.sl_moar + '' : '') + '
        -[❌] ' + L.sl_close + ' -- ' + L.sl_hits.format(res.hits.length) + (res.trunc ? ' -- ' + L.sl_moar + '' : '') + '
        -
        ' + links + + var links = linksplit(r.rp + '', null, id, true).join('/'), + nodes = ['
        -
        ' + links + '
        ' + hsz]; @@ -6847,5 +7759,5 @@ artist = '', title = '', - ret = {'hits': [], 'tag_order': ['artist', 'title', '.dur'], 'trunc': false}; + ret = { 'hits': [], 'tag_order': ['artist', 'title', '.dur'], 'trunc': false }; for (var a = 0; a < lines.length; a++) { @@ -6883,5 +7795,5 @@ "sz": 100000 + n, "rp": url, - "tags": {".dur": dur, "artist": artist, "title": title} + "tags": { ".dur": dur, "artist": artist, "title": title } }); dur = 1; @@ -6898,5 +7810,4 @@ } - function aligngriditems() { if (!treectl) @@ -6922,8 +7833,10 @@ val = 'left'; } else { - val = treectl.hidden ? 'center' : 'space-between'; + val = 'center';// treectl.hidden ? 'center' : 'space-between'; } if (st.justifyContent != val) st.justifyContent = val; + + onwidgetresize(); } onresize100.add(aligngriditems); @@ -6946,4 +7859,77 @@ onresize100.add(filecolwidth, true); +function onwidgetresize(){ + var mumodal = QS('#music.vis'); + var widget = ebi('widget'); + var bar = ebi('pctl'); + var pbarthinpos = ebi('pbarthinpos'); + + if(mumodal){ + var pb_container = ebi('mu_pbb'); + pb_container.appendChild(ebi('progbar')); + pb_container.appendChild(ebi('altprogbar')); + ebi('mu_toggles').appendChild(ebi('cyclebtn_loopmode')); + ebi('mu_toggles').appendChild(ebi('au_shuf')); + ebi('altprogbar').maxWidth = ''; + ebi('mu_vol').appendChild(ebi('pvolbg')); + pbar.onresize(); + return; + } + + var width = widget.offsetWidth; + + var thin = width < 800; //px + + clmod(ebi('pathBar'), 'thin', thin); + clmod(ebi('wrap'), 'thin', thin); + + thin = thin || IE; + + var gtc = 'max-content max-content max-content ' + (thin ? '' : '20%') + ' auto max-content max-content max-content'; + if(!thin && bar.children.length < gtc.split(' ').length){ + try{ + //wide + ebi('trackname').after(ebi('progbar')); + ebi('trackname').after(ebi('altprogbar')); + + bar.style.paddingTop = '0'; + } + catch (e) { + // unnacounted old browser fallback + console.log(e); + thin = true; + } + } + if(thin && pbarthinpos.children.length < 2){ + //thin + pbarthinpos.appendChild(ebi('progbar')); + pbarthinpos.appendChild(ebi('altprogbar')); + bar.style.paddingTop = '.3em'; + } + ebi('altprogbar').maxWidth = thin ? '' : '40vw' + bar.style.gridTemplateColumns = gtc; + + clmod(widget, 'thin', thin); + + if(ebi('mu_vol').children.length > 0){ + bar.appendChild(ebi('pvolbg')); + } + var inmu = ebi('mu_toggles').children.length + if(!thin && inmu){ + ebi('au_tgls').appendChild(ebi('cyclebtn_loopmode')); + ebi('au_tgls').appendChild(ebi('au_shuf')); + } + else if(thin && !inmu){ + ebi('mu_toggles').appendChild(ebi('cyclebtn_loopmode')); + ebi('mu_toggles').appendChild(ebi('au_shuf')); + } + + pbar.onresize(); + + // keep path scrolled right + ebi('path').onresize = keep_right(ebi('path')); +} +window.addEventListener('resize', onwidgetresize); +onwidgetresize(); var treectl = (function () { @@ -6971,5 +7957,5 @@ bcfg_bind(r, 'ireadme', 'ireadme', true); bcfg_bind(r, 'idxh', 'idxh', idxh, setidxh); - bcfg_bind(r, 'dyn', 'dyntree', true, onresize); + bcfg_bind(r, 'dyn', 'dyntree', false, onresize); // dyntree size offset needs to be considered when drag-resizing before considering to re-enable this default bcfg_bind(r, 'csel', 'csel', dgsel); bcfg_bind(r, 'dsel', 'dsel', !MOBILE); @@ -6986,5 +7972,5 @@ bcfg_bind(r, 'dir1st', 'dir1st', true, resort); setwrap(bcfg_bind(r, 'wtree', 'wraptree', true, setwrap)); - setwrap(bcfg_bind(r, 'parpane', 'parpane', true, onscroll)); + setwrap(bcfg_bind(r, 'parpane', 'parpane', !N3DS, onscroll)); bcfg_bind(r, 'htree', 'hovertree', false, reload_tree); bcfg_bind(r, 'ask', 'bd_ask', MOBILE && FIREFOX); @@ -7025,20 +8011,37 @@ get_tree("", get_evpath(), true); - r.show(); + r.show(nostore); + + clmod(ebi('treeToggleBtn'), 'on', true) } - r.show = function () { + r.show = function (instant) { r.hidden = false; if (!entreed) { - ebi('path').style.display = nonav ? 'none' : 'inline-block'; return; } - ebi('path').style.display = 'none'; - ebi('tree').style.display = 'block'; + var tree = ebi('tree') + tree.style.display = 'block'; + tree.style.width = 0; + clmod(ebi('pathBar'), 'shifted', false); window.addEventListener('scroll', onscroll); window.addEventListener('resize', onresize); - onresize(); - aligngriditems(); + + if(!instant){ + // makes animation work by waiting for next frame + setTimeout(function () { + onresize(); + aligngriditems(); + }, 10); + } + else { + tree.style.transition = 'none'; + onresize(); + setTimeout(function(){ + aligngriditems(); + tree.style.transition = ''; + }, 1) + } }; @@ -7052,14 +8055,32 @@ if (!nonav) ebi('path').style.display = ''; + + clmod(ebi('treeToggleBtn'), 'on', false) + }; + + r.toggleTree = function (e, nostore) { + if(entreed){ + r.detree(e, nostore); + } + else{ + r.entree(e, nostore); + } }; r.hide = function () { r.hidden = true; - ebi('path').style.display = 'none'; - ebi('tree').style.display = 'none'; + ebi('tree').style.width = ebi('tree_footer').style.width = '0'; + ebi('tree_footer').style.display = 'none'; + ebi('widget').style.marginLeft = '0'; ebi('wrap').style.marginLeft = ''; + clmod(ebi('pathBar'), 'shifted', true); window.removeEventListener('resize', onresize); window.removeEventListener('scroll', onscroll); aligngriditems(); + + // wait for animation to finish (.15s), then set display to none if width still 0 + setTimeout(function () { + onresize(); + }, 170); }; @@ -7112,5 +8133,5 @@ } - if (!r.parpane || !r.pdir.length || y >= r.pdir.slice(-1)[0][0] || y <= r.pdir[0][0]) { + if (!r.parpane || !r.pdir.length) { clmod(parp, 'off', 1); r.pdirh = null; @@ -7136,46 +8157,19 @@ els[a].onclick = bad_proxy; } - y = ebi('treeh').offsetHeight; - if (!fixedpos) - y += tree.offsetTop - yscroll(); - - y = (y - 3) + 'px'; + y = ebi('treesuperh').offsetHeight + 'px'; if (parp.style.top != y) parp.style.top = y; } - - if (wraptop === null) - return; - - prev_atop = atop; - prev_winh = winh; - - if (fixedpos && atop >= 0) { - tree.style.position = 'absolute'; - tree.style.bottom = ''; - fixedpos = false; - } - else if (!fixedpos && atop < 0) { - tree.style.position = 'fixed'; - tree.style.height = 'auto'; - fixedpos = true; - } - - if (fixedpos) { - tree.style.top = Math.max(0, parseInt(atop)) + 'px'; - } - else { - var top = Math.max(0, wraptop), - treeh = winh - atop; - - tree.style.top = top + 'px'; - tree.style.height = treeh < 10 ? '' : Math.floor(treeh) + 'px'; - } } timer.add(onscroll2, true); function onresize(e) { - if (!entreed || r.hidden) + if (!entreed || r.hidden){ + if(ebi('tree').style.width == '0px') + ebi('tree').style.display = 'none'; + ebi('reszbar').style.left = '2px'; + setcvar('--nav-sz', 0); return; + } var q = '#tree', @@ -7188,13 +8182,19 @@ break; } - nq = Math.max(nq, get_evpath().split('/').length - 2); + if(r.dyn) + nq = Math.max(nq, get_evpath().split('/').length - 2); var iw = (treesz + Math.max(0, nq)), w = iw + 'em', - w2 = (iw + 2) + 'em'; + w2 = (iw + 1) + 'em'; setcvar('--nav-sz', w); ebi('tree').style.width = w; - ebi('wrap').style.marginLeft = w2; - onscroll(); + ebi('tree_footer').style.width = (iw - 2) + 'em'; + ebi('tree_footer').style.display = 'block'; + ebi('wrap').style.marginLeft = w; + ebi('reszbar').style.left = w; + ebi('widget').style.marginLeft = (iw /1.4) + 'em'; + onwidgetresize(); + setTimeout(onscroll, 1); } @@ -7318,4 +8318,8 @@ } } + + var dirs = QSA("#treeul li a:nth-child(2)"); + for (var i = 0; i < dirs.length; i++) + drag.mktarget(dirs[i]); }; @@ -7352,4 +8356,5 @@ for (var a = 0, aa = links.length; a < aa; a++) { links[a].setAttribute('dst', links[a].nextSibling.getAttribute('href')); + clmod(links[a], 'ld', false); links[a].onclick = treegrow; } @@ -7382,14 +8387,19 @@ var p = this.offsetParent, pp = p.offsetParent, - ppy = pp.offsetTop, - y = this.offsetTop + p.offsetTop + ppy - p.scrollTop - pp.scrollTop - (ppy ? document.documentElement.scrollTop : 0); + ppy = pp ? pp.offsetTop : 0, + y = this.offsetTop + p.offsetTop + ppy - p.scrollTop - (pp ? pp.scrollTop : 0) - (ppy ? document.documentElement.scrollTop : 0); this.style.top = y + 'px'; this.style.position = 'fixed'; + this.style.width = 'auto'; + this.style.minWidth = ebi('tree').offsetWidth + p.offsetLeft - 5 + 'px'; mentered = this; } function mleave(e) { + this.style.top = ''; this.style.position = ''; + this.style.minWidth = ''; + this.style.width = ''; mentered = null; } @@ -7424,5 +8434,5 @@ r.dir_cb = tree_scrollto; thegrid.setvis(true); - clmod(this, 'ld', 1); + clmod(this.previousSibling, 'ld', 1); } @@ -7535,8 +8545,14 @@ clmod(ebi('griden'), 'on', thegrid.en = dgrid); + var accent = parseColor(sread('paccent')) + if(!accent && (res.tcolor || tcolor)) + setColor(parseColor(res.tcolor ? res.tcolor : tcolor), true); + srvinf = res.srvinf; - if (rtt !== null) + volspace = res.volspace; + if (rtt !== null){ srvinf += (srvinf ? ' // rtt: ' : 'rtt: ') + rtt; - + ebi('rtt_latency').innerHTML = 'latency: ' + rtt + 'ms'; + } var o = ebi('srv_info2'); if (o) @@ -7546,4 +8562,5 @@ while (qsr('head>link[rel~="icon"]')) { } document.head.insertAdjacentHTML('beforeend', res.ufavico); + ebi('favico_onpage').innerHTML = res.ufavico; } @@ -7583,5 +8600,5 @@ lg1 = L.f_empty; - sandbox(ebi('pro'), sb_lg, sba_lg,'', lg0); + sandbox(ebi('pro'), sb_lg, sba_lg, '', lg0); if (dirchg) sandbox(ebi('epi'), sb_lg, sba_lg, '', lg1); @@ -7611,6 +8628,18 @@ } + if (drag.is_dragging) { + fileman.paste(true); + drag.is_dragging = false; + } + drag.initfiles(); + if (can_shr && in_shr && QS('#op_unpost.act')) goto('unpost'); + + setBg(res.bg_img); + + if(!override_thm && res.dtheme){ + settheme.init(res.dtheme) + } } @@ -7628,4 +8657,7 @@ r.gentab = function (top, res) { showfile.untail(); + for (var a = 0; a < res.dirs.length; a++) + res.dirs[a].cls = 'dir'; + var nodes = res.dirs.concat(res.files), html = mk_files_header(res.taglist), @@ -7700,7 +8732,8 @@ '" hl="' + id + '" name="' + hname + '">-txt-'; - var cl = /\.PARTIAL$/.exec(fname) ? ' class="fade"' : '', - ln = ['' + tn.lead + '' + hname + + var cl = (/\.PARTIAL$/.exec(fname) ? 'fade ' : '') + (img_re.exec(fname) ? 'img ' : '') + (tn.cls || ''), + ln = ['
        ' + tn.lead + '' + + get_thumb(tn.href, id, (/\bdir\b/i).test(cl), false) + + '' + hname + '' + filesizefun(tn.sz)]; @@ -7726,4 +8759,9 @@ html = html.join('\n'); set_files_html(html); + + var ths = QSA('#files .imgcontainer>img'); + for (var a = 0, aa = ths.length; a < aa; a++) + ths[a].onload = th_onload; + if (r.dlni) { var o = QSA('#files a[id]'); @@ -7805,4 +8843,9 @@ vbar.onresize(); showfile.addlinks(); + drag.initfiles(); + + var media = scan_hash(location.hash); + if(media && media[0] == 'g') + clmod(ebi('wrap'), 'waiting', true); setTimeout(eval_hash, 1); }; @@ -7938,14 +8981,22 @@ ev(e); treesz += parseInt(this.getAttribute("step")); + r.settreesize(treesz); + } + + r.settreesize = function(width){ + treesz = width; if (!isNum(treesz)) treesz = 16; - treesz = clamp(treesz, 2, 120); + treesz = clamp(treesz, 6, 120); swrite('treesz', treesz); onresize(); } - ebi('entree').onclick = r.entree; - ebi('detree').onclick = r.detree; + var svg_hamburger = svg_box + '' + ebi('treeToggleBtn').innerHTML = svg_hamburger; + ebi('treeToggleBtn').setAttribute('tt', L.tt_entree); + + ebi('treeToggleBtn').onclick = r.toggleTree; ebi('visdir').onclick = tree_scrollto; ebi('twig').onclick = scaletree; @@ -7958,4 +9009,6 @@ cs = 'na'; r.detree(null, 1); + ebi('treeToggleBtn').style.display = ebi('reszbar').style.display = 'none'; + clmod(ebi('pathBar'), 'shifted', false); } @@ -7975,4 +9028,14 @@ mdk = /[?&](k=[^&#]+)/.exec('' + url); + if(location.hash == '' && QS('#bbox-overlay.visible')){ + baguetteBox.hide(); + baguetteBox.resetUserAction(); + return; + } + if(baguetteBox.getUserAction()){ + baguetteBox.resetUserAction(); + return; + } + if (mdoc && hbase == cbase) return showfile.show(hbase + showfile.sname(url.search), true); @@ -8014,5 +9077,5 @@ clearTimeout(r.t); r.t = setTimeout(r.reset, 300); - ebi('wfp').style.opacity = 0.1; + //ebi('wfp').style.opacity = 0.1; } }; @@ -8044,5 +9107,6 @@ if (konmai < 0) { acct = 'Ted Faro'; - srvinf = 'FAS Nexus // 57.3 EiB free of 127 EiB'; + ebi('srv_name').innerHTML = 'FAS Nexus'; + volspace = ['57.3 EiB', '127 EiB', 4512]; res.shr_who = 'auth'; perms = res.perms = chk; @@ -8084,22 +9148,44 @@ dst = idp_login.replace(/\{dst\}/g, get_evpath()); - ebi('acc_info').innerHTML = '' + srvinf + - '' + (acct != '*' ? - '
        ' : - '' + L.login + ''); + function goHome(){ + window.location.href = dst; + } + + if(acct != '*'){ + ebi('acc_name').innerHTML = acct; + ebi('acc_pfp').innerHTML = acct.substring(0, 1).toUpperCase(); + ebi('accessType').innerHTML = ''; + ebi('blogout').value = L.logout; + } + else{ + ebi('blogout').style.display = 'none'; + ebi('acc_name').innerHTML = L.login; + ebi('acc_pfp').innerHTML = fun_tgl && EMOJI ? '👤' : 'acc'; + } + clmod(ebi('acc_pfp'), 'placeholder', acct == '*'); - var o = QSA('#ops>a[data-perm]'); + var o = QSA('#ops>a'); for (var a = 0; a < o.length; a++) { + if(!o[a].hasAttribute('data-perm')){ + clmod(o[a], 'disabled', false); + continue; + } var display = ''; + var enabled = true; var needed = o[a].getAttribute('data-perm').split(' '); for (var b = 0; b < needed.length; b++) { if (!has(perms, needed[b])) { - display = 'none'; + enabled = false; + if(needed[b] != 'write') + display = 'none'; } } + clmod(o[a], 'disabled', !enabled); o[a].style.display = display; } + clmod(ebi('up_quick'), 'disabled', !has(perms, 'write')); + ebi('bup_tgl').style.display = has(perms, 'write') ? '' : 'none'; - var o = QSA('#ops>a[data-dep], #u2conf td[data-dep]'); + var o = QSA('#ops>a[data-dep], #u2conf div[data-dep]'); for (var a = 0; a < o.length; a++) o[a].style.display = ( @@ -8118,5 +9204,5 @@ var have_write = has(perms, "write"), de = document.documentElement, - tds = QSA('#u2conf td'); + tds = QSA('#u2conf div'); shr_who = res.shr_who || shr_who; @@ -8139,6 +9225,7 @@ u2ts = res.u2ts; - if (up2k) - up2k.set_fsearch(); + + if(up2k) + wait_set_fsearch(); if (res.cfg) @@ -8149,6 +9236,27 @@ widget.setvis(); thegrid.setvis(); - if (!have_read && have_write) + + var up_only = !have_read && have_write; + if (up_only){ goto('up2k'); + clmod(document.documentElement, 'noscroll', false); + } + if(up_only && !up2k_lgcy){ + up2k_lgcy = !up2k_lgcy; + ebi('reloc_up').innerHTML = up2k_lgcy ? '' : ''; + clmod(ebi('up2k'), 'unmodal', up2k_lgcy); + } + if(EMOJI) + clmod(ebi('opa_mkd'), 'vis', up_only); +} + +function wait_set_fsearch(){ + var t = setInterval(function () { + if (!up2k || !up2k.set_fsearch) + return; + + clearInterval(t); + up2k.set_fsearch(); + }, 50); } @@ -8156,5 +9264,5 @@ function tr2id(tr) { try { - return tr.cells[1].querySelector('a[id]').getAttribute('id'); + return tr.querySelector('.flink').getAttribute('id'); } catch (ex) { @@ -8188,5 +9296,6 @@ var html = [ '
        c!thFile NameSizeTExtDate
        -new-