mirror of
https://github.com/9001/copyparty.git
synced 2025-10-24 00:24:04 +00:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5712d9f25 | ||
|
|
161d57bdda | ||
|
|
bae0d440bf | ||
|
|
fff052dde1 | ||
|
|
73b06eaa02 | ||
|
|
08a8ebed17 | ||
|
|
74d07426b3 | ||
|
|
69a2bba99a | ||
|
|
4d685d78ee | ||
|
|
5845ec3f49 | ||
|
|
13373426fe | ||
|
|
8e55551a06 | ||
|
|
12a3f0ac31 | ||
|
|
18e33edc88 | ||
|
|
c72c5ad4ee | ||
|
|
0fbc81ab2f | ||
|
|
af0a34cf82 | ||
|
|
b4590c5398 | ||
|
|
f787a66230 | ||
|
|
b21a99fd62 | ||
|
|
eb16306cde | ||
|
|
7bc23687e3 | ||
|
|
e1eaa057f2 | ||
|
|
97c264ca3e | ||
|
|
cf848ab1f7 | ||
|
|
cf83f9b0fd | ||
|
|
d98e361083 | ||
|
|
ce7f5309c7 | ||
|
|
75c485ced7 | ||
|
|
9c6e2ec012 | ||
|
|
1a02948a61 | ||
|
|
8b05ba4ba1 | ||
|
|
21e2874cb7 | ||
|
|
360ed5c46c | ||
|
|
5099bc365d | ||
|
|
12986da147 | ||
|
|
23e72797bc | ||
|
|
ac7b6f8f55 | ||
|
|
981b9ff11e | ||
|
|
4186906f4c | ||
|
|
0850d24e0c | ||
|
|
7ab8334c96 | ||
|
|
a4d7329ab7 | ||
|
|
3f4eae6bce | ||
|
|
518cf4be57 | ||
|
|
71096182be | ||
|
|
6452e927ea | ||
|
|
bc70cfa6f0 | ||
|
|
2b6e5ebd2d | ||
|
|
c761bd799a | ||
|
|
2f7c2fdee4 | ||
|
|
70a76ec343 | ||
|
|
7c3f64abf2 | ||
|
|
f5f38f195c | ||
|
|
7e84f4f015 | ||
|
|
4802f8cf07 | ||
|
|
cc05e67d8f | ||
|
|
2b6b174517 | ||
|
|
a1d05e6e12 | ||
|
|
f95ceb6a9b | ||
|
|
8f91b0726d | ||
|
|
97807f4383 | ||
|
|
5f42237f2c | ||
|
|
68289cfa54 | ||
|
|
42ea30270f | ||
|
|
ebbbbf3d82 | ||
|
|
27516e2d16 | ||
|
|
84bb6f915e | ||
|
|
46752f758a |
215
README.md
215
README.md
@@ -6,7 +6,7 @@
|
||||
|
||||
## summary
|
||||
|
||||
turn your phone or raspi into a portable file server with resumable uploads/downloads using IE6 or any other browser
|
||||
turn your phone or raspi into a portable file server with resumable uploads/downloads using *any* web browser
|
||||
|
||||
* server runs on anything with `py2.7` or `py3.3+`
|
||||
* browse/upload with IE4 / netscape4.0 on win3.11 (heh)
|
||||
@@ -19,49 +19,53 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
||||
## readme toc
|
||||
|
||||
* top
|
||||
* [quickstart](#quickstart)
|
||||
* [on debian](#on-debian)
|
||||
* [notes](#notes)
|
||||
* [status](#status)
|
||||
* [testimonials](#testimonials)
|
||||
* [quickstart](#quickstart) - download [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) and you're all set!
|
||||
* [on debian](#on-debian) - recommended additional steps on debian
|
||||
* [notes](#notes) - general notes
|
||||
* [status](#status) - summary: all planned features work! now please enjoy the bloatening
|
||||
* [testimonials](#testimonials) - small collection of user feedback
|
||||
* [bugs](#bugs)
|
||||
* [general bugs](#general-bugs)
|
||||
* [not my bugs](#not-my-bugs)
|
||||
* [the browser](#the-browser)
|
||||
* [tabs](#tabs)
|
||||
* [hotkeys](#hotkeys)
|
||||
* [navpane](#navpane)
|
||||
* [thumbnails](#thumbnails)
|
||||
* [zip downloads](#zip-downloads)
|
||||
* [uploading](#uploading)
|
||||
* [file-search](#file-search)
|
||||
* [file manager](#file-manager)
|
||||
* [batch rename](#batch-rename)
|
||||
* [markdown viewer](#markdown-viewer)
|
||||
* [accounts and volumes](#accounts-and-volumes) - per-folder, per-user permissions
|
||||
* [the browser](#the-browser) - accessing a copyparty server using a web-browser
|
||||
* [tabs](#tabs) - the main tabs in the ui
|
||||
* [hotkeys](#hotkeys) - the browser has the following hotkeys (always qwerty)
|
||||
* [navpane](#navpane) - switching between breadcrumbs or navpane
|
||||
* [thumbnails](#thumbnails) - press `g` to toggle image/video thumbnails instead of the file listing
|
||||
* [zip downloads](#zip-downloads) - download folders (or file selections) as `zip` or `tar` files
|
||||
* [uploading](#uploading) - web-browsers can upload using `bup` and `up2k`
|
||||
* [file-search](#file-search) - drop files/folders into up2k to see if they exist on the server
|
||||
* [unpost](#unpost) - undo/delete accidental uploads
|
||||
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
||||
* [batch rename](#batch-rename) - select some files and press F2 to bring up the rename UI
|
||||
* [markdown viewer](#markdown-viewer) - and there are *two* editors
|
||||
* [other tricks](#other-tricks)
|
||||
* [searching](#searching)
|
||||
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
||||
* [server config](#server-config)
|
||||
* [upload rules](#upload-rules)
|
||||
* [database location](#database-location)upload rules
|
||||
* [metadata from audio files](#metadata-from-audio-files)
|
||||
* [file parser plugins](#file-parser-plugins)
|
||||
* [file indexing](#file-indexing)
|
||||
* [upload rules](#upload-rules) - set upload rules using volume flags, some examples
|
||||
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
|
||||
* [database location](#database-location) - can be stored in-volume (default) or elsewhere
|
||||
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
||||
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags
|
||||
* [complete examples](#complete-examples)
|
||||
* [browser support](#browser-support)
|
||||
* [client examples](#client-examples)
|
||||
* [up2k](#up2k)
|
||||
* [performance](#performance)
|
||||
* [dependencies](#dependencies)
|
||||
* [optional dependencies](#optional-dependencies)
|
||||
* [browser support](#browser-support) - TLDR: yes
|
||||
* [client examples](#client-examples) - interact with copyparty using non-browser clients
|
||||
* [up2k](#up2k) - quick outline of the up2k protocol, see [uploading](#uploading) for the web-client
|
||||
* [performance](#performance) - defaults are good for most cases
|
||||
* [dependencies](#dependencies) - mandatory deps
|
||||
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
|
||||
* [install recommended deps](#install-recommended-deps)
|
||||
* [optional gpl stuff](#optional-gpl-stuff)
|
||||
* [sfx](#sfx)
|
||||
* [sfx repack](#sfx-repack)
|
||||
* [sfx](#sfx) - there are two self-contained "binaries"
|
||||
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
|
||||
* [install on android](#install-on-android)
|
||||
* [building](#building)
|
||||
* [dev env setup](#dev-env-setup)
|
||||
* [just the sfx](#just-the-sfx)
|
||||
* [complete release](#complete-release)
|
||||
* [todo](#todo)
|
||||
* [todo](#todo) - roughly sorted by priority
|
||||
* [discarded ideas](#discarded-ideas)
|
||||
|
||||
|
||||
@@ -86,7 +90,9 @@ you may also want these, especially on servers:
|
||||
|
||||
### on debian
|
||||
|
||||
recommended steps to enable audio metadata and thumbnails (from images and videos):
|
||||
recommended additional steps on debian
|
||||
|
||||
enable audio metadata and thumbnails (from images and videos):
|
||||
|
||||
* as root, run the following:
|
||||
`apt install python3 python3-pip python3-dev ffmpeg`
|
||||
@@ -99,7 +105,7 @@ recommended steps to enable audio metadata and thumbnails (from images and video
|
||||
|
||||
## notes
|
||||
|
||||
general:
|
||||
general notes:
|
||||
* paper-printing is affected by dark/light-mode! use lightmode for color, darkmode for grayscale
|
||||
* because no browsers currently implement the media-query to do this properly orz
|
||||
|
||||
@@ -124,7 +130,7 @@ summary: all planned features work! now please enjoy the bloatening
|
||||
* ☑ basic: plain multipart, ie6 support
|
||||
* ☑ [up2k](#uploading): js, resumable, multithreaded
|
||||
* ☑ stash: simple PUT filedropper
|
||||
* ☑ unpost: undo/delete accidental uploads
|
||||
* ☑ [unpost](#unpost): undo/delete accidental uploads
|
||||
* ☑ symlink/discard existing files (content-matching)
|
||||
* download
|
||||
* ☑ single files in browser
|
||||
@@ -184,6 +190,7 @@ small collection of user feedback
|
||||
|
||||
# accounts and volumes
|
||||
|
||||
per-folder, per-user permissions
|
||||
* `-a usr:pwd` adds account `usr` with password `pwd`
|
||||
* `-v .::r` adds current-folder `.` as the webroot, `r`eadable by anyone
|
||||
* the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
|
||||
@@ -208,14 +215,17 @@ example:
|
||||
|
||||
# the browser
|
||||
|
||||

|
||||
accessing a copyparty server using a web-browser
|
||||
|
||||

|
||||
|
||||
|
||||
## tabs
|
||||
|
||||
* `[🔎]` search by size, date, path/name, mp3-tags ... see [searching](#searching)
|
||||
* `[🧯]` unpost: undo/delete accidental uploads
|
||||
* `[🚀]` and `[🎈]` are the uploaders, see [uploading](#uploading)
|
||||
the main tabs in the ui
|
||||
* `[🔎]` [search](#searching) by size, date, path/name, mp3-tags ...
|
||||
* `[🧯]` [unpost](#unpost): undo/delete accidental uploads
|
||||
* `[🚀]` and `[🎈]` are the [uploaders](#uploading)
|
||||
* `[📂]` mkdir: create directories
|
||||
* `[📝]` new-md: create a new markdown document
|
||||
* `[📟]` send-msg: either to server-log or into textfiles if `--urlform save`
|
||||
@@ -225,7 +235,7 @@ example:
|
||||
|
||||
## hotkeys
|
||||
|
||||
the browser has the following hotkeys (assumes qwerty, ignores actual layout)
|
||||
the browser has the following hotkeys (always qwerty)
|
||||
* `B` toggle breadcrumbs / navpane
|
||||
* `I/K` prev/next folder
|
||||
* `M` parent folder (or unexpand current)
|
||||
@@ -274,14 +284,18 @@ the browser has the following hotkeys (assumes qwerty, ignores actual layout)
|
||||
|
||||
## navpane
|
||||
|
||||
by default there's a breadcrumbs path; you can replace this with a navpane (tree-browser sidebar thing) by clicking the `🌲` or pressing the `B` hotkey
|
||||
switching between breadcrumbs or navpane
|
||||
|
||||
click the `🌲` or pressing the `B` hotkey to toggle between breadcrumbs path (default), or a navpane (tree-browser sidebar thing)
|
||||
|
||||
click `[-]` and `[+]` (or hotkeys `A`/`D`) to adjust the size, and the `[a]` toggles if the tree should widen dynamically as you go deeper or stay fixed-size
|
||||
|
||||
|
||||
## thumbnails
|
||||
|
||||

|
||||
press `g` to toggle image/video thumbnails instead of the file listing
|
||||
|
||||

|
||||
|
||||
it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are
|
||||
|
||||
@@ -292,7 +306,9 @@ in the grid/thumbnail view, if the audio player panel is open, songs will start
|
||||
|
||||
## zip downloads
|
||||
|
||||
the `zip` link next to folders can produce various types of zip/tar files using these alternatives in the browser settings tab:
|
||||
download folders (or file selections) as `zip` or `tar` files
|
||||
|
||||
select which type of archive you want in the browser settings tab:
|
||||
|
||||
| name | url-suffix | description |
|
||||
|--|--|--|
|
||||
@@ -309,15 +325,16 @@ the `zip` link next to folders can produce various types of zip/tar files using
|
||||
|
||||
you can also zip a selection of files or folders by clicking them in the browser, that brings up a selection editor and zip button in the bottom right
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## uploading
|
||||
|
||||
two upload methods are available in the html client:
|
||||
web-browsers can upload using `bup` and `up2k`:
|
||||
* `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0
|
||||
* `[🚀] up2k`, the fancy one
|
||||
|
||||
you can undo/delete uploads using `[🧯] unpost` if the server is running with `-e2d`
|
||||
you can undo/delete uploads using `[🧯]` [unpost](#unpost)
|
||||
|
||||
up2k has several advantages:
|
||||
* you can drop folders into the browser (files are added recursively)
|
||||
@@ -331,7 +348,7 @@ up2k has several advantages:
|
||||
|
||||
see [up2k](#up2k) for details on how it works
|
||||
|
||||

|
||||

|
||||
|
||||
**protip:** you can avoid scaring away users with [docs/minimal-up2k.html](docs/minimal-up2k.html) which makes it look [much simpler](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
|
||||
|
||||
@@ -339,7 +356,7 @@ the up2k UI is the epitome of polished inutitive experiences:
|
||||
* "parallel uploads" specifies how many chunks to upload at the same time
|
||||
* `[🏃]` analysis of other files should continue while one is uploading
|
||||
* `[💭]` ask for confirmation before files are added to the list
|
||||
* `[💤]` sync uploading between other copyparty tabs so only one is active
|
||||
* `[💤]` sync uploading between other copyparty browser-tabs so only one is active
|
||||
* `[🔎]` switch between upload and file-search mode
|
||||
|
||||
and then theres the tabs below it,
|
||||
@@ -350,11 +367,14 @@ and then theres the tabs below it,
|
||||
* plus up to 3 entries each from `[done]` and `[que]` for context
|
||||
* `[que]` is all the files that are still queued
|
||||
|
||||
|
||||
### file-search
|
||||
|
||||

|
||||
drop files/folders into up2k to see if they exist on the server
|
||||
|
||||
in the `[🚀 up2k]` tab, after toggling the `[🔎]` switch green, any files/folders you drop onto the dropzone will be hashed on the client-side. Each hash is sent to the server which checks if that file exists somewhere already
|
||||

|
||||
|
||||
in the `[🚀 up2k]` tab, after toggling the `[🔎]` switch green, any files/folders you drop onto the dropzone will be hashed on the client-side. Each hash is sent to the server which checks if that file exists somewhere
|
||||
|
||||
files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]`
|
||||
* the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much
|
||||
@@ -366,19 +386,28 @@ note that since up2k has to read the file twice, `[🎈 bup]` can be up to 2x fa
|
||||
up2k has saved a few uploads from becoming corrupted in-transfer already; caught an android phone on wifi redhanded in wireshark with a bitflip, however bup with https would *probably* have noticed as well (thanks to tls also functioning as an integrity check)
|
||||
|
||||
|
||||
### unpost
|
||||
|
||||
undo/delete accidental uploads
|
||||
|
||||

|
||||
|
||||
you can unpost even if you don't have regular move/delete access, however only for files uploaded within the past `--unpost` seconds (default 12 hours) and the server must be running with `-e2d`
|
||||
|
||||
|
||||
## file manager
|
||||
|
||||
if you have the required permissions, you can cut/paste, rename, and delete files/folders
|
||||
cut/paste, rename, and delete files/folders (if you have permission)
|
||||
|
||||
you can move files across browser tabs (cut in one tab, paste in another)
|
||||
|
||||
|
||||
## batch rename
|
||||
|
||||

|
||||
|
||||
select some files and press F2 to bring up the rename UI
|
||||
|
||||

|
||||
|
||||
quick explanation of the buttons,
|
||||
* `[✅ apply rename]` confirms and begins renaming
|
||||
* `[❌ cancel]` aborts and closes the rename window
|
||||
@@ -422,6 +451,8 @@ the metadata keys you can use in the format field are the ones in the file-brows
|
||||
|
||||
## markdown viewer
|
||||
|
||||
and there are *two* editors
|
||||
|
||||

|
||||
|
||||
* the document preview has a max-width which is the same as an A4 paper when printed
|
||||
@@ -436,7 +467,9 @@ the metadata keys you can use in the format field are the ones in the file-brows
|
||||
|
||||
## searching
|
||||
|
||||

|
||||
search by size, date, path/name, mp3-tags, ...
|
||||
|
||||

|
||||
|
||||
when started with `-e2dsa` copyparty will scan/index all your files. This avoids duplicates on upload, and also makes the volumes searchable through the web-ui:
|
||||
* make search queries by `size`/`date`/`directory-path`/`filename`, or...
|
||||
@@ -451,6 +484,8 @@ add the argument `-e2ts` to also scan/index tags from music files, which brings
|
||||
|
||||
# server config
|
||||
|
||||
## file indexing
|
||||
|
||||
file indexing relies on two databases, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`). Configuration can be done through arguments, volume flags, or a mix of both.
|
||||
|
||||
through arguments:
|
||||
@@ -480,7 +515,7 @@ if you set `--no-hash`, you can enable hashing for specific volumes using flag `
|
||||
|
||||
## upload rules
|
||||
|
||||
you can set upload rules using volume flags, some examples:
|
||||
set upload rules using volume flags, some examples:
|
||||
|
||||
* `:c,sz=1k-3m` sets allowed filesize between 1 KiB and 3 MiB inclusive (suffixes: b, k, m, g)
|
||||
* `:c,nosub` disallow uploading into subdirectories; goes well with `rotn` and `rotf`:
|
||||
@@ -499,7 +534,9 @@ you can also set transaction limits which apply per-IP and per-volume, but these
|
||||
|
||||
## compress uploads
|
||||
|
||||
files can be autocompressed on upload, either on user-request (if config allows) or forced by server-config
|
||||
files can be autocompressed on upload
|
||||
|
||||
compression is either on user-request (if config allows) or forced by server-config
|
||||
|
||||
* volume flag `gz` allows gz compression
|
||||
* volume flag `xz` allows lzma compression
|
||||
@@ -520,6 +557,8 @@ some examples,
|
||||
|
||||
## database location
|
||||
|
||||
can be stored in-volume (default) or elsewhere
|
||||
|
||||
copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
|
||||
|
||||
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
|
||||
@@ -533,6 +572,8 @@ note:
|
||||
|
||||
## metadata from audio files
|
||||
|
||||
set `-e2t` to index tags on upload
|
||||
|
||||
`-mte` decides which tags to index and display in the browser (and also the display order), this can be changed per-volume:
|
||||
* `-v ~/music::r:c,mte=title,artist` indexes and displays *title* followed by *artist*
|
||||
|
||||
@@ -556,6 +597,8 @@ see the beautiful mess of a dictionary in [mtag.py](https://github.com/9001/copy
|
||||
|
||||
## file parser plugins
|
||||
|
||||
provide custom parsers to index additional tags
|
||||
|
||||
copyparty can invoke external programs to collect additional metadata for files using `mtp` (either as argument or volume flag), there is a default timeout of 30sec
|
||||
|
||||
* `-mtp .bpm=~/bin/audio-bpm.py` will execute `~/bin/audio-bpm.py` with the audio file as argument 1 to provide the `.bpm` tag, if that does not exist in the audio metadata
|
||||
@@ -576,32 +619,38 @@ copyparty can invoke external programs to collect additional metadata for files
|
||||
|
||||
# browser support
|
||||
|
||||
TLDR: yes
|
||||
|
||||

|
||||
|
||||
`ie` = internet-explorer, `ff` = firefox, `c` = chrome, `iOS` = iPhone/iPad, `Andr` = Android
|
||||
|
||||
| feature | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
||||
| --------------- | --- | --- | ---- | ---- | ----- | ---- | --- | ---- |
|
||||
| browse files | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| basic uploader | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| make directory | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| send message | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| set sort order | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| zip selection | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| navpane | - | - | `*1` | yep | yep | yep | yep | yep |
|
||||
| up2k | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| markdown editor | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| markdown viewer | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| play mp3/m4a | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| play ogg/opus | - | - | - | - | yep | yep | `*2` | yep |
|
||||
| thumbnail view | - | - | - | - | yep | yep | yep | yep |
|
||||
| image viewer | - | - | - | - | yep | yep | yep | yep |
|
||||
| **= feature =** | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
||||
| feature | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
||||
| --------------- | --- | ---- | ---- | ---- | ----- | ---- | --- | ---- |
|
||||
| browse files | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| thumbnail view | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| basic uploader | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| up2k | - | - | `*1` | `*1` | yep | yep | yep | yep |
|
||||
| make directory | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| send message | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| set sort order | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| zip selection | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| file rename | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| file cut/paste | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| navpane | - | `*2` | yep | yep | yep | yep | yep | yep |
|
||||
| image viewer | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| video player | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| markdown editor | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| markdown viewer | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| play mp3/m4a | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| play ogg/opus | - | - | - | - | yep | yep | `*3` | yep |
|
||||
| **= feature =** | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
||||
|
||||
* internet explorer 6 to 8 behave the same
|
||||
* firefox 52 and chrome 49 are the last winxp versions
|
||||
* `*1` only public folders (login session is dropped) and no history / back-button
|
||||
* `*2` using a wasm decoder which can sometimes get stuck and consumes a bit more power
|
||||
* `*1` yes, but extremely slow (ie10: 1 MiB/s, ie11: 270 KiB/s)
|
||||
* `*2` causes a full-page refresh on each navigation
|
||||
* `*3` using a wasm decoder which can sometimes get stuck and consumes a bit more power
|
||||
|
||||
quick summary of more eccentric web-browsers trying to view a directory index:
|
||||
|
||||
@@ -613,12 +662,15 @@ quick summary of more eccentric web-browsers trying to view a directory index:
|
||||
| **lynx** (2.8.9/macports) | can browse, login, upload/mkdir/msg |
|
||||
| **w3m** (0.5.3/macports) | can browse, login, upload at 100kB/s, mkdir/msg |
|
||||
| **netsurf** (3.10/arch) | is basically ie6 with much better css (javascript has almost no effect) |
|
||||
| **opera** (11.60/winxp) | OK: thumbnails, image-viewer, zip-selection, rename/cut/paste. NG: up2k, navpane, markdown, audio |
|
||||
| **ie4** and **netscape** 4.0 | can browse (text is yellow on white), upload with `?b=u` |
|
||||
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
|
||||
|
||||
|
||||
# client examples
|
||||
|
||||
interact with copyparty using non-browser clients
|
||||
|
||||
* javascript: dump some state into a file (two separate examples)
|
||||
* `await fetch('https://127.0.0.1:3923/', {method:"PUT", body: JSON.stringify(foo)});`
|
||||
* `var xhr = new XMLHttpRequest(); xhr.open('POST', 'https://127.0.0.1:3923/msgs?raw'); xhr.send('foo');`
|
||||
@@ -661,7 +713,9 @@ quick outline of the up2k protocol, see [uploading](#uploading) for the web-clie
|
||||
|
||||
# performance
|
||||
|
||||
defaults are good for most cases, don't mind the `cannot efficiently use multiple CPU cores` message, it's very unlikely to be a problem
|
||||
defaults are good for most cases
|
||||
|
||||
you can ignore the `cannot efficiently use multiple CPU cores` message, it's very unlikely to be a problem
|
||||
|
||||
below are some tweaks roughly ordered by usefulness:
|
||||
|
||||
@@ -678,11 +732,14 @@ below are some tweaks roughly ordered by usefulness:
|
||||
|
||||
# dependencies
|
||||
|
||||
mandatory deps:
|
||||
* `jinja2` (is built into the SFX)
|
||||
|
||||
|
||||
## optional dependencies
|
||||
|
||||
install these to enable bonus features
|
||||
|
||||
enable music tags:
|
||||
* either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk)
|
||||
* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
|
||||
@@ -715,7 +772,7 @@ these are standalone programs and will never be imported / evaluated by copypart
|
||||
|
||||
# sfx
|
||||
|
||||
currently there are two self-contained "binaries":
|
||||
there are two self-contained "binaries":
|
||||
* [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) -- pure python, works everywhere, **recommended**
|
||||
* [copyparty-sfx.sh](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.sh) -- smaller, but only for linux and macos, kinda deprecated
|
||||
|
||||
@@ -726,6 +783,8 @@ pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `
|
||||
|
||||
## sfx repack
|
||||
|
||||
reduce the size of an sfx by removing features
|
||||
|
||||
if you don't need all the features, you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except if you're on windows then you need msys2 or WSL)
|
||||
* `525k` size of original sfx.py as of v0.11.30
|
||||
* `315k` after `./scripts/make-sfx.sh re no-ogv`
|
||||
@@ -769,7 +828,7 @@ pip install black bandit pylint flake8 # vscode tooling
|
||||
|
||||
## just the sfx
|
||||
|
||||
unless you need to modify something in the web-dependencies, it's faster to grab those from a previous release:
|
||||
grab the web-dependencies from a previous sfx (unless you need to modify something in those):
|
||||
|
||||
```sh
|
||||
rm -rf copyparty/web/deps
|
||||
@@ -789,7 +848,7 @@ then build the sfx using any of the following examples:
|
||||
|
||||
## complete release
|
||||
|
||||
also builds the sfx so disregard the sfx section above
|
||||
also builds the sfx so skip the sfx section above
|
||||
|
||||
in the `scripts` folder:
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (0, 13, 3)
|
||||
VERSION = (0, 13, 9)
|
||||
CODENAME = "future-proof"
|
||||
BUILD_DT = (2021, 8, 14)
|
||||
BUILD_DT = (2021, 8, 29)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -956,10 +956,14 @@ class HttpCli(object):
|
||||
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
||||
self._assert_safe_rem(rem)
|
||||
|
||||
upload_vpath = self.vpath
|
||||
lim = vfs.get_dbv(rem)[0].lim
|
||||
fdir_base = os.path.join(vfs.realpath, rem)
|
||||
if lim:
|
||||
fdir_base, rem = lim.all(self.ip, rem, -1, fdir_base)
|
||||
upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/")
|
||||
if not nullwrite:
|
||||
bos.makedirs(fdir_base)
|
||||
|
||||
files = []
|
||||
errmsg = ""
|
||||
@@ -986,8 +990,6 @@ class HttpCli(object):
|
||||
if lim:
|
||||
lim.chk_bup(self.ip)
|
||||
lim.chk_nup(self.ip)
|
||||
if not nullwrite:
|
||||
bos.makedirs(fdir)
|
||||
|
||||
try:
|
||||
with ren_open(fname, "wb", 512 * 1024, **open_args) as f:
|
||||
@@ -1038,7 +1040,9 @@ class HttpCli(object):
|
||||
raise
|
||||
|
||||
except Pebkac as ex:
|
||||
errmsg = volsan(self.asrv.vfs.all_vols.values(), unicode(ex))
|
||||
errmsg = vol_san(
|
||||
self.asrv.vfs.all_vols.values(), unicode(ex).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
|
||||
td = max(0.1, time.time() - t0)
|
||||
sz_total = sum(x[0] for x in files)
|
||||
@@ -1058,7 +1062,7 @@ class HttpCli(object):
|
||||
errmsg = "ERROR: " + errmsg
|
||||
|
||||
for sz, sha512, ofn, lfn in files:
|
||||
vpath = (self.vpath + "/" if self.vpath else "") + lfn
|
||||
vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
|
||||
msg += 'sha512: {} // {} bytes // <a href="/{}">{}</a>\n'.format(
|
||||
sha512[:56], sz, quotep(vpath), html_escape(ofn, crlf=True)
|
||||
)
|
||||
@@ -1695,7 +1699,7 @@ class HttpCli(object):
|
||||
|
||||
q = "select sz, rd, fn, at from up where ip=? and at>?"
|
||||
for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
|
||||
vp = "/" + "/".join([rd, fn]).strip("/")
|
||||
vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
|
||||
if filt and filt not in vp:
|
||||
continue
|
||||
|
||||
@@ -1778,7 +1782,7 @@ class HttpCli(object):
|
||||
for fn in self.args.th_covers.split(","):
|
||||
fp = os.path.join(abspath, fn)
|
||||
if bos.path.exists(fp):
|
||||
vrem = "{}/{}".format(vrem.rstrip("/"), fn)
|
||||
vrem = "{}/{}".format(vrem.rstrip("/"), fn).strip("/")
|
||||
is_dir = False
|
||||
break
|
||||
|
||||
|
||||
@@ -1223,7 +1223,8 @@ class Up2k(object):
|
||||
|
||||
self._new_upload(job)
|
||||
|
||||
purl = "/{}/".format("{}/{}".format(job["vtop"], job["prel"]).strip("/"))
|
||||
purl = "{}/{}".format(job["vtop"], job["prel"]).strip("/")
|
||||
purl = "/{}/".format(purl) if purl else "/"
|
||||
|
||||
return {
|
||||
"name": job["name"],
|
||||
@@ -1427,7 +1428,11 @@ class Up2k(object):
|
||||
ptop = vn.realpath
|
||||
atop = vn.canonical(rem, False)
|
||||
adir, fn = os.path.split(atop)
|
||||
st = bos.lstat(atop)
|
||||
try:
|
||||
st = bos.lstat(atop)
|
||||
except:
|
||||
raise Pebkac(400, "file not found on disk (already deleted?)")
|
||||
|
||||
scandir = not self.args.no_scandir
|
||||
if stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode):
|
||||
dbv, vrem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
||||
|
||||
@@ -19,7 +19,7 @@ import subprocess as sp # nosec
|
||||
from datetime import datetime
|
||||
from collections import Counter
|
||||
|
||||
from .__init__ import PY2, WINDOWS, ANYWIN
|
||||
from .__init__ import PY2, WINDOWS, ANYWIN, VT100
|
||||
from .stolen import surrogateescape
|
||||
|
||||
FAKE_MP = False
|
||||
@@ -58,7 +58,7 @@ except:
|
||||
return struct.unpack(f.decode("ascii"), *a, **ka)
|
||||
|
||||
|
||||
ansi_re = re.compile("\033\\[[^m]*m")
|
||||
ansi_re = re.compile("\033\\[[^mK]*[mK]")
|
||||
|
||||
|
||||
surrogateescape.register_surrogateescape()
|
||||
@@ -207,17 +207,22 @@ class ProgressPrinter(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
msg = None
|
||||
fmt = " {}\033[K\r" if VT100 else " {} $\r"
|
||||
while not self.end:
|
||||
time.sleep(0.1)
|
||||
if msg == self.msg or self.end:
|
||||
continue
|
||||
|
||||
msg = self.msg
|
||||
uprint(" {}\033[K\r".format(msg))
|
||||
uprint(fmt.format(msg))
|
||||
if PY2:
|
||||
sys.stdout.flush()
|
||||
|
||||
print("\033[K", end="")
|
||||
if VT100:
|
||||
print("\033[K", end="")
|
||||
elif msg:
|
||||
print("------------------------")
|
||||
|
||||
sys.stdout.flush() # necessary on win10 even w/ stderr btw
|
||||
|
||||
|
||||
@@ -346,10 +351,8 @@ def log_thrs(log, ival, name):
|
||||
|
||||
def vol_san(vols, txt):
|
||||
for vol in vols:
|
||||
txt = txt.replace(
|
||||
vol.realpath.encode("utf-8"), vol.vpath.encode("utf-8")
|
||||
)
|
||||
|
||||
txt = txt.replace(vol.realpath.encode("utf-8"), vol.vpath.encode("utf-8"))
|
||||
|
||||
return txt
|
||||
|
||||
|
||||
|
||||
@@ -742,11 +742,11 @@ window.baguetteBox = (function () {
|
||||
if (rot || orot === null)
|
||||
return;
|
||||
|
||||
el.classList.add('nt');
|
||||
clmod(el, 'nt', 1);
|
||||
el.removeAttribute('rot');
|
||||
el.removeAttribute("style");
|
||||
rot = el.offsetHeight;
|
||||
el.classList.remove('nt');
|
||||
clmod(el, 'nt');
|
||||
timer.rm(rotn);
|
||||
}
|
||||
|
||||
@@ -799,15 +799,21 @@ window.baguetteBox = (function () {
|
||||
}
|
||||
|
||||
function updateOffset() {
|
||||
var offset = -currentIndex * 100 + '%';
|
||||
var offset = -currentIndex * 100 + '%',
|
||||
xform = slider.style.perspective !== undefined;
|
||||
|
||||
if (options.animation === 'fadeIn') {
|
||||
slider.style.opacity = 0;
|
||||
setTimeout(function () {
|
||||
slider.style.transform = 'translate3d(' + offset + ',0,0)';
|
||||
xform ?
|
||||
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
||||
slider.style.left = offset;
|
||||
slider.style.opacity = 1;
|
||||
}, 400);
|
||||
} else {
|
||||
slider.style.transform = 'translate3d(' + offset + ',0,0)';
|
||||
xform ?
|
||||
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
||||
slider.style.left = offset;
|
||||
}
|
||||
playvid(false);
|
||||
var v = vid();
|
||||
@@ -828,9 +834,9 @@ window.baguetteBox = (function () {
|
||||
|
||||
var prev = QS('.full-image.vis');
|
||||
if (prev)
|
||||
prev.classList.remove('vis');
|
||||
clmod(prev, 'vis');
|
||||
|
||||
el.closest('div').classList.add('vis');
|
||||
clmod(el.closest('div'), 'vis', 1);
|
||||
}
|
||||
|
||||
function preloadNext(index) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
:root {
|
||||
--grid-sz: 10em;
|
||||
--grid-ln: 3;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'scp';
|
||||
src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(/.cpr/deps/scp.woff2) format('woff2');
|
||||
}
|
||||
* {
|
||||
line-height: 1.2em;
|
||||
@@ -23,7 +28,7 @@ html, body {
|
||||
padding: 0;
|
||||
}
|
||||
pre, code, tt {
|
||||
font-family: monospace, monospace;
|
||||
font-family: 'scp', monospace, monospace;
|
||||
}
|
||||
#path,
|
||||
#path * {
|
||||
@@ -113,7 +118,7 @@ a, #files tbody div a:last-child {
|
||||
border-top: 1px solid #383838;
|
||||
}
|
||||
#files tbody td:nth-child(3) {
|
||||
font-family: monospace, monospace;
|
||||
font-family: 'scp', monospace, monospace;
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
white-space: nowrap;
|
||||
@@ -208,8 +213,8 @@ a, #files tbody div a:last-child {
|
||||
color: #720;
|
||||
text-shadow: 0 0 .3em #b80;
|
||||
}
|
||||
#ggrid a.play,
|
||||
html.light #ggrid a.play {
|
||||
#ggrid>a.play,
|
||||
html.light #ggrid>a.play {
|
||||
color: #fff;
|
||||
background: #750;
|
||||
border-color: #c90;
|
||||
@@ -217,36 +222,38 @@ html.light #ggrid a.play {
|
||||
box-shadow: 0 .1em 1.2em #b83;
|
||||
}
|
||||
#files tbody tr.sel td,
|
||||
#ggrid a.sel,
|
||||
html.light #ggrid a.sel {
|
||||
#ggrid>a.sel,
|
||||
#ggrid>a[tt].sel,
|
||||
html.light #ggrid>a.sel,
|
||||
html.light #ggrid>a[tt].sel {
|
||||
color: #fff;
|
||||
background: #925;
|
||||
border-color: #c37;
|
||||
}
|
||||
#files tbody tr.sel:hover td,
|
||||
#files tbody tr.sel:focus td,
|
||||
#ggrid a.sel:hover,
|
||||
html.light #ggrid a.sel:hover {
|
||||
#ggrid>a.sel:hover,
|
||||
html.light #ggrid>a.sel:hover {
|
||||
color: #fff;
|
||||
background: #d39;
|
||||
border-color: #d48;
|
||||
text-shadow: 1px 1px 0 #804;
|
||||
}
|
||||
#ggrid a.sel,
|
||||
html.light #ggrid a.sel {
|
||||
#ggrid>a.sel,
|
||||
html.light #ggrid>a.sel {
|
||||
border-top: 1px solid #d48;
|
||||
box-shadow: 0 .1em 1.2em #b36;
|
||||
transition: all 0.2s cubic-bezier(.2, 2.2, .5, 1); /* https://cubic-bezier.com/#.4,2,.7,1 */
|
||||
}
|
||||
#ggrid a.sel img,
|
||||
#ggrid a.play img {
|
||||
#ggrid>a.sel img,
|
||||
#ggrid>a.play img {
|
||||
opacity: .7;
|
||||
filter: contrast(130%) brightness(107%);
|
||||
}
|
||||
#ggrid a.sel img {
|
||||
#ggrid>a.sel img {
|
||||
box-shadow: 0 0 1em #b36;
|
||||
}
|
||||
#ggrid a.play img {
|
||||
#ggrid>a.play img {
|
||||
box-shadow: 0 0 1em #b83;
|
||||
}
|
||||
#files tr.sel a {
|
||||
@@ -310,7 +317,7 @@ html.light #ggrid a.sel {
|
||||
}
|
||||
#widget {
|
||||
position: fixed;
|
||||
font-size: 1.5em;
|
||||
font-size: 1.4em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -6em;
|
||||
@@ -336,10 +343,11 @@ html.light #ggrid a.sel {
|
||||
cursor: url(/.cpr/dd/4.png), pointer;
|
||||
animation: cursor 500ms;
|
||||
position: relative;
|
||||
top: -.06em;
|
||||
font-size: .9em;
|
||||
top: -.04em;
|
||||
}
|
||||
#wtgrid {
|
||||
font-size: .8em;
|
||||
font-size: .75em;
|
||||
top: -.12em;
|
||||
}
|
||||
#wtgrid:hover,
|
||||
@@ -359,7 +367,8 @@ html.light #ggrid a.sel {
|
||||
#wtoggle {
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
top: -1.2em;
|
||||
font-size: .8em;
|
||||
top: -1em;
|
||||
right: 0;
|
||||
height: 1em;
|
||||
font-size: 2em;
|
||||
@@ -369,7 +378,7 @@ html.light #ggrid a.sel {
|
||||
background: #3c3c3c;
|
||||
box-shadow: 0 0 .5em #222;
|
||||
border-radius: .3em 0 0 0;
|
||||
padding: .2em .2em;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
}
|
||||
#wzip, #wnp {
|
||||
@@ -402,7 +411,7 @@ html.light #ggrid a.sel {
|
||||
#wzip a {
|
||||
font-size: .5em;
|
||||
padding: 0 .3em;
|
||||
margin: -.3em .2em;
|
||||
margin: -.3em .1em;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -417,23 +426,16 @@ html.light #ggrid a.sel {
|
||||
html.light #wfm a:not(.en) {
|
||||
color: #c4a;
|
||||
}
|
||||
#files tbody tr.c1 td {
|
||||
animation: fcut1 .5s ease-out;
|
||||
#files tbody tr.fcut td {
|
||||
animation: fcut .5s ease-out;
|
||||
}
|
||||
#files tbody tr.c2 td {
|
||||
animation: fcut2 .5s ease-out;
|
||||
}
|
||||
@keyframes fcut1 {
|
||||
0% {opacity:0}
|
||||
100% {opacity:1}
|
||||
}
|
||||
@keyframes fcut2 {
|
||||
@keyframes fcut {
|
||||
0% {opacity:0}
|
||||
100% {opacity:1}
|
||||
}
|
||||
#wzip a {
|
||||
font-size: .4em;
|
||||
margin: -.3em .3em;
|
||||
margin: -.3em .1em;
|
||||
}
|
||||
#wtoggle.sel #wzip #selzip {
|
||||
top: -.6em;
|
||||
@@ -533,24 +535,9 @@ html.light #wfm a:not(.en) {
|
||||
box-shadow: 0 -.15em .2em #000 inset;
|
||||
padding-bottom: .3em;
|
||||
}
|
||||
#ops i {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
#ops i:before {
|
||||
content: 'x';
|
||||
color: #282828;
|
||||
text-shadow: 0 0 .08em #01a7e1;
|
||||
position: relative;
|
||||
}
|
||||
#ops i:after {
|
||||
content: 'x';
|
||||
color: #282828;
|
||||
text-shadow: 0 0 .08em #ff3f1a;
|
||||
margin-left: -.35em;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
#ops,
|
||||
.opbox {
|
||||
.opbox,
|
||||
#u2etas {
|
||||
border: 1px solid #3a3a3a;
|
||||
box-shadow: 0 0 1em #222 inset;
|
||||
}
|
||||
@@ -699,6 +686,7 @@ input.eq_gain {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: 7em;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-ms-scroll-chaining: none;
|
||||
overscroll-behavior-y: none;
|
||||
@@ -709,6 +697,10 @@ input.eq_gain {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
height: 2.2em;
|
||||
line-height: 2.2em;
|
||||
border-bottom: 1px solid #555;
|
||||
overflow: hidden;
|
||||
}
|
||||
#thx_ff {
|
||||
padding: 5em 0;
|
||||
@@ -726,8 +718,8 @@ input.eq_gain {
|
||||
}
|
||||
#treeul {
|
||||
position: relative;
|
||||
left: -1.7em;
|
||||
width: calc(100% + 1.3em);
|
||||
left: -2em;
|
||||
width: calc(100% + 2em);
|
||||
}
|
||||
.btn {
|
||||
padding: .2em .4em;
|
||||
@@ -788,7 +780,9 @@ input.eq_gain {
|
||||
color: #fff;
|
||||
}
|
||||
#treeul a:first-child {
|
||||
font-family: monospace, monospace;
|
||||
font-family: 'scp', monospace, monospace;
|
||||
font-size: 1.2em;
|
||||
line-height: 0;
|
||||
}
|
||||
.dumb_loader_thing {
|
||||
display: inline-block;
|
||||
@@ -878,10 +872,12 @@ input.eq_gain {
|
||||
background: #3c3c3c;
|
||||
border: 1px solid #444;
|
||||
border-radius: .3em;
|
||||
padding: .5em;
|
||||
padding: .2em .5em;
|
||||
line-height: 2.3em;
|
||||
margin: 0 1.5em 1em .4em;
|
||||
position: sticky;
|
||||
top: -.3em;
|
||||
z-index: 1;
|
||||
}
|
||||
html.light #ghead {
|
||||
background: #f7f7f7;
|
||||
@@ -891,11 +887,24 @@ html.light #ghead {
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
#ghead>span {
|
||||
white-space: pre;
|
||||
padding-left: .3em;
|
||||
}
|
||||
#ggrid {
|
||||
padding-top: .5em;
|
||||
}
|
||||
#ggrid a {
|
||||
#ggrid>a>span {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: var(--grid-ln);
|
||||
-webkit-box-orient: vertical;
|
||||
padding-top: .3em;
|
||||
}
|
||||
#ggrid>a {
|
||||
display: inline-block;
|
||||
width: 10em;
|
||||
width: var(--grid-sz);
|
||||
vertical-align: top;
|
||||
overflow-wrap: break-word;
|
||||
@@ -907,34 +916,45 @@ html.light #ghead {
|
||||
padding: .3em;
|
||||
margin: .5em;
|
||||
}
|
||||
#ggrid a img {
|
||||
#ggrid>a[tt] {
|
||||
background: linear-gradient(135deg, #383838 95%, #555 95%);
|
||||
}
|
||||
#ggrid>a img {
|
||||
border-radius: .2em;
|
||||
max-width: 10em;
|
||||
max-width: var(--grid-sz);
|
||||
max-height: 8em;
|
||||
max-height: calc(var(--grid-sz)/1.25);
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
#ggrid a span {
|
||||
padding: .2em .3em;
|
||||
display: block;
|
||||
}
|
||||
#ggrid span.dir:before {
|
||||
#ggrid>a.dir:before {
|
||||
content: '📂';
|
||||
line-height: 0;
|
||||
font-size: 2em;
|
||||
margin: -.7em .1em -.5em -.3em;
|
||||
}
|
||||
#ggrid a:hover {
|
||||
#ggrid>a:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
padding: .3em 0;
|
||||
margin: -.4em;
|
||||
text-shadow: 0 0 .1em #000;
|
||||
background: linear-gradient(135deg,rgba(255,255,255,0) 50%,rgba(255,255,255,0.2));
|
||||
border-radius: .3em;
|
||||
font-size: 2em;
|
||||
}
|
||||
#ggrid>a:hover {
|
||||
background: #444;
|
||||
border-color: #555;
|
||||
color: #fd9;
|
||||
}
|
||||
html.light #ggrid a {
|
||||
html.light #ggrid>a {
|
||||
background: #f7f7f7;
|
||||
border-color: #ddd;
|
||||
box-shadow: 0 .1em .2em #ddd;
|
||||
}
|
||||
html.light #ggrid a:hover {
|
||||
html.light #ggrid>a[tt] {
|
||||
background: linear-gradient(135deg, #f7f7f7 95%, #ccc 95%);
|
||||
}
|
||||
html.light #ggrid>a:hover {
|
||||
background: #fff;
|
||||
border-color: #ccc;
|
||||
color: #015;
|
||||
@@ -978,7 +998,7 @@ html.light #rui {
|
||||
padding: .2em 0 .2em .5em;
|
||||
}
|
||||
#rn_vadv input {
|
||||
font-family: monospace, monospace;
|
||||
font-family: 'scp', monospace, monospace;
|
||||
}
|
||||
#rui td+td,
|
||||
#rui td input[type="text"] {
|
||||
@@ -1011,8 +1031,12 @@ html.light #rui {
|
||||
#pvol,
|
||||
#barbuf,
|
||||
#barpos,
|
||||
a.btn,
|
||||
#u2btn,
|
||||
#u2conf label,
|
||||
#rui label,
|
||||
#modal-ok,
|
||||
#modal-ng,
|
||||
#ops {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
@@ -1051,7 +1075,8 @@ html.light {
|
||||
}
|
||||
html.light #ops,
|
||||
html.light .opbox,
|
||||
html.light #srch_form {
|
||||
html.light #srch_form,
|
||||
html.light #u2etas {
|
||||
background: #f7f7f7;
|
||||
box-shadow: 0 0 .3em #ddd;
|
||||
border-color: #f7f7f7;
|
||||
@@ -1251,6 +1276,7 @@ html.light #files tr.sel a:hover {
|
||||
}
|
||||
html.light #treeh {
|
||||
background: #eee;
|
||||
border-color: #ddd;
|
||||
}
|
||||
html.light #tree {
|
||||
scrollbar-color: #a70 #ddd;
|
||||
@@ -1307,11 +1333,11 @@ html.light #tree::-webkit-scrollbar {
|
||||
opacity: 1;
|
||||
}
|
||||
.full-image {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.full-image figure {
|
||||
display: inline;
|
||||
@@ -1554,7 +1580,7 @@ html.light #bbox-overlay figcaption a {
|
||||
color: #333;
|
||||
}
|
||||
#u2conf #u2btn {
|
||||
margin: -1.5em 0;
|
||||
margin: -2.4em 0;
|
||||
padding: .8em 0;
|
||||
width: 100%;
|
||||
max-width: 12em;
|
||||
@@ -1573,6 +1599,7 @@ html.light #bbox-overlay figcaption a {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
#u2tab {
|
||||
border-collapse: collapse;
|
||||
margin: 3em auto;
|
||||
width: calc(100% - 2em);
|
||||
max-width: 100em;
|
||||
@@ -1583,7 +1610,7 @@ html.light #bbox-overlay figcaption a {
|
||||
#u2tab td {
|
||||
border: 1px solid #ccc;
|
||||
border-width: 0 0px 1px 0;
|
||||
padding: .1em .3em;
|
||||
padding: .2em .3em;
|
||||
}
|
||||
#u2tab td:nth-child(2) {
|
||||
width: 5em;
|
||||
@@ -1600,17 +1627,51 @@ html.light #bbox-overlay figcaption a {
|
||||
#u2tab tbody tr:hover td {
|
||||
background: #222;
|
||||
}
|
||||
#u2etas {
|
||||
background: #333;
|
||||
padding: .2em .5em;
|
||||
border-radius: .5em;
|
||||
border-width: .25em 0;
|
||||
width: 17em;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
font-family: 'scp', monospace, monospace;
|
||||
}
|
||||
#u2etas.o {
|
||||
width: 20em;
|
||||
}
|
||||
#u2etas .o {
|
||||
display: none;
|
||||
}
|
||||
#u2etas.o .o {
|
||||
display: unset;
|
||||
}
|
||||
#u2etaw {
|
||||
width: 18em;
|
||||
font-size: .94em;
|
||||
margin: 1.8em auto .5em auto;
|
||||
}
|
||||
#u2etas.o #u2etaw {
|
||||
width: 21em;
|
||||
}
|
||||
#u2cards {
|
||||
padding: 1em 0 .3em 1em;
|
||||
margin: 1.5em auto -2.5em auto;
|
||||
padding: 1em 1em .3em 1em;
|
||||
margin: 0 auto -1.5em auto;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
min-width: 24em;
|
||||
}
|
||||
#u2cards.w {
|
||||
width: 45em;
|
||||
width: 44em;
|
||||
text-align: left;
|
||||
}
|
||||
#u2etaw.w {
|
||||
width: 52em;
|
||||
text-align: right;
|
||||
margin: 3em auto -2.7em auto;
|
||||
}
|
||||
#u2cards a {
|
||||
padding: .2em 1em;
|
||||
border: 1px solid #777;
|
||||
@@ -1717,6 +1778,14 @@ html.light #bbox-overlay figcaption a {
|
||||
box-shadow: none;
|
||||
opacity: .2;
|
||||
}
|
||||
#u2flagblock {
|
||||
display: none;
|
||||
margin: 2em auto 4em auto;
|
||||
padding: .5em;
|
||||
max-width: 27em;
|
||||
text-align: center;
|
||||
border: .2em dashed rgba(128, 128, 128, 0.3);
|
||||
}
|
||||
#u2foot {
|
||||
color: #fff;
|
||||
font-style: italic;
|
||||
@@ -1751,7 +1820,27 @@ html.light #u2foot .warn span {
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
.prog {
|
||||
font-family: monospace, monospace;
|
||||
font-family: 'scp', monospace, monospace;
|
||||
}
|
||||
#u2tab span.inf,
|
||||
#u2tab span.ok,
|
||||
#u2tab span.err {
|
||||
color: #fff;
|
||||
padding: .22em;
|
||||
border-radius: .2em;
|
||||
border: 2px solid #f0f;
|
||||
}
|
||||
#u2tab span.inf {
|
||||
background: #07a;
|
||||
border-color: #0be;
|
||||
}
|
||||
#u2tab span.ok {
|
||||
background: #380;
|
||||
border-color: #8e4;
|
||||
}
|
||||
#u2tab span.err {
|
||||
background: #900;
|
||||
border-color: #d06;
|
||||
}
|
||||
#u2tab a>span {
|
||||
font-weight: bold;
|
||||
|
||||
@@ -32,7 +32,7 @@ ebi('widget').innerHTML = (
|
||||
'<div id="wtoggle">' +
|
||||
'<span id="wfm"><a' +
|
||||
' href="#" id="fren" tt="rename selected item$NHotkey: F2">✎<span>name</span></a><a' +
|
||||
' href="#" id="fdel" tt="delete selected items$NHotkey: ctrl-K">⌫<span>delete</span></a><a' +
|
||||
' href="#" id="fdel" tt="delete selected items$NHotkey: ctrl-K">⌫<span>del.</span></a><a' +
|
||||
' href="#" id="fcut" tt="cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X">✂<span>cut</span></a><a' +
|
||||
' href="#" id="fpst" tt="paste a previously cut/copied selection$NHotkey: ctrl-V">📋<span>paste</span></a>' +
|
||||
'</span><span id="wzip"><a' +
|
||||
@@ -43,7 +43,7 @@ ebi('widget').innerHTML = (
|
||||
' href="#" id="npirc" tt="copy irc-formatted track info">📋irc</a><a' +
|
||||
' href="#" id="nptxt" tt="copy plaintext track info">📋txt</a>' +
|
||||
'</span><a' +
|
||||
' href="#" id="wtgrid" tt="toggle grid/list view">田</a><a' +
|
||||
' href="#" id="wtgrid" tt="toggle grid/list view$NHotkey: G">田</a><a' +
|
||||
' href="#" id="wtico">♫</a>' +
|
||||
'</div>' +
|
||||
'<div id="widgeti">' +
|
||||
@@ -102,6 +102,12 @@ ebi('op_up2k').innerHTML = (
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
|
||||
'<div id="u2etaw"><div id="u2etas"><div class="o">\n' +
|
||||
' hash: <span id="u2etah" tt="average <em>hashing</em> speed, and estimated time until finish">(no uploads are queued yet)</span><br />\n' +
|
||||
' send: <span id="u2etau" tt="average <em>upload</em> speed and estimated time until finish">(no uploads are queued yet)</span><br />\n' +
|
||||
' </div><span class="o">done: </span><span id="u2etat" tt="average <em>total</em> speed and estimated time until finish">(no uploads are queued yet)</span>\n' +
|
||||
'</div></div>\n' +
|
||||
|
||||
'<div id="u2cards">\n' +
|
||||
' <a href="#" act="ok" tt="completed successfully">ok <span>0</span></a><a\n' +
|
||||
' href="#" act="ng" tt="failed / rejected / not-found">ng <span>0</span></a><a\n' +
|
||||
@@ -121,6 +127,7 @@ ebi('op_up2k').innerHTML = (
|
||||
' <tbody></tbody>\n' +
|
||||
'</table>\n' +
|
||||
|
||||
'<p id="u2flagblock"><b>the files were added to the queue</b><br />however there is a busy up2k in another browser tab,<br />so waiting for that to finish first</p>\n' +
|
||||
'<p id="u2foot"></p>\n' +
|
||||
'<p id="u2footfoot" data-perm="write">( you can use the <a href="#" id="u2nope">basic uploader</a> if you don\'t need lastmod timestamps, resumable uploads, or progress bars )</p>'
|
||||
);
|
||||
@@ -160,6 +167,7 @@ ebi('tree').innerHTML = (
|
||||
' <a href="#" class="btn" step="2" id="twobytwo" tt="Hotkey: A">+</a>\n' +
|
||||
' <a href="#" class="btn" step="-2" id="twig" tt="Hotkey: D">–</a>\n' +
|
||||
' <a href="#" class="tgl btn" id="dyntree" tt="autogrow as tree expands">a</a>\n' +
|
||||
' <a href="#" class="btn" id="visdir" tt="scroll to selected folder">v</a>\n' +
|
||||
'</div>\n' +
|
||||
'<ul id="treeul"></ul>\n' +
|
||||
'<div id="thx_ff"> </div>'
|
||||
@@ -391,6 +399,7 @@ function MPlayer() {
|
||||
r.au_native2 = null;
|
||||
r.au_ogvjs = null;
|
||||
r.au_ogvjs2 = null;
|
||||
r.loading = false;
|
||||
r.tracks = {};
|
||||
r.order = [];
|
||||
|
||||
@@ -414,18 +423,14 @@ function MPlayer() {
|
||||
}
|
||||
}
|
||||
|
||||
r.vol = sread('vol');
|
||||
if (r.vol !== null)
|
||||
r.vol = parseFloat(r.vol);
|
||||
else
|
||||
r.vol = 0.5;
|
||||
r.vol = clamp(fcfg_get('vol', 0.5), 0, 1);
|
||||
|
||||
r.expvol = function (v) {
|
||||
return 0.5 * v + 0.5 * v * v;
|
||||
};
|
||||
|
||||
r.setvol = function (vol) {
|
||||
r.vol = Math.max(Math.min(vol, 1), 0);
|
||||
r.vol = clamp(vol, 0, 1);
|
||||
swrite('vol', vol);
|
||||
r.stopfade(true);
|
||||
|
||||
@@ -672,7 +677,7 @@ var pbar = (function () {
|
||||
|
||||
bctx.clearRect(0, 0, bc.w, bc.h);
|
||||
|
||||
if (!mp.au)
|
||||
if (!mp.au || mp.loading)
|
||||
return;
|
||||
|
||||
var sm = bc.w * 1.0 / mp.au.duration,
|
||||
@@ -694,25 +699,26 @@ var pbar = (function () {
|
||||
r.drawpos = function () {
|
||||
var bc = r.buf,
|
||||
pc = r.pos,
|
||||
pctx = pc.ctx;
|
||||
pctx = pc.ctx,
|
||||
apos, adur;
|
||||
|
||||
pctx.clearRect(0, 0, pc.w, pc.h);
|
||||
|
||||
if (!mp.au || isNaN(mp.au.duration) || isNaN(mp.au.currentTime))
|
||||
if (!mp.au || mp.loading || isNaN(adur = mp.au.duration) || isNaN(apos = mp.au.currentTime) || apos < 0 || adur < apos)
|
||||
return; // not-init || unsupp-codec
|
||||
|
||||
var sm = bc.w * 1.0 / mp.au.duration;
|
||||
var sm = bc.w * 1.0 / adur;
|
||||
|
||||
pctx.fillStyle = light ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)';
|
||||
for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++)
|
||||
for (var p = 1, mins = adur / 10; p <= mins; p++)
|
||||
pctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
|
||||
|
||||
pctx.fillStyle = light ? 'rgba(0,64,0,0.5)' : 'rgba(192,255,96,0.5)';
|
||||
for (var p = 1, mins = mp.au.duration / 60; p <= mins; p++)
|
||||
for (var p = 1, mins = adur / 60; p <= mins; p++)
|
||||
pctx.fillRect(Math.floor(sm * p * 60), 0, 2, pc.h);
|
||||
|
||||
var w = 8,
|
||||
x = sm * mp.au.currentTime;
|
||||
x = sm * apos;
|
||||
|
||||
pctx.fillStyle = '#573'; pctx.fillRect((x - w / 2) - 1, 0, w + 2, pc.h);
|
||||
pctx.fillStyle = '#dfc'; pctx.fillRect((x - w / 2), 0, 8, pc.h);
|
||||
@@ -723,8 +729,8 @@ var pbar = (function () {
|
||||
pctx.font = '1em sans-serif';
|
||||
|
||||
var m = pctx.measureText.bind(pctx),
|
||||
t1 = s2ms(mp.au.duration),
|
||||
t2 = s2ms(mp.au.currentTime),
|
||||
t1 = s2ms(adur),
|
||||
t2 = s2ms(apos),
|
||||
yt = pc.h / 3 * 2.1,
|
||||
xt1 = pc.w - (m(t1).width + 12),
|
||||
xt2 = x < pc.w / 2 ? (x + 12) : (Math.min(pc.w - m(t1 + ":88").width, x - 12) - m(t2).width);
|
||||
@@ -957,7 +963,7 @@ var mpui = (function () {
|
||||
}
|
||||
|
||||
// preload next song
|
||||
if (mpl.preload && preloaded != mp.au.src) {
|
||||
if (mpl.preload && !mp.loading && preloaded != mp.au.src) {
|
||||
var pos = mp.au.currentTime,
|
||||
len = mp.au.duration;
|
||||
|
||||
@@ -1013,6 +1019,21 @@ function need_ogv_for(url) {
|
||||
}
|
||||
|
||||
|
||||
function start_sinegen() {
|
||||
var af = 'data:audio/wav;base64,UklGRlaxAgBXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAATElTVBoAAABJTkZPSVNGVA4AAABMYXZmNTguNzYuMTAwAGRhdGEQsQIAAAB',
|
||||
body = 'iArcE8AYCCeEKggzaDeQOmA/0D/QPmQ/kDtsNggzhCgMJ8Qa4BGMCAQCe/Un7EPn+9h/1fvMm8hzxaPAM8AzwZ/Ac8SXyfvMf9f32D/lI+539//9';
|
||||
|
||||
while (af.length < 235304)
|
||||
af += body;
|
||||
|
||||
var au = new Audio(af.slice(0, 235304));
|
||||
au.onplay = au.pause.bind(au);
|
||||
au.volume = 0.5;
|
||||
au.play();
|
||||
return au;
|
||||
}
|
||||
|
||||
|
||||
var audio_eq = (function () {
|
||||
var r = {
|
||||
"en": false,
|
||||
@@ -1276,11 +1297,15 @@ function play(tid, is_ev, seek, call_depth) {
|
||||
mp.au = mp.au_ogvjs;
|
||||
}
|
||||
else if (window['OGVPlayer']) {
|
||||
mp.loading = true;
|
||||
mp.au = mp.au_ogvjs = new OGVPlayer();
|
||||
attempt_play = is_ev;
|
||||
mp.au.addEventListener('error', evau_error, true);
|
||||
mp.au.addEventListener('progress', pbar.drawpos);
|
||||
mp.au.addEventListener('ended', next_song);
|
||||
mp.au.onerror = evau_error;
|
||||
mp.au.onprogress = pbar.drawpos;
|
||||
mp.au.onended = next_song;
|
||||
mp.au.onloadedmetadata = mp.au.onloadeddata = function () {
|
||||
mp.loading = false;
|
||||
};
|
||||
widget.open();
|
||||
}
|
||||
else {
|
||||
@@ -1292,6 +1317,10 @@ function play(tid, is_ev, seek, call_depth) {
|
||||
import_js('/.cpr/deps/ogv.js', function () {
|
||||
toast.hide();
|
||||
play(tid, false, seek, 1);
|
||||
|
||||
var m = /.* Version\/([0-9]+)\.[0-9\.]+ Mobile\/[^ ]+ Safari\/[0-9\.]+$/.exec(navigator.userAgent);
|
||||
if (m && parseInt(m[1]) < 14)
|
||||
toast.err(0, 'because this is an apple device,\nsafari 14 or newer is required\n\nyou are using safari version ' + m[1] + ', so playback of ogg/vorbis/opus files will be buggy\n\nnote: every iOS browser is safari');
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -1300,18 +1329,26 @@ function play(tid, is_ev, seek, call_depth) {
|
||||
else {
|
||||
if (!mp.au_native) {
|
||||
mp.au = mp.au_native = new Audio();
|
||||
mp.au.addEventListener('error', evau_error, true);
|
||||
mp.au.addEventListener('progress', pbar.drawpos);
|
||||
mp.au.addEventListener('ended', next_song);
|
||||
mp.au.onerror = evau_error;
|
||||
mp.au.onprogress = pbar.drawpos;
|
||||
mp.au.onended = next_song;
|
||||
widget.open();
|
||||
}
|
||||
mp.au = mp.au_native;
|
||||
mp.loading = false;
|
||||
}
|
||||
|
||||
audio_eq.apply();
|
||||
|
||||
url += (url.indexOf('?') < 0 ? '?cache' : '&cache');
|
||||
if (mp.au.src == url)
|
||||
mp.au.currentTime = 0;
|
||||
else {
|
||||
mp.loading = mp.au == mp.au_ogvjs;
|
||||
mp.au.src = url;
|
||||
}
|
||||
|
||||
mp.au.tid = tid;
|
||||
mp.au.src = url + (url.indexOf('?') < 0 ? '?cache' : '&cache');
|
||||
mp.au.volume = mp.expvol(mp.vol);
|
||||
var oid = 'a' + tid;
|
||||
setclass(oid, 'play act');
|
||||
@@ -1352,7 +1389,7 @@ function play(tid, is_ev, seek, call_depth) {
|
||||
return true;
|
||||
}
|
||||
catch (ex) {
|
||||
toast.err(0, 'playback failed: ' + ex);
|
||||
toast.err(0, esc('playback failed: ' + ex));
|
||||
}
|
||||
setclass(oid, 'play');
|
||||
setTimeout(next_song, 500);
|
||||
@@ -1386,7 +1423,7 @@ function evau_error(e) {
|
||||
|
||||
err += '\n\nFile: «' + uricom_dec(eplaya.src.split('/').slice(-1)[0])[0] + '»';
|
||||
|
||||
toast.warn(15, err);
|
||||
toast.warn(15, esc(err + ''));
|
||||
}
|
||||
|
||||
|
||||
@@ -1427,9 +1464,16 @@ function autoplay_blocked(seek) {
|
||||
go.textContent = 'Play "' + fn + '"';
|
||||
go.onclick = function (e) {
|
||||
unblocked(e);
|
||||
// chrome 91 may permanently taint on a failed play()
|
||||
// depending on win10 settings or something? idk
|
||||
mp.au_native = mp.au_ogvjs = null;
|
||||
if (mp.au !== mp.au_ogvjs)
|
||||
// chrome 91 may permanently taint on a failed play()
|
||||
// depending on win10 settings or something? idk
|
||||
mp.au_native = null;
|
||||
else
|
||||
// iOS browsers allow playing ogg/vorbis/opus in the background
|
||||
// if there is an <audio> tag which at some point played audio
|
||||
if (!mp.sinegen)
|
||||
mp.sinegen = start_sinegen();
|
||||
|
||||
play(tid, true, seek);
|
||||
mp.fade_in();
|
||||
};
|
||||
@@ -1657,7 +1701,7 @@ var fileman = (function () {
|
||||
r.clip = jread('fman_clip', []);
|
||||
|
||||
var nsel = msel.getsel().length;
|
||||
clmod(bren, 'en', nsel == 1);
|
||||
clmod(bren, 'en', nsel);
|
||||
clmod(bdel, 'en', nsel);
|
||||
clmod(bcut, 'en', nsel);
|
||||
clmod(bpst, 'en', r.clip && r.clip.length);
|
||||
@@ -1689,7 +1733,7 @@ var fileman = (function () {
|
||||
|
||||
var vsp = vsplit(vp);
|
||||
if (base != vsp[0])
|
||||
return toast.err(0, 'bug:\n' + base + '\n' + vsp[0]);
|
||||
return toast.err(0, esc('bug:\n' + base + '\n' + vsp[0]));
|
||||
|
||||
var vars = ft2dict(ebi(sel[a].id).closest('tr'));
|
||||
mkeys = vars[1].concat(vars[2]);
|
||||
@@ -1905,7 +1949,7 @@ var fileman = (function () {
|
||||
re = new RegExp(ptn, cs ? 'i' : '');
|
||||
}
|
||||
catch (ex) {
|
||||
return toast.err(5, 'invalid regex:\n' + ex);
|
||||
return toast.err(5, esc('invalid regex:\n' + ex));
|
||||
}
|
||||
toast.hide();
|
||||
|
||||
@@ -1937,7 +1981,7 @@ var fileman = (function () {
|
||||
return rn_cancel();
|
||||
}
|
||||
|
||||
toast.inf(0, 'renaming ' + f.length + ' items\n\n' + f[0].ofn);
|
||||
toast.inf(0, esc('renaming ' + f.length + ' items\n\n' + f[0].ofn));
|
||||
var dst = base + uricom_enc(f[0].inew.value, false);
|
||||
|
||||
function rename_cb() {
|
||||
@@ -1984,7 +2028,7 @@ var fileman = (function () {
|
||||
treectl.goto(get_evpath());
|
||||
return;
|
||||
}
|
||||
toast.inf(0, 'deleting ' + (vps.length + 1) + ' items\n\n' + vp);
|
||||
toast.inf(0, esc('deleting ' + (vps.length + 1) + ' items\n\n' + vp));
|
||||
|
||||
xhr.open('GET', vp + '?delete', true);
|
||||
xhr.onreadystatechange = delete_cb;
|
||||
@@ -2002,8 +2046,8 @@ var fileman = (function () {
|
||||
deleter();
|
||||
}
|
||||
|
||||
modal.confirm('===== DANGER =====\nDELETE these ' + vps.length + ' items?\n\n' + uricom_adec(vps).join('\n'), function () {
|
||||
modal.confirm('Last chance! Delete?', deleter, null);
|
||||
modal.confirm('<h6 style="color:#900">DANGER</h6>\n<b>DELETE these ' + vps.length + ' items?</b><ul>' + uricom_adec(vps, true).join('') + '</ul>', function () {
|
||||
modal.confirm('<b>Last chance!</b> Delete?', deleter, null);
|
||||
}, null);
|
||||
};
|
||||
|
||||
@@ -2018,17 +2062,21 @@ var fileman = (function () {
|
||||
if (!sel.length)
|
||||
return toast.err(3, 'select at least 1 item to cut');
|
||||
|
||||
if (sel.length < 100)
|
||||
for (var a = 0; a < sel.length; a++) {
|
||||
vps.push(sel[a].vp);
|
||||
var cl = ebi(sel[a].id).closest('tr').classList,
|
||||
inv = cl.contains('c1');
|
||||
|
||||
cl.remove(inv ? 'c1' : 'c2');
|
||||
cl.add(inv ? 'c2' : 'c1');
|
||||
var els = [];
|
||||
for (var a = 0; a < sel.length; a++) {
|
||||
vps.push(sel[a].vp);
|
||||
if (sel.length < 100) {
|
||||
els.push(ebi(sel[a].id).closest('tr'));
|
||||
clmod(els[a], 'fcut');
|
||||
}
|
||||
}
|
||||
|
||||
toast.inf(1, 'cut ' + sel.length + ' items');
|
||||
setTimeout(function () {
|
||||
for (var a = 0; a < els.length; a++)
|
||||
clmod(els[a], 'fcut', 1);
|
||||
}, 1);
|
||||
|
||||
toast.inf(1.5, 'cut ' + sel.length + ' items');
|
||||
jwrite('fman_clip', vps);
|
||||
r.tx(1);
|
||||
};
|
||||
@@ -2063,7 +2111,7 @@ var fileman = (function () {
|
||||
}
|
||||
|
||||
if (exists.length)
|
||||
toast.warn(30, 'these ' + exists.length + ' items cannot be pasted here (names already exist):\n\n' + uricom_adec(exists).join('\n'));
|
||||
toast.warn(30, 'these ' + exists.length + ' items cannot be pasted here (names already exist):<ul>' + uricom_adec(exists, true).join('') + '</ul>');
|
||||
|
||||
if (!req.length)
|
||||
return;
|
||||
@@ -2078,7 +2126,7 @@ var fileman = (function () {
|
||||
r.tx(srcdir);
|
||||
return;
|
||||
}
|
||||
toast.inf(0, 'pasting ' + (req.length + 1) + ' items\n\n' + uricom_dec(vp)[0]);
|
||||
toast.inf(0, esc('pasting ' + (req.length + 1) + ' items\n\n' + uricom_dec(vp)[0]));
|
||||
|
||||
var dst = get_evpath() + vp.split('/').slice(-1)[0];
|
||||
|
||||
@@ -2098,7 +2146,7 @@ var fileman = (function () {
|
||||
paster();
|
||||
}
|
||||
|
||||
modal.confirm('paste these ' + req.length + ' items here?\n\n' + uricom_adec(req).join('\n'), function () {
|
||||
modal.confirm('paste these ' + req.length + ' items here?<ul>' + uricom_adec(req, true).join('') + '</ul>', function () {
|
||||
paster();
|
||||
jwrite('fman_clip', []);
|
||||
}, null);
|
||||
@@ -2141,14 +2189,16 @@ var thegrid = (function () {
|
||||
gfiles.style.display = 'none';
|
||||
gfiles.innerHTML = (
|
||||
'<div id="ghead">' +
|
||||
'<a href="#" class="tgl btn" id="gridsel" tt="enable file selection; ctrl-click a file to override$NHotkey: S">multiselect</a> zoom ' +
|
||||
'<a href="#" class="tgl btn" id="gridsel" tt="enable file selection; ctrl-click a file to override$NHotkey: S">multiselect</a> <span>zoom: ' +
|
||||
'<a href="#" class="btn" z="-1.2" tt="Hotkey: shift-A">–</a> ' +
|
||||
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a> sort by: ' +
|
||||
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a></span> <span>chop: ' +
|
||||
'<a href="#" class="btn" l="-1" tt="truncate filenames more (show less)">–</a> ' +
|
||||
'<a href="#" class="btn" l="1" tt="truncate filenames less (show more)">+</a></span> <span>sort by: ' +
|
||||
'<a href="#" s="href">name</a>, ' +
|
||||
'<a href="#" s="sz">size</a>, ' +
|
||||
'<a href="#" s="ts">date</a>, ' +
|
||||
'<a href="#" s="ext">type</a>' +
|
||||
'</div>' +
|
||||
'</span></div>' +
|
||||
'<div id="ggrid"></div>'
|
||||
);
|
||||
lfiles.parentNode.insertBefore(gfiles, lfiles);
|
||||
@@ -2157,7 +2207,8 @@ var thegrid = (function () {
|
||||
'thumbs': bcfg_get('thumbs', true),
|
||||
'en': bcfg_get('griden', false),
|
||||
'sel': bcfg_get('gridsel', false),
|
||||
'sz': fcfg_get('gridsz', 10),
|
||||
'sz': clamp(fcfg_get('gridsz', 10), 4, 40),
|
||||
'ln': clamp(icfg_get('gridln', 3), 1, 7),
|
||||
'isdirty': true,
|
||||
'bbox': null
|
||||
};
|
||||
@@ -2177,8 +2228,7 @@ var thegrid = (function () {
|
||||
loadgrid();
|
||||
}
|
||||
else {
|
||||
lfiles.style.display = '';
|
||||
gfiles.style.display = 'none';
|
||||
ungrid();
|
||||
}
|
||||
pbar.onresize();
|
||||
vbar.onresize();
|
||||
@@ -2187,11 +2237,15 @@ var thegrid = (function () {
|
||||
var btnclick = function (e) {
|
||||
ev(e);
|
||||
var s = this.getAttribute('s'),
|
||||
z = this.getAttribute('z');
|
||||
z = this.getAttribute('z'),
|
||||
l = this.getAttribute('l');
|
||||
|
||||
if (z)
|
||||
return setsz(z > 0 ? r.sz * z : r.sz / (-z));
|
||||
|
||||
if (l)
|
||||
return setln(parseInt(l));
|
||||
|
||||
var t = lfiles.tHead.rows[0].cells;
|
||||
for (var a = 0; a < t.length; a++)
|
||||
if (t[a].getAttribute('name') == s) {
|
||||
@@ -2202,7 +2256,7 @@ var thegrid = (function () {
|
||||
r.setdirty();
|
||||
};
|
||||
|
||||
var links = QSA('#ghead>a');
|
||||
var links = QSA('#ghead a');
|
||||
for (var a = 0; a < links.length; a++)
|
||||
links[a].onclick = btnclick;
|
||||
|
||||
@@ -2215,19 +2269,35 @@ var thegrid = (function () {
|
||||
|
||||
r.setvis = function (vis) {
|
||||
(r.en ? gfiles : lfiles).style.display = vis ? '' : 'none';
|
||||
}
|
||||
};
|
||||
|
||||
r.setdirty = function () {
|
||||
r.dirty = true;
|
||||
if (r.en) {
|
||||
loadgrid();
|
||||
}
|
||||
};
|
||||
|
||||
function setln(v) {
|
||||
if (v) {
|
||||
r.ln += v;
|
||||
if (r.ln < 1) r.ln = 1;
|
||||
if (r.ln > 7) r.ln = v < 0 ? 7 : 99;
|
||||
swrite('gridln', r.ln);
|
||||
setTimeout(r.tippen, 20);
|
||||
}
|
||||
try {
|
||||
document.documentElement.style.setProperty('--grid-ln', r.ln);
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
setln();
|
||||
|
||||
function setsz(v) {
|
||||
if (v !== undefined) {
|
||||
r.sz = v;
|
||||
r.sz = clamp(v, 4, 40);
|
||||
swrite('gridsz', r.sz);
|
||||
setTimeout(r.tippen, 20);
|
||||
}
|
||||
try {
|
||||
document.documentElement.style.setProperty('--grid-sz', r.sz + 'em');
|
||||
@@ -2285,14 +2355,56 @@ var thegrid = (function () {
|
||||
var ths = QSA('#ggrid>a');
|
||||
|
||||
for (var a = 0, aa = ths.length; a < aa; a++) {
|
||||
var tr = ebi(ths[a].getAttribute('ref')).closest('tr');
|
||||
ths[a].setAttribute('class', tr.getAttribute('class'));
|
||||
var tr = ebi(ths[a].getAttribute('ref')).closest('tr'),
|
||||
cl = tr.getAttribute('class') || '';
|
||||
|
||||
if (ths[a].getAttribute('href').endsWith('/'))
|
||||
cl += ' dir';
|
||||
|
||||
ths[a].setAttribute('class', cl);
|
||||
}
|
||||
var uns = QS('#ggrid a[ref="unsearch"]');
|
||||
if (uns)
|
||||
uns.onclick = function () {
|
||||
ebi('unsearch').click();
|
||||
};
|
||||
};
|
||||
|
||||
r.tippen = function () {
|
||||
var els = QSA('#ggrid>a>span'),
|
||||
aa = els.length;
|
||||
|
||||
if (!aa)
|
||||
return;
|
||||
|
||||
var cs = window.getComputedStyle(els[0]),
|
||||
fs = parseFloat(cs.lineHeight),
|
||||
pad = parseFloat(cs.paddingTop),
|
||||
pels = [],
|
||||
todo = [];
|
||||
|
||||
for (var a = 0; a < aa; a++) {
|
||||
var vis = Math.round((els[a].offsetHeight - pad) / fs),
|
||||
all = Math.round((els[a].scrollHeight - pad) / fs),
|
||||
par = els[a].parentNode;
|
||||
|
||||
pels.push(par);
|
||||
todo.push(vis < all ? par.getAttribute('ttt') : null);
|
||||
}
|
||||
|
||||
for (var a = 0; a < todo.length; a++) {
|
||||
if (todo[a])
|
||||
pels[a].setAttribute('tt', todo[a]);
|
||||
else
|
||||
pels[a].removeAttribute('tt');
|
||||
}
|
||||
|
||||
tt.att(ebi('ggrid'));
|
||||
};
|
||||
|
||||
function ungrid() {
|
||||
lfiles.style.display = '';
|
||||
gfiles.style.display = 'none';
|
||||
}
|
||||
|
||||
function loadgrid() {
|
||||
@@ -2310,6 +2422,7 @@ var thegrid = (function () {
|
||||
for (var a = 0, aa = files.length; a < aa; a++) {
|
||||
var ao = files[a],
|
||||
href = esc(ao.getAttribute('href')),
|
||||
name = uricom_dec(vsplit(href)[1])[0],
|
||||
ref = ao.getAttribute('id'),
|
||||
isdir = href.split('?')[0].slice(-1)[0] == '/',
|
||||
ac = isdir ? ' class="dir"' : '',
|
||||
@@ -2342,7 +2455,8 @@ var thegrid = (function () {
|
||||
ihref = '/.cpr/ico/' + ihref.slice(0, -1);
|
||||
}
|
||||
|
||||
html.push('<a href="' + href + '" ref="' + ref + '"><img src="' +
|
||||
html.push('<a href="' + href + '" ref="' + ref +
|
||||
'"' + ac + ' ttt="' + esc(name) + '"><img src="' +
|
||||
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
|
||||
}
|
||||
ebi('ggrid').innerHTML = html.join('\n');
|
||||
@@ -2354,6 +2468,7 @@ var thegrid = (function () {
|
||||
r.dirty = false;
|
||||
r.bagit();
|
||||
r.loadsel();
|
||||
setTimeout(r.tippen, 20);
|
||||
}
|
||||
|
||||
r.bagit = function () {
|
||||
@@ -2391,7 +2506,8 @@ var thegrid = (function () {
|
||||
})();
|
||||
|
||||
|
||||
function tree_scrollto() {
|
||||
function tree_scrollto(e) {
|
||||
ev(e);
|
||||
var act = QS('#treeul a.hl'),
|
||||
ul = act ? act.offsetParent : null;
|
||||
|
||||
@@ -2851,7 +2967,7 @@ var treectl = (function () {
|
||||
var treectl = {
|
||||
"hidden": true,
|
||||
"ls_cb": null,
|
||||
"dir_cb": null
|
||||
"dir_cb": tree_scrollto
|
||||
},
|
||||
entreed = false,
|
||||
fixedpos = false,
|
||||
@@ -2859,9 +2975,7 @@ var treectl = (function () {
|
||||
prev_winh = null,
|
||||
dyn = bcfg_get('dyntree', true),
|
||||
dots = bcfg_get('dotfiles', false),
|
||||
treesz = icfg_get('treesz', 16);
|
||||
|
||||
treesz = Math.min(Math.max(treesz, 4), 50);
|
||||
treesz = clamp(icfg_get('treesz', 16), 4, 50);
|
||||
|
||||
treectl.entree = function (e) {
|
||||
ev(e);
|
||||
@@ -3155,7 +3269,13 @@ var treectl = (function () {
|
||||
}
|
||||
html.push('</tbody>');
|
||||
html = html.join('\n');
|
||||
ebi('files').innerHTML = html;
|
||||
try {
|
||||
ebi('files').innerHTML = html;
|
||||
}
|
||||
catch (ex) { //ie9
|
||||
window.location.href = this.top;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hpush)
|
||||
hist_push(this.top);
|
||||
@@ -3239,6 +3359,7 @@ var treectl = (function () {
|
||||
|
||||
ebi('entree').onclick = treectl.entree;
|
||||
ebi('detree').onclick = treectl.detree;
|
||||
ebi('visdir').onclick = tree_scrollto;
|
||||
ebi('dotfiles').onclick = tdots;
|
||||
ebi('dyntree').onclick = dyntree;
|
||||
ebi('twig').onclick = scaletree;
|
||||
@@ -3775,7 +3896,7 @@ var msel = (function () {
|
||||
if (r.all && r.all.length) {
|
||||
for (var a = 0; a < r.all.length; a++) {
|
||||
var ao = r.all[a];
|
||||
ao.sel = ebi(ao.id).closest('tr').classList.contains('sel');
|
||||
ao.sel = clgot(ebi(ao.id).closest('tr'), 'sel');
|
||||
if (ao.sel)
|
||||
r.sel.push(ao);
|
||||
}
|
||||
@@ -3791,7 +3912,7 @@ var msel = (function () {
|
||||
item = {};
|
||||
|
||||
item.id = links[a].getAttribute('id');
|
||||
item.sel = links[a].closest('tr').classList.contains('sel');
|
||||
item.sel = clgot(links[a].closest('tr'), 'sel');
|
||||
item.vp = href.indexOf('/') !== -1 ? href : vbase + href;
|
||||
|
||||
r.all.push(item);
|
||||
|
||||
@@ -185,7 +185,7 @@ function md_plug_err(ex, js) {
|
||||
errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
|
||||
errbox.textContent = msg;
|
||||
errbox.onclick = function () {
|
||||
modal.alert('<pre>' + ex.stack + '</pre>');
|
||||
modal.alert('<pre>' + esc(ex.stack) + '</pre>');
|
||||
};
|
||||
if (o) {
|
||||
errbox.appendChild(o);
|
||||
|
||||
@@ -370,8 +370,8 @@ function save_cb() {
|
||||
}
|
||||
|
||||
if (!r.ok) {
|
||||
if (!this.btn.classList.contains('force-save')) {
|
||||
this.btn.classList.add('force-save');
|
||||
if (!clgot(this.btn, 'force-save')) {
|
||||
clmod(this.btn, 'force-save', 1);
|
||||
var msg = [
|
||||
'This file has been modified since you started editing it!\n',
|
||||
'if you really want to overwrite, press save again.\n',
|
||||
@@ -387,7 +387,7 @@ function save_cb() {
|
||||
return toast.err(0, 'Error! Save failed. Maybe this JSON explains why:\n\n' + this.responseText);
|
||||
}
|
||||
|
||||
this.btn.classList.remove('force-save');
|
||||
clmod(this.btn, 'force-save');
|
||||
//alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512);
|
||||
|
||||
run_savechk(r.lastmod, this.txt, this.btn, 0);
|
||||
|
||||
@@ -96,20 +96,16 @@ function md_changed(mde, on_srv) {
|
||||
var md_now = mde.value();
|
||||
var save_btn = QS('.editor-toolbar button.save');
|
||||
|
||||
if (md_now == window.md_saved)
|
||||
save_btn.classList.add('disabled');
|
||||
else
|
||||
save_btn.classList.remove('disabled');
|
||||
|
||||
clmod(save_btn, 'disabled', md_now == window.md_saved);
|
||||
set_jumpto();
|
||||
}
|
||||
|
||||
function save(mde) {
|
||||
var save_btn = QS('.editor-toolbar button.save');
|
||||
if (save_btn.classList.contains('disabled'))
|
||||
if (clgot(save_btn, 'disabled'))
|
||||
return toast.inf(2, 'no changes');
|
||||
|
||||
var force = save_btn.classList.contains('force-save');
|
||||
var force = clgot(save_btn, 'force-save');
|
||||
function save2() {
|
||||
var txt = mde.value();
|
||||
|
||||
@@ -153,8 +149,8 @@ function save_cb() {
|
||||
}
|
||||
|
||||
if (!r.ok) {
|
||||
if (!this.btn.classList.contains('force-save')) {
|
||||
this.btn.classList.add('force-save');
|
||||
if (!clgot(this.btn, 'force-save')) {
|
||||
clmod(this.btn, 'force-save', 1);
|
||||
var msg = [
|
||||
'This file has been modified since you started editing it!\n',
|
||||
'if you really want to overwrite, press save again.\n',
|
||||
@@ -170,7 +166,7 @@ function save_cb() {
|
||||
return toast.err(0, 'Error! Save failed. Maybe this JSON explains why:\n\n' + this.responseText);
|
||||
}
|
||||
|
||||
this.btn.classList.remove('force-save');
|
||||
clmod(this.btn, 'force-save');
|
||||
//alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512);
|
||||
|
||||
// download the saved doc from the server and compare
|
||||
|
||||
@@ -11,6 +11,7 @@ html {
|
||||
background: #333;
|
||||
font-family: sans-serif;
|
||||
text-shadow: 1px 1px 0px #000;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
html, body {
|
||||
margin: 0;
|
||||
@@ -25,4 +26,4 @@ pre {
|
||||
}
|
||||
a {
|
||||
color: #fc5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ html, body, #wrap {
|
||||
background: #f7f7f7;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
html {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
#wrap {
|
||||
max-width: 40em;
|
||||
margin: 2em auto;
|
||||
@@ -73,4 +76,4 @@ html.dark input {
|
||||
}
|
||||
html.dark .num {
|
||||
border-color: #777;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
html {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
#tt, #toast {
|
||||
position: fixed;
|
||||
max-width: 34em;
|
||||
max-width: min(34em, 90%);
|
||||
max-width: min(34em, calc(100% - 7em));
|
||||
background: #222;
|
||||
border: 0 solid #777;
|
||||
box-shadow: 0 .2em .5em #222;
|
||||
@@ -8,8 +13,9 @@
|
||||
z-index: 9001;
|
||||
}
|
||||
#tt {
|
||||
max-width: min(34em, calc(100% - 3.3em));
|
||||
overflow: hidden;
|
||||
margin-top: 1em;
|
||||
margin: .7em 0;
|
||||
padding: 0 1.3em;
|
||||
height: 0;
|
||||
opacity: .1;
|
||||
@@ -73,7 +79,7 @@
|
||||
background: #8e4;
|
||||
}
|
||||
#toast.warn {
|
||||
background: #970;
|
||||
background: #960;
|
||||
border-color: #fc0;
|
||||
}
|
||||
#toast.warn #toastc {
|
||||
@@ -165,6 +171,16 @@ html.light #tt em {
|
||||
min-width: 30em;
|
||||
}
|
||||
}
|
||||
#modalc li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
#modalc h6 {
|
||||
font-size: 1.3em;
|
||||
border-bottom: 1px solid #999;
|
||||
margin: 0;
|
||||
padding: .3em;
|
||||
text-align: center;
|
||||
}
|
||||
#modalb {
|
||||
position: sticky;
|
||||
text-align: right;
|
||||
@@ -200,4 +216,4 @@ html.light #tt em {
|
||||
}
|
||||
#modali:focus {
|
||||
border-color: #06d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,15 @@ function U2pvis(act, btns) {
|
||||
r.tail = -1;
|
||||
r.wsz = 3;
|
||||
|
||||
var markup = {
|
||||
'404': '<span class="err">404</span>',
|
||||
'ERROR': '<span class="err">ERROR</span>',
|
||||
'OS-error': '<span class="err">OS-error</span>',
|
||||
'found': '<span class="inf">found</span>',
|
||||
'YOLO': '<span class="inf">YOLO</span>',
|
||||
'done': '<span class="ok">done</span>',
|
||||
};
|
||||
|
||||
r.addfile = function (entry, sz, draw) {
|
||||
r.tab.push({
|
||||
"hn": entry[0],
|
||||
@@ -185,7 +194,7 @@ function U2pvis(act, btns) {
|
||||
return;
|
||||
|
||||
var obj = ebi('f{0}{1}'.format(nfile, field.slice(1)));
|
||||
obj.innerHTML = html;
|
||||
obj.innerHTML = field == 'ht' ? (markup[html] || html) : html;
|
||||
if (field == 'hp') {
|
||||
obj.style.color = '';
|
||||
obj.style.background = '';
|
||||
@@ -227,7 +236,7 @@ function U2pvis(act, btns) {
|
||||
p = r.perc(nb, 0, fobj.size, fobj.t_hashing);
|
||||
|
||||
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
||||
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
||||
f2f(p[0], 2), p[1], f2f(p[2], 2)
|
||||
);
|
||||
if (!r.is_act(fo.in))
|
||||
return;
|
||||
@@ -249,7 +258,7 @@ function U2pvis(act, btns) {
|
||||
|
||||
var p = r.perc(fo.bd, fo.bd0, fo.bt, fobj.t_uploading);
|
||||
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
||||
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
||||
f2f(p[0], 2), p[1], f2f(p[2], 2)
|
||||
);
|
||||
|
||||
if (!r.is_act(fo.in))
|
||||
@@ -424,7 +433,7 @@ function U2pvis(act, btns) {
|
||||
td1 = '<td id="f' + nfile,
|
||||
td = '</td>' + td1,
|
||||
ret = td1 + 'n">' + row.hn +
|
||||
td + 't">' + row.ht +
|
||||
td + 't">' + (markup[row.ht] || row.ht) +
|
||||
td + 'p" class="prog">' + row.hp + '</td>';
|
||||
|
||||
if (as_html)
|
||||
@@ -511,7 +520,7 @@ function up2k_init(subtle) {
|
||||
import_js('/.cpr/deps/' + fn, unmodal);
|
||||
|
||||
if (is_https)
|
||||
ebi('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500kB/s at best';
|
||||
ebi('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500 KiB/s at best';
|
||||
else
|
||||
ebi('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance <span style="color:#' +
|
||||
(sha_js == 'ac' ? 'c84">(expecting 20' : '8a5">(but dont worry too much, expect 100') + ' MiB/s)</span>';
|
||||
@@ -587,8 +596,15 @@ function up2k_init(subtle) {
|
||||
"upload": []
|
||||
},
|
||||
"bytes": {
|
||||
"total": 0,
|
||||
"hashed": 0,
|
||||
"uploaded": 0
|
||||
"uploaded": 0,
|
||||
"finished": 0
|
||||
},
|
||||
"time": {
|
||||
"hashing": 0,
|
||||
"uploading": 0,
|
||||
"busy": 0
|
||||
}
|
||||
};
|
||||
|
||||
@@ -709,11 +725,11 @@ function up2k_init(subtle) {
|
||||
match = false;
|
||||
|
||||
if (match) {
|
||||
var msg = ['directory iterator got stuck on the following {0} items; good chance your browser is about to spinlock:'.format(missing.length)];
|
||||
var msg = ['directory iterator got stuck on the following {0} items; good chance your browser is about to spinlock:<ul>'.format(missing.length)];
|
||||
for (var a = 0; a < Math.min(20, missing.length); a++)
|
||||
msg.push(missing[a]);
|
||||
msg.push('<li>' + esc(missing[a]) + '</li>');
|
||||
|
||||
return modal.alert(msg.join('\n-- '), function () {
|
||||
return modal.alert(msg.join('') + '</ul>', function () {
|
||||
read_dirs(rd, [], [], good, bad, spins);
|
||||
});
|
||||
}
|
||||
@@ -784,12 +800,12 @@ function up2k_init(subtle) {
|
||||
});
|
||||
}
|
||||
|
||||
var msg = ['upload these ' + good_files.length + ' files?'];
|
||||
var msg = ['{0} these {1} files?<ul>'.format(fsearch ? 'search' : 'upload', good_files.length)];
|
||||
for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++)
|
||||
msg.push(good_files[a][1]);
|
||||
msg.push('<li>' + esc(good_files[a][1]) + '</li>');
|
||||
|
||||
if (ask_up && !fsearch)
|
||||
return modal.confirm(msg.join('\n'), function () { up_them(good_files); }, null);
|
||||
return modal.confirm(msg.join('') + '</ul>', function () { up_them(good_files); }, null);
|
||||
|
||||
up_them(good_files);
|
||||
}
|
||||
@@ -805,13 +821,13 @@ function up2k_init(subtle) {
|
||||
for (var a = 0; a < good_files.length; a++) {
|
||||
var fobj = good_files[a][0],
|
||||
name = good_files[a][1],
|
||||
fdir = '',
|
||||
fdir = evpath,
|
||||
now = Date.now(),
|
||||
lmod = fobj.lastModified || now,
|
||||
ofs = name.lastIndexOf('/') + 1;
|
||||
|
||||
if (ofs) {
|
||||
fdir = name.slice(0, ofs);
|
||||
fdir += url_enc(name.slice(0, ofs));
|
||||
name = name.slice(ofs);
|
||||
}
|
||||
|
||||
@@ -828,6 +844,9 @@ function up2k_init(subtle) {
|
||||
},
|
||||
key = entry.name + '\n' + entry.size;
|
||||
|
||||
if (fsearch)
|
||||
entry.srch = 1;
|
||||
|
||||
if (seen[key])
|
||||
continue;
|
||||
|
||||
@@ -840,6 +859,7 @@ function up2k_init(subtle) {
|
||||
''
|
||||
], fobj.size, draw_each);
|
||||
|
||||
st.bytes.total += fobj.size;
|
||||
st.files.push(entry);
|
||||
if (turbo)
|
||||
push_t(st.todo.head, entry);
|
||||
@@ -878,6 +898,75 @@ function up2k_init(subtle) {
|
||||
}
|
||||
ebi('u2cleanup').onclick = u2cleanup;
|
||||
|
||||
var etaref = 0, etaskip = 0;
|
||||
function etafun() {
|
||||
var nhash = st.busy.head.length + st.busy.hash.length + st.todo.head.length + st.todo.hash.length,
|
||||
nsend = st.busy.upload.length + st.todo.upload.length,
|
||||
now = Date.now(),
|
||||
td = (now - (etaref || now)) / 1000.0;
|
||||
|
||||
etaref = now;
|
||||
//ebi('acc_info').innerHTML = f2f(st.time.busy, 1) + ' ' + f2f(now / 1000, 1);
|
||||
|
||||
if (!nhash)
|
||||
ebi('u2etah').innerHTML = 'Done ({0}, {1} files)'.format(humansize(st.bytes.hashed), pvis.ctr["ok"] + pvis.ctr["ng"]);
|
||||
|
||||
if (!nsend && !nhash)
|
||||
ebi('u2etau').innerHTML = ebi('u2etat').innerHTML = (
|
||||
'Done ({0}, {1} files)'.format(humansize(st.bytes.uploaded), pvis.ctr["ok"] + pvis.ctr["ng"]));
|
||||
|
||||
if (!st.busy.hash.length && !hashing_permitted())
|
||||
nhash = 0;
|
||||
|
||||
if (!parallel_uploads || !(nhash + nsend) || (flag && flag.owner && !flag.ours))
|
||||
return;
|
||||
|
||||
var t = [];
|
||||
if (nhash) {
|
||||
st.time.hashing += td;
|
||||
t.push(['u2etah', st.bytes.hashed, st.bytes.hashed, st.time.hashing]);
|
||||
if (fsearch)
|
||||
t.push(['u2etat', st.bytes.hashed, st.bytes.hashed, st.time.hashing]);
|
||||
}
|
||||
if (nsend) {
|
||||
st.time.uploading += td;
|
||||
t.push(['u2etau', st.bytes.uploaded, st.bytes.finished, st.time.uploading]);
|
||||
}
|
||||
if ((nhash || nsend) && !fsearch) {
|
||||
if (!st.bytes.finished) {
|
||||
ebi('u2etat').innerHTML = '(preparing to upload)';
|
||||
}
|
||||
else {
|
||||
st.time.busy += td;
|
||||
t.push(['u2etat', st.bytes.finished, st.bytes.finished, st.time.busy]);
|
||||
}
|
||||
}
|
||||
for (var a = 0; a < t.length; a++) {
|
||||
var rem = st.bytes.total - t[a][2],
|
||||
bps = t[a][1] / t[a][3],
|
||||
eta = Math.floor(rem / bps);
|
||||
|
||||
if (t[a][1] < 1024 || t[a][3] < 0.1) {
|
||||
ebi(t[a][0]).innerHTML = '(preparing to upload)';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (etaskip)
|
||||
continue;
|
||||
|
||||
if (eta < 60 * 60 * 24)
|
||||
try {
|
||||
eta = /.*(..:..:..).*/.exec(new Date(eta * 1000).toUTCString())[1];
|
||||
}
|
||||
catch (ex) { }
|
||||
|
||||
ebi(t[a][0]).innerHTML = '{0}, {1}/s, {2}'.format(
|
||||
humansize(rem), humansize(bps, 1), eta);
|
||||
}
|
||||
if (++etaskip > 2)
|
||||
etaskip = 0;
|
||||
}
|
||||
|
||||
/////
|
||||
////
|
||||
/// actuator
|
||||
@@ -916,8 +1005,11 @@ function up2k_init(subtle) {
|
||||
}
|
||||
|
||||
function hashing_permitted() {
|
||||
if (!parallel_uploads)
|
||||
return false;
|
||||
|
||||
if (multitask) {
|
||||
var ahead = st.bytes.hashed - st.bytes.uploaded;
|
||||
var ahead = st.bytes.hashed - st.bytes.finished;
|
||||
return ahead < 1024 * 1024 * 1024 * 4 &&
|
||||
st.todo.handshake.length + st.busy.handshake.length < 16;
|
||||
}
|
||||
@@ -961,11 +1053,40 @@ function up2k_init(subtle) {
|
||||
|
||||
window[(is_busy ? "add" : "remove") +
|
||||
"EventListener"]("beforeunload", warn_uploader_busy);
|
||||
|
||||
if (!is_busy) {
|
||||
var k = fsearch ? 'searches' : 'uploads',
|
||||
ks = fsearch ? 'Search' : 'Upload',
|
||||
tok = fsearch ? 'successful (found on server)' : 'completed successfully',
|
||||
tng = fsearch ? 'failed (NOT found on server)' : 'failed, sorry',
|
||||
ok = pvis.ctr["ok"],
|
||||
ng = pvis.ctr["ng"],
|
||||
t = ask_up ? 0 : 10;
|
||||
|
||||
if (ok && ng)
|
||||
toast.warn(t, 'Finished, but some {0} failed:\n{1} {2},\n{3} {4}'.format(k, ok, tok, ng, tng));
|
||||
else if (ok > 1)
|
||||
toast.ok(t, 'All {1} {0} {2}'.format(k, ok, tok));
|
||||
else if (ok)
|
||||
toast.ok(t, '{0} {1}'.format(ks, tok));
|
||||
else if (ng > 1)
|
||||
toast.err(t, 'All {1} {0} {2}'.format(k, ng, tng));
|
||||
else if (ng)
|
||||
toast.err(t, '{0} {1}'.format(ks, tng));
|
||||
|
||||
timer.rm(etafun);
|
||||
}
|
||||
else {
|
||||
timer.add(etafun, false);
|
||||
ebi('u2etas').style.textAlign = 'left';
|
||||
}
|
||||
etafun();
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
if (is_busy) {
|
||||
flag.take(now);
|
||||
ebi('u2flagblock').style.display = flag.ours ? '' : 'block';
|
||||
if (!flag.ours)
|
||||
return defer();
|
||||
}
|
||||
@@ -1101,7 +1222,6 @@ function up2k_init(subtle) {
|
||||
function exec_hash() {
|
||||
var t = st.todo.hash.shift();
|
||||
st.busy.hash.push(t);
|
||||
st.bytes.hashed += t.size;
|
||||
t.bytes_uploaded = 0;
|
||||
|
||||
var bpend = 0,
|
||||
@@ -1127,6 +1247,7 @@ function up2k_init(subtle) {
|
||||
cdr = t.size;
|
||||
|
||||
bpend += cdr - car;
|
||||
st.bytes.hashed += cdr - car;
|
||||
|
||||
function orz(e) {
|
||||
if (!min_filebuf && nch == 1) {
|
||||
@@ -1157,11 +1278,11 @@ function up2k_init(subtle) {
|
||||
if (handled) {
|
||||
pvis.move(t.n, 'ng');
|
||||
apop(st.busy.hash, t);
|
||||
st.bytes.uploaded += t.size;
|
||||
st.bytes.finished += t.size;
|
||||
return tasker();
|
||||
}
|
||||
|
||||
toast.err(0, 'y o u b r o k e i t\nfile: ' + t.name + '\nerror: ' + err);
|
||||
toast.err(0, 'y o u b r o k e i t\nfile: ' + esc(t.name + '') + '\nerror: ' + err);
|
||||
};
|
||||
reader.readAsArrayBuffer(
|
||||
bobslice.call(t.fobj, car, cdr));
|
||||
@@ -1249,21 +1370,22 @@ function up2k_init(subtle) {
|
||||
}
|
||||
}
|
||||
apop(st.busy.head, t);
|
||||
if (!ok)
|
||||
if (!ok && !t.srch)
|
||||
return push_t(st.todo.hash, t);
|
||||
|
||||
t.done = true;
|
||||
st.bytes.hashed += t.size;
|
||||
st.bytes.uploaded += t.size;
|
||||
pvis.seth(t.n, 1, 'YOLO');
|
||||
st.bytes.finished += t.size;
|
||||
pvis.move(t.n, 'bz');
|
||||
pvis.seth(t.n, 1, ok ? 'YOLO' : '404');
|
||||
pvis.seth(t.n, 2, "turbo'd");
|
||||
pvis.move(t.n, 'ok');
|
||||
pvis.move(t.n, ok ? 'ok' : 'ng');
|
||||
};
|
||||
xhr.onload = function (e) {
|
||||
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||
};
|
||||
|
||||
xhr.open('HEAD', t.purl + t.name, true);
|
||||
xhr.open('HEAD', t.purl + uricom_enc(t.name) + '?raw', true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
@@ -1334,18 +1456,19 @@ function up2k_init(subtle) {
|
||||
pvis.seth(t.n, 1, smsg);
|
||||
pvis.move(t.n, smsg == '404' ? 'ng' : 'ok');
|
||||
apop(st.busy.handshake, t);
|
||||
st.bytes.uploaded += t.size;
|
||||
st.bytes.finished += t.size;
|
||||
t.done = true;
|
||||
tasker();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.purl !== t.purl || response.name !== t.name) {
|
||||
var rsp_purl = url_enc(response.purl);
|
||||
if (rsp_purl !== t.purl || response.name !== t.name) {
|
||||
// server renamed us (file exists / path restrictions)
|
||||
console.log("server-rename [" + t.purl + "] [" + t.name + "] to [" + response.purl + "] [" + response.name + "]");
|
||||
t.purl = response.purl;
|
||||
console.log("server-rename [" + t.purl + "] [" + t.name + "] to [" + rsp_purl + "] [" + response.name + "]");
|
||||
t.purl = rsp_purl;
|
||||
t.name = response.name;
|
||||
pvis.seth(t.n, 0, linksplit(t.purl + t.name).join(' '));
|
||||
pvis.seth(t.n, 0, linksplit(uricom_dec(t.purl)[0] + t.name).join(' '));
|
||||
}
|
||||
|
||||
var chunksize = get_chunksize(t.size),
|
||||
@@ -1374,7 +1497,7 @@ function up2k_init(subtle) {
|
||||
pvis.prog(t, 0, cbd[0]);
|
||||
|
||||
var done = true,
|
||||
msg = '🎷🐛';
|
||||
msg = 'done';
|
||||
|
||||
if (t.postlist.length) {
|
||||
var arr = st.todo.upload,
|
||||
@@ -1401,12 +1524,12 @@ function up2k_init(subtle) {
|
||||
|
||||
if (done) {
|
||||
t.done = true;
|
||||
st.bytes.uploaded += t.size - t.bytes_uploaded;
|
||||
st.bytes.finished += t.size - t.bytes_uploaded;
|
||||
var spd1 = (t.size / ((t.t_hashed - t.t_hashing) / 1000.)) / (1024 * 1024.),
|
||||
spd2 = (t.size / ((t.t_uploaded - t.t_uploading) / 1000.)) / (1024 * 1024.);
|
||||
|
||||
pvis.seth(t.n, 2, 'hash {0}, up {1} MB/s'.format(
|
||||
spd1.toFixed(2), spd2.toFixed(2)));
|
||||
f2f(spd1, 2), isNaN(spd2) ? '--' : f2f(spd2, 2)));
|
||||
|
||||
pvis.move(t.n, 'ok');
|
||||
}
|
||||
@@ -1434,7 +1557,7 @@ function up2k_init(subtle) {
|
||||
return;
|
||||
}
|
||||
|
||||
st.bytes.uploaded += t.size;
|
||||
st.bytes.finished += t.size;
|
||||
if (rsp.indexOf('partial upload exists') !== -1 ||
|
||||
rsp.indexOf('file already exists') !== -1) {
|
||||
err = rsp;
|
||||
@@ -1469,7 +1592,7 @@ function up2k_init(subtle) {
|
||||
"lmod": t.lmod,
|
||||
"hash": t.hash
|
||||
};
|
||||
if (fsearch)
|
||||
if (t.srch)
|
||||
req.srch = 1;
|
||||
|
||||
xhr.open('POST', t.purl, true);
|
||||
@@ -1505,6 +1628,7 @@ function up2k_init(subtle) {
|
||||
var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
|
||||
if (xhr.status == 200) {
|
||||
pvis.prog(t, npart, cdr - car);
|
||||
st.bytes.finished += cdr - car;
|
||||
st.bytes.uploaded += cdr - car;
|
||||
t.bytes_uploaded += cdr - car;
|
||||
}
|
||||
@@ -1572,6 +1696,7 @@ function up2k_init(subtle) {
|
||||
parent.appendChild(btn);
|
||||
ebi('u2conf').setAttribute('class', wide ? 'has_btn' : '');
|
||||
ebi('u2cards').setAttribute('class', wide ? 'w' : '');
|
||||
ebi('u2etaw').setAttribute('class', wide ? 'w' : '');
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', onresize);
|
||||
@@ -1659,7 +1784,7 @@ function up2k_init(subtle) {
|
||||
|
||||
function draw_turbo() {
|
||||
var msgu = '<p class="warn">WARNING: turbo enabled, <span> client may not detect and resume incomplete uploads; see turbo-button tooltip</span></p>',
|
||||
msgs = '<p class="warn">WARNING: turbo enabled, <span> search may give false-positives; see turbo-button tooltip</span></p>',
|
||||
msgs = '<p class="warn">WARNING: turbo enabled, <span> search results can be incorrect; see turbo-button tooltip</span></p>',
|
||||
msg = fsearch ? msgs : msgu,
|
||||
omsg = fsearch ? msgu : msgs,
|
||||
html = ebi('u2foot').innerHTML,
|
||||
@@ -1703,11 +1828,10 @@ function up2k_init(subtle) {
|
||||
catch (ex) { }
|
||||
|
||||
try {
|
||||
var fun = fsearch ? 'add' : 'remove',
|
||||
ico = fsearch ? '🔎' : '🚀',
|
||||
var ico = fsearch ? '🔎' : '🚀',
|
||||
desc = fsearch ? 'Search' : 'Upload';
|
||||
|
||||
ebi('op_up2k').classList[fun]('srch');
|
||||
clmod(ebi('op_up2k'), 'srch', fsearch);
|
||||
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
||||
}
|
||||
catch (ex) { }
|
||||
@@ -1733,6 +1857,9 @@ function up2k_init(subtle) {
|
||||
}
|
||||
}
|
||||
else if (!flag_en && flag) {
|
||||
if (flag.ours)
|
||||
flag.give();
|
||||
|
||||
flag.ch.close();
|
||||
flag = false;
|
||||
}
|
||||
@@ -1763,9 +1890,9 @@ function up2k_init(subtle) {
|
||||
if (o)
|
||||
o.addEventListener('click', tgl_fsearch, false);
|
||||
|
||||
var nodes = ebi('u2conf').getElementsByTagName('a');
|
||||
for (var a = nodes.length - 1; a >= 0; a--)
|
||||
nodes[a].addEventListener('touchend', nop, false);
|
||||
ebi('u2etas').onclick = function () {
|
||||
clmod(ebi('u2etas'), 'o', 't');
|
||||
};
|
||||
|
||||
set_fsearch();
|
||||
bumpthread({ "target": 1 });
|
||||
|
||||
@@ -123,11 +123,18 @@ if (!String.startsWith) {
|
||||
return this.substring(i, i + s.length) === s;
|
||||
};
|
||||
}
|
||||
if (!Element.prototype.matches) {
|
||||
Element.prototype.matches =
|
||||
Element.prototype.oMatchesSelector ||
|
||||
Element.prototype.msMatchesSelector ||
|
||||
Element.prototype.mozMatchesSelector ||
|
||||
Element.prototype.webkitMatchesSelector;
|
||||
}
|
||||
if (!Element.prototype.closest) {
|
||||
Element.prototype.closest = function (s) {
|
||||
var el = this;
|
||||
do {
|
||||
if (el.msMatchesSelector(s)) return el;
|
||||
if (el.matches(s)) return el;
|
||||
el = el.parentElement || el.parentNode;
|
||||
} while (el !== null && el.nodeType === 1);
|
||||
}
|
||||
@@ -170,12 +177,37 @@ function crc32(str) {
|
||||
}
|
||||
|
||||
|
||||
function clmod(obj, cls, add) {
|
||||
var re = new RegExp('\\s*\\b' + cls + '\\s*\\b', 'g');
|
||||
if (add == 't')
|
||||
add = !re.test(obj.className);
|
||||
function clmod(el, cls, add) {
|
||||
if (el.classList) {
|
||||
var have = el.classList.contains(cls);
|
||||
if (add == 't')
|
||||
add = !have;
|
||||
|
||||
obj.className = obj.className.replace(re, ' ') + (add ? ' ' + cls : '');
|
||||
if (add != have)
|
||||
el.classList[add ? 'add' : 'remove'](cls);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var re = new RegExp('\\s*\\b' + cls + '\\s*\\b', 'g'),
|
||||
n1 = el.className;
|
||||
|
||||
if (add == 't')
|
||||
add = !re.test(n1);
|
||||
|
||||
var n2 = n1.replace(re, ' ') + (add ? ' ' + cls : '');
|
||||
|
||||
if (n1 != n2)
|
||||
el.className = n2;
|
||||
}
|
||||
|
||||
|
||||
function clgot(el, cls) {
|
||||
if (el.classList)
|
||||
return el.classList.contains(cls);
|
||||
|
||||
var lst = (el.getAttribute('class') + '').split(/ /g);
|
||||
return has(lst, cls);
|
||||
}
|
||||
|
||||
|
||||
@@ -308,6 +340,16 @@ function uricom_enc(txt, do_fb_enc) {
|
||||
}
|
||||
}
|
||||
|
||||
function url_enc(txt) {
|
||||
var parts = txt.split('/'),
|
||||
ret = [];
|
||||
|
||||
for (var a = 0; a < parts.length; a++)
|
||||
ret.push(uricom_enc(parts[a]));
|
||||
|
||||
return ret.join('/');
|
||||
}
|
||||
|
||||
|
||||
function uricom_dec(txt) {
|
||||
try {
|
||||
@@ -320,10 +362,12 @@ function uricom_dec(txt) {
|
||||
}
|
||||
|
||||
|
||||
function uricom_adec(arr) {
|
||||
function uricom_adec(arr, li) {
|
||||
var ret = [];
|
||||
for (var a = 0; a < arr.length; a++)
|
||||
ret.push(uricom_dec(arr[a])[0]);
|
||||
for (var a = 0; a < arr.length; a++) {
|
||||
var txt = uricom_dec(arr[a])[0];
|
||||
ret.push(li ? '<li>' + esc(txt) + '</li>' : txt);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -368,6 +412,28 @@ function s2ms(s) {
|
||||
}
|
||||
|
||||
|
||||
function f2f(val, nd) {
|
||||
// 10.toFixed(1) returns 10.00 for certain values of 10
|
||||
val = (val * Math.pow(10, nd)).toFixed(0).split('.')[0];
|
||||
return nd ? (val.slice(0, -nd) || '0') + '.' + val.slice(-nd) : val;
|
||||
}
|
||||
|
||||
|
||||
function humansize(b, terse) {
|
||||
var i = 0, u = terse ? ['B', 'K', 'M', 'G'] : ['B', 'KB', 'MB', 'GB'];
|
||||
while (b >= 1000 && i < u.length) {
|
||||
b /= 1024;
|
||||
i += 1;
|
||||
}
|
||||
return f2f(b, b >= 100 ? 0 : b >= 10 ? 1 : 2) + ' ' + u[i];
|
||||
}
|
||||
|
||||
|
||||
function clamp(v, a, b) {
|
||||
return Math.min(Math.max(v, a), b);
|
||||
}
|
||||
|
||||
|
||||
function has(haystack, needle) {
|
||||
for (var a = 0; a < haystack.length; a++)
|
||||
if (haystack[a] == needle)
|
||||
@@ -526,6 +592,8 @@ var tt = (function () {
|
||||
r.skip = false;
|
||||
return;
|
||||
}
|
||||
if (QS('body.bbox-open'))
|
||||
return;
|
||||
|
||||
var cfg = sread('tooltips');
|
||||
if (cfg !== null && cfg != '1')
|
||||
@@ -538,28 +606,39 @@ var tt = (function () {
|
||||
r.el = this;
|
||||
var pos = this.getBoundingClientRect(),
|
||||
dir = this.getAttribute('ttd') || '',
|
||||
left = pos.left < window.innerWidth / 2,
|
||||
top = pos.top < window.innerHeight / 2,
|
||||
big = this.className.indexOf(' ttb') !== -1;
|
||||
|
||||
if (dir.indexOf('u') + 1) top = false;
|
||||
if (dir.indexOf('d') + 1) top = true;
|
||||
if (dir.indexOf('l') + 1) left = false;
|
||||
if (dir.indexOf('r') + 1) left = true;
|
||||
|
||||
clmod(r.tt, 'b', big);
|
||||
r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
|
||||
r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
|
||||
r.tt.style.left = left ? pos.left + 'px' : 'auto';
|
||||
r.tt.style.right = left ? 'auto' : (window.innerWidth - pos.right) + 'px';
|
||||
r.tt.style.left = '0';
|
||||
r.tt.style.top = '0';
|
||||
|
||||
r.tt.innerHTML = msg.replace(/\$N/g, "<br />");
|
||||
var tw = r.tt.offsetWidth,
|
||||
x = pos.left + (pos.right - pos.left) / 2 - tw / 2;
|
||||
|
||||
if (x < 0)
|
||||
x = 8;
|
||||
|
||||
if (x + tw >= window.innerWidth - 8) {
|
||||
x = window.innerWidth - tw - 8;
|
||||
}
|
||||
|
||||
r.tt.style.left = x + 'px';
|
||||
r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
|
||||
r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
|
||||
|
||||
r.el.addEventListener('mouseleave', r.hide);
|
||||
window.addEventListener('scroll', r.hide);
|
||||
clmod(r.tt, 'show', 1);
|
||||
};
|
||||
|
||||
r.hide = function (e) {
|
||||
ev(e);
|
||||
window.removeEventListener('scroll', r.hide);
|
||||
clmod(r.tt, 'show');
|
||||
if (r.el)
|
||||
r.el.removeEventListener('mouseleave', r.hide);
|
||||
@@ -567,13 +646,23 @@ var tt = (function () {
|
||||
|
||||
if (is_touch && IPHONE) {
|
||||
var f1 = r.show,
|
||||
f2 = r.hide;
|
||||
f2 = r.hide,
|
||||
q = [];
|
||||
|
||||
// if an onclick-handler creates a new timer,
|
||||
// iOS 13.1.2 delays the entire handler by up to 401ms,
|
||||
// win by using a shared timer instead
|
||||
|
||||
timer.add(function () {
|
||||
while (q.length && Date.now() >= q[0][0])
|
||||
q.shift()[1]();
|
||||
});
|
||||
|
||||
r.show = function () {
|
||||
setTimeout(f1.bind(this), 301);
|
||||
q.push([Date.now() + 100, f1.bind(this)]);
|
||||
};
|
||||
r.hide = function () {
|
||||
setTimeout(f2.bind(this), 301);
|
||||
q.push([Date.now() + 100, f2.bind(this)]);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -629,6 +718,7 @@ var toast = (function () {
|
||||
obj.setAttribute('id', 'toast');
|
||||
document.body.appendChild(obj);
|
||||
r.visible = false;
|
||||
r.txt = null;
|
||||
|
||||
r.hide = function (e) {
|
||||
ev(e);
|
||||
@@ -648,6 +738,7 @@ var toast = (function () {
|
||||
obj.className += ' vis';
|
||||
ebi('toastc').onclick = r.hide;
|
||||
r.visible = true;
|
||||
r.txt = txt;
|
||||
};
|
||||
|
||||
r.ok = function (ms, txt) {
|
||||
@@ -692,11 +783,15 @@ var modal = (function () {
|
||||
a.onclick = ok;
|
||||
|
||||
(ebi('modali') || a).focus();
|
||||
document.addEventListener('focus', onfocus);
|
||||
timer.add(onfocus);
|
||||
};
|
||||
|
||||
r.hide = function () {
|
||||
o.parentNode.removeChild(o);
|
||||
timer.rm(onfocus);
|
||||
document.removeEventListener('focus', onfocus);
|
||||
document.removeEventListener('keydown', onkey);
|
||||
o.parentNode.removeChild(o);
|
||||
r.busy = false;
|
||||
setTimeout(next, 50);
|
||||
};
|
||||
@@ -715,6 +810,17 @@ var modal = (function () {
|
||||
cb_ng(null);
|
||||
}
|
||||
|
||||
function onfocus(e) {
|
||||
var ctr = ebi('modalc');
|
||||
if (!ctr || !ctr.contains || !document.activeElement || ctr.contains(document.activeElement))
|
||||
return;
|
||||
|
||||
setTimeout(function () {
|
||||
ebi('modal-ok').focus();
|
||||
}, 20);
|
||||
ev(e);
|
||||
}
|
||||
|
||||
function onkey(e) {
|
||||
if (e.code == 'Enter') {
|
||||
var a = ebi('modal-ng');
|
||||
|
||||
@@ -1,36 +1,3 @@
|
||||
/* put filetype icons inline with text
|
||||
#ggrid>a>span:before,
|
||||
#ggrid>a>span.dir:before {
|
||||
display: inline;
|
||||
line-height: 0;
|
||||
font-size: 1.7em;
|
||||
margin: -.7em .1em -.5em -.6em;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/* move folder icons top-left */
|
||||
#ggrid>a>span.dir:before {
|
||||
content: initial;
|
||||
}
|
||||
#ggrid>a[href$="/"]:before {
|
||||
content: '📂';
|
||||
}
|
||||
|
||||
|
||||
/* put filetype icons top-left */
|
||||
#ggrid>a:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
padding: .3em 0;
|
||||
margin: -.4em;
|
||||
text-shadow: 0 0 .1em #000;
|
||||
background: linear-gradient(135deg,rgba(255,255,255,0) 50%,rgba(255,255,255,0.2));
|
||||
border-radius: .3em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
|
||||
/* video */
|
||||
#ggrid>a:is(
|
||||
[href$=".mkv"i],
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#u2cleanup, #u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */
|
||||
|
||||
#u2cards /* and the upload progress tabs */
|
||||
#u2cards, #u2etaw /* and the upload progress tabs */
|
||||
|
||||
{display: none !important} /* do it! */
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
/* add some margins because now it's weird */
|
||||
.opview {margin-top: 2.5em}
|
||||
#op_up2k {margin-top: 3em}
|
||||
#op_up2k {margin-top: 6em}
|
||||
|
||||
/* and embiggen the upload button */
|
||||
#u2conf #u2btn, #u2btn {padding:1.5em 0}
|
||||
|
||||
@@ -126,6 +126,10 @@ e=6; s=10; d=~/dev/copyparty/srv/aus; n=1; p=0; e=$((e*60)); rm -rf $d; mkdir $d
|
||||
-v srv/aus:aus:r:ce2dsa:ce2ts:cmtp=fgsfds=bin/mtag/sleep.py
|
||||
sqlite3 .hist/up2k.db 'select * from mt where k="fgsfds" or k="t:mtp"' | tee /dev/stderr | wc -l
|
||||
|
||||
# generate the sine meme
|
||||
for ((f=420;f<1200;f++)); do sz=$(ffmpeg -y -f lavfi -i sine=frequency=$f:duration=2 -vf volume=0.1 -ac 1 -ar 44100 -f s16le /dev/shm/a.wav 2>/dev/null; base64 -w0 </dev/shm/a.wav | gzip -c | wc -c); printf '%d %d\n' $f $sz; done | tee /dev/stderr | sort -nrk2,2
|
||||
ffmpeg -y -f lavfi -i sine=frequency=1050:duration=2 -vf volume=0.1 -ac 1 -ar 44100 /dev/shm/a.wav
|
||||
|
||||
|
||||
##
|
||||
## vscode
|
||||
@@ -157,7 +161,7 @@ brew install python@2
|
||||
pip install virtualenv
|
||||
|
||||
# readme toc
|
||||
cat README.md | awk '!/^#/{next} {lv=length($1);sub(/[^ ]+ /,"");bab=$0;gsub(/ /,"-",bab)} {printf "%" ((lv-1)*4+1) "s [%s](#%s)\n", "*",$0,bab}'
|
||||
cat README.md | awk 'function pr() { if (!h) {return}; if (/^ *[*!#]/||!s) {printf "%s\n",h;h=0;return}; if (/.../) {printf "%s - %s\n",h,$0;h=0}; }; /^#/{s=1;pr()} /^#* *(file indexing|install on android|dev env setup|just the sfx|complete release|optional gpl stuff)|`$/{s=0} /^#/{lv=length($1);sub(/[^ ]+ /,"");bab=$0;gsub(/ /,"-",bab); h=sprintf("%" ((lv-1)*4+1) "s [%s](#%s)", "*",$0,bab);next} !h{next} {sub(/:$/,"")} {pr()}' > toc; grep -E '^## readme toc' -B1000 -A2 <README.md >p1; grep -E '^## quickstart' -B2 -A999999 <README.md >p2; (cat p1; grep quickstart -A1000 <toc; cat p2) >README.md
|
||||
|
||||
# fix firefox phantom breakpoints,
|
||||
# suggestions from bugtracker, doesnt work (debugger is not attachable)
|
||||
|
||||
@@ -3,7 +3,7 @@ WORKDIR /z
|
||||
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
|
||||
ver_hashwasm=4.7.0 \
|
||||
ver_marked=1.1.0 \
|
||||
ver_ogvjs=1.8.0 \
|
||||
ver_ogvjs=1.8.4 \
|
||||
ver_mde=2.14.0 \
|
||||
ver_codemirror=5.59.3 \
|
||||
ver_fontawesome=5.13.0 \
|
||||
@@ -74,23 +74,16 @@ RUN cd hash-wasm \
|
||||
# build ogvjs
|
||||
RUN cd ogvjs-$ver_ogvjs \
|
||||
&& cp -pv \
|
||||
ogv.js \
|
||||
ogv-worker-audio.js \
|
||||
ogv-demuxer-ogg-wasm.js \
|
||||
ogv-demuxer-ogg-wasm.wasm \
|
||||
ogv-demuxer-webm-wasm.js \
|
||||
ogv-demuxer-webm-wasm.wasm \
|
||||
ogv-decoder-audio-opus-wasm.js \
|
||||
ogv-decoder-audio-opus-wasm.wasm \
|
||||
ogv-decoder-audio-vorbis-wasm.js \
|
||||
ogv-decoder-audio-vorbis-wasm.wasm \
|
||||
/z/dist
|
||||
|
||||
# ogv-demuxer-ogg.js \
|
||||
# ogv-demuxer-webm.js \
|
||||
# ogv-decoder-audio-opus.js \
|
||||
# ogv-decoder-audio-vorbis.js \
|
||||
# dynamicaudio.swf \
|
||||
/z/dist \
|
||||
&& cp -pv \
|
||||
ogv-es2017.js /z/dist/ogv.js
|
||||
|
||||
|
||||
# build marked
|
||||
|
||||
@@ -2,7 +2,7 @@ all: $(addsuffix .gz, $(wildcard *.*))
|
||||
|
||||
%.gz: %
|
||||
#brotli -q 11 $<
|
||||
pigz -11 -J 34 -I 573 $<
|
||||
pigz -11 -I 573 $<
|
||||
|
||||
# pigz -11 -J 34 -I 100 -F < $< > $@.first
|
||||
|
||||
|
||||
Reference in New Issue
Block a user