Compare commits

...

69 Commits

Author SHA1 Message Date
ed
f5712d9f25 v0.13.9 2021-08-29 02:24:09 +02:00
ed
161d57bdda v0.13.8 2021-08-29 01:38:06 +02:00
ed
bae0d440bf upgrade ogvjs to 1.8.4 2021-08-29 01:11:44 +02:00
ed
fff052dde1 explain the magic 2021-08-29 00:11:06 +02:00
ed
73b06eaa02 coerce iOS into playing opus in the background 2021-08-29 00:05:14 +02:00
ed
08a8ebed17 minor cleanup 2021-08-28 22:40:59 +02:00
ed
74d07426b3 make tray tab smaller 2021-08-28 22:37:39 +02:00
ed
69a2bba99a fix ogv.js crashing iOS 2021-08-28 22:35:47 +02:00
ed
4d685d78ee v0.13.7 2021-08-28 04:55:06 +02:00
ed
5845ec3f49 nevermind, nailed it 2021-08-28 04:08:22 +02:00
ed
13373426fe alright fine apple you win 2021-08-28 03:44:07 +02:00
ed
8e55551a06 positioning fixes 2021-08-28 03:27:14 +02:00
ed
12a3f0ac31 update the filetype icons example 2021-08-28 02:56:07 +02:00
ed
18e33edc88 hide tooltips on scroll 2021-08-28 02:46:06 +02:00
ed
c72c5ad4ee make the ellipsis more visible 2021-08-28 02:38:31 +02:00
ed
0fbc81ab2f missed some 2021-08-28 02:37:28 +02:00
ed
af0a34cf82 improve iphone fix 2021-08-28 02:11:40 +02:00
ed
b4590c5398 horizontally centered tooltips 2021-08-28 01:49:21 +02:00
ed
f787a66230 that was dumb 2021-08-28 01:47:36 +02:00
ed
b21a99fd62 only tooltip the ellipsed thumbnails 2021-08-28 01:25:27 +02:00
ed
eb16306cde misc cleanup 2021-08-28 00:03:30 +02:00
ed
7bc23687e3 this kinda broke ellipsing, hopefully not too expensive 2021-08-28 00:02:59 +02:00
ed
e1eaa057f2 optimize clmod 2021-08-27 23:58:23 +02:00
ed
97c264ca3e snappy taps 2021-08-27 23:57:46 +02:00
ed
cf848ab1f7 add ellipsing of thumbnail filename, fixes #3 (+ clamp zoom level) 2021-08-27 23:50:09 +02:00
ed
cf83f9b0fd v0.13.6 2021-08-27 00:09:36 +02:00
ed
d98e361083 quick debounce 2021-08-26 23:59:17 +02:00
ed
ce7f5309c7 tweak toast bg 2021-08-26 23:46:04 +02:00
ed
75c485ced7 misc toast rice and html escaping 2021-08-26 23:45:28 +02:00
ed
9c6e2ec012 misc modal rice and html escaping 2021-08-26 23:23:56 +02:00
ed
1a02948a61 prevent text selection on most buttons 2021-08-26 23:01:24 +02:00
ed
8b05ba4ba1 stop counting eta when we don't hold the flag 2021-08-26 22:51:07 +02:00
ed
21e2874cb7 warning when another browser tab holds the flag 2021-08-26 22:50:22 +02:00
ed
360ed5c46c release the up2k flag when disabling it 2021-08-26 22:48:57 +02:00
ed
5099bc365d better eta for fsearch 2021-08-26 22:47:43 +02:00
ed
12986da147 might be useful some time 2021-08-26 22:45:50 +02:00
ed
23e72797bc remove some more ansi escapes on win7 2021-08-26 22:45:36 +02:00
ed
ac7b6f8f55 update turbo hint for fsearch 2021-08-26 20:44:36 +02:00
ed
981b9ff11e more accurate eta 2021-08-26 20:43:52 +02:00
ed
4186906f4c pause hashing as well when parallel uploads is 0 2021-08-26 20:43:27 +02:00
ed
0850d24e0c improve spacing on narrow screens 2021-08-26 20:42:20 +02:00
ed
7ab8334c96 remove debug 2021-08-26 01:16:59 +02:00
ed
a4d7329ab7 revert to fixed MiB/s in upload tab 2021-08-26 01:13:20 +02:00
ed
3f4eae6bce yolo search + show in bz + md search 2021-08-26 00:57:49 +02:00
ed
518cf4be57 set fsearch tag on tasks 2021-08-26 00:54:00 +02:00
ed
71096182be toFixed is busted, workaround 2021-08-26 00:51:35 +02:00
ed
6452e927ea download-eta accuracy + misc ux 2021-08-26 00:40:12 +02:00
ed
bc70cfa6f0 fix tmi 2021-08-25 09:02:34 +02:00
ed
2b6e5ebd2d update minimal-up2k 2021-08-25 08:26:38 +02:00
ed
c761bd799a add pane with total eta for all uploads 2021-08-25 02:06:29 +02:00
ed
2f7c2fdee4 add colors to status column in up2k ui 2021-08-24 00:32:53 +02:00
ed
70a76ec343 add toast on upload/fsearch completion 2021-08-24 00:31:01 +02:00
ed
7c3f64abf2 fix navpane h.scroll bug 2021-08-24 00:29:11 +02:00
ed
f5f38f195c use scp.woff in browser too 2021-08-24 00:28:16 +02:00
ed
7e84f4f015 fence focus inside modals 2021-08-24 00:26:54 +02:00
ed
4802f8cf07 better msg when unposting a deleted file 2021-08-24 00:24:50 +02:00
ed
cc05e67d8f add summaries to readme toc 2021-08-22 17:23:42 +02:00
ed
2b6b174517 the smallest nitpick 2021-08-20 19:25:57 +02:00
ed
a1d05e6e12 folder thumbnail fix 2021-08-20 19:22:25 +02:00
ed
f95ceb6a9b fix toc 2021-08-17 08:54:19 +02:00
ed
8f91b0726d add missing hotkey hint 2021-08-17 00:24:27 +02:00
ed
97807f4383 update screenshots 2021-08-17 00:23:12 +02:00
ed
5f42237f2c v0.13.5 2021-08-16 08:40:26 +02:00
ed
68289cfa54 v0.13.4 2021-08-16 08:18:52 +02:00
ed
42ea30270f up2k-ui: post absolute URLs 2021-08-16 08:16:52 +02:00
ed
ebbbbf3d82 misc old-browser support 2021-08-16 00:22:30 +02:00
ed
27516e2d16 scroll navpane to open folder on load 2021-08-16 00:07:31 +02:00
ed
84bb6f915e fix unpost ui for nonroot volumes 2021-08-16 00:03:05 +02:00
ed
46752f758a fix bup into volumes with upload rules 2021-08-15 23:59:41 +02:00
21 changed files with 878 additions and 378 deletions

215
README.md
View File

@@ -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
![copyparty-browser-fs8](https://user-images.githubusercontent.com/241032/115978054-65106380-a57d-11eb-98f8-59e3dee73557.png)
accessing a copyparty server using a web-browser
![copyparty-browser-fs8](https://user-images.githubusercontent.com/241032/129635359-d6dd9b07-8079-4020-ad77-2bfdb9ebd8d5.png)
## 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
![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/120070302-10836b00-c08a-11eb-8eb4-82004a34c342.png)
press `g` to toggle image/video thumbnails instead of the file listing
![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/129636211-abd20fa2-a953-4366-9423-1c88ebb96ba9.png)
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
![copyparty-zipsel-fs8](https://user-images.githubusercontent.com/241032/116008321-372a2e00-a614-11eb-9a4a-4a1fd9074224.png)
![copyparty-zipsel-fs8](https://user-images.githubusercontent.com/241032/129635374-e5136e01-470a-49b1-a762-848e8a4c9cdc.png)
## 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
![copyparty-upload-fs8](https://user-images.githubusercontent.com/241032/115978061-680b5400-a57d-11eb-9ef6-cbb5f60aeccc.png)
![copyparty-upload-fs8](https://user-images.githubusercontent.com/241032/129635371-48fc54ca-fa91-48e3-9b1d-ba413e4b68cb.png)
**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
![copyparty-fsearch-fs8](https://user-images.githubusercontent.com/241032/116008320-36919780-a614-11eb-803f-04162326a700.png)
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
![copyparty-fsearch-fs8](https://user-images.githubusercontent.com/241032/129635361-c79286f0-b8f1-440e-aaf4-6e929428fac9.png)
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
![copyparty-unpost-fs8](https://user-images.githubusercontent.com/241032/129635368-3afa6634-c20f-418c-90dc-ec411f3b3897.png)
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
![batch-rename-fs8](https://user-images.githubusercontent.com/241032/128434204-eb136680-3c07-4ec7-92e0-ae86af20c241.png)
select some files and press F2 to bring up the rename UI
![batch-rename-fs8](https://user-images.githubusercontent.com/241032/128434204-eb136680-3c07-4ec7-92e0-ae86af20c241.png)
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
![copyparty-md-read-fs8](https://user-images.githubusercontent.com/241032/115978057-66419080-a57d-11eb-8539-d2be843991aa.png)
* 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
![copyparty-search-fs8](https://user-images.githubusercontent.com/241032/115978060-6772bd80-a57d-11eb-81d3-174e869b72c3.png)
search by size, date, path/name, mp3-tags, ...
![copyparty-search-fs8](https://user-images.githubusercontent.com/241032/129635365-c0ff2a9f-0ee5-4fc3-8bb6-006033cf67b8.png)
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
![copyparty-ie4-fs8](https://user-images.githubusercontent.com/241032/118192791-fb31fe00-b446-11eb-9647-898ea8efc1f7.png)
`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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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])

View File

@@ -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

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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 &lt;small&gt;(then paste somewhere else)&lt;/small&gt;$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 &lt;em&gt;hashing&lt;/em&gt; speed, and estimated time until finish">(no uploads are queued yet)</span><br />\n' +
' send: <span id="u2etau" tt="average &lt;em&gt;upload&lt;/em&gt; 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 &lt;em&gt;total&lt;/em&gt; 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">&ndash;</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">&nbsp;</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> &nbsp; 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">&ndash;</a> ' +
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a> &nbsp; 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)">&ndash;</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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 = '&#x1f3b7;&#x1f41b;';
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>&nbsp;client may not detect and resume incomplete uploads; see turbo-button tooltip</span></p>',
msgs = '<p class="warn">WARNING: turbo enabled, <span>&nbsp;search may give false-positives; see turbo-button tooltip</span></p>',
msgs = '<p class="warn">WARNING: turbo enabled, <span>&nbsp;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 });

View File

@@ -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');

View File

@@ -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],

View File

@@ -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}

View File

@@ -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)

View File

@@ -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

View File

@@ -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