mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-24 16:56:16 -04:00
Merge pull request #39 from schlagmichdoch/enable_sending_from_cli
Closes #34
This commit is contained in:
commit
12a2fc1b0a
10 changed files with 494 additions and 114 deletions
10
README.md
10
README.md
|
@ -47,11 +47,11 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop)
|
|||
* On iOS and Android the devices share menu is opened instead of downloading the files
|
||||
* Multiple files are transferred at once with an overall progress indicator
|
||||
|
||||
### Share Files Directly From Share / Context Menu
|
||||
* [Share files directly from context menu on Windows](/docs/how-to.md#share-files-directly-from-context-menu-on-windows)
|
||||
* [Share directly from share menu on iOS](/docs/how-to.md#share-directly-from-share-menu-on-ios)
|
||||
* [Share directly from share menu on Android](/docs/how-to.md#share-directly-from-share-menu-on-android)
|
||||
|
||||
### Send Files or Text Directly From Share Menu, Context Menu or CLI
|
||||
* [Send files directly from context menu on Windows](/docs/how-to.md#send-files-directly-from-context-menu-on-windows)
|
||||
* [Send directly from share menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios)
|
||||
* [Send directly from share menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android)
|
||||
* [Send directly via command-line interface](/docs/how-to.md#send-directly-via-command-line-interface)
|
||||
|
||||
### Other changes
|
||||
* [Paste Mode](https://github.com/RobinLinus/snapdrop/pull/534)
|
||||
|
|
14
docs/faq.md
14
docs/faq.md
|
@ -33,12 +33,16 @@ iOS Shortcuts to the win:
|
|||
I created a simple iOS shortcut that takes your photos and saves them to your gallery:
|
||||
https://routinehub.co/shortcut/13988/
|
||||
|
||||
### Is it possible to share files directly from the context / share menu?
|
||||
Yes it finally is!
|
||||
* [Share files directly from context menu on Windows](/docs/how-to.md#share-files-directly-from-context-menu-on-windows)
|
||||
* [Share directly from share menu on iOS](/docs/how-to.md#share-directly-from-share-menu-on-ios)
|
||||
* [Share directly from share menu on Android](/docs/how-to.md#share-directly-from-share-menu-on-android)
|
||||
### Is it possible to send files or text directly from the context or share menu?
|
||||
Yes, it finally is!
|
||||
* [Send files directly from context menu on Windows](/docs/how-to.md#send-files-directly-from-context-menu-on-windows)
|
||||
* [Send directly from share menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios)
|
||||
* [Send directly from share menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android)
|
||||
|
||||
### Is it possible to send files or text directly via CLI?
|
||||
Yes, it is!
|
||||
|
||||
* [Send directly from command-line interface](/docs/how-to.md#send-directly-via-command-line-interface)
|
||||
### What about the connection? Is it a P2P-connection directly from device to device or is there any third-party-server?
|
||||
It uses a P2P connection if WebRTC is supported by the browser. WebRTC needs a Signaling Server, but it is only used to establish a connection and is not involved in the file transfer.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# How-To
|
||||
## Share files directly from context menu on Windows
|
||||
## Send files directly from context menu on Windows
|
||||
### Registering to open files with PairDrop
|
||||
The [File Handling API](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files) is implemented
|
||||
|
||||
|
@ -25,17 +25,58 @@ Outstandingly, it is also possible to send multiple files to PairDrop via the co
|
|||
|
||||
[//]: # (Todo: add screenshots)
|
||||
|
||||
## Share directly from share menu on iOS
|
||||
## Send directly from share menu on iOS
|
||||
I created an iOS shortcut to send images, files, folder, URLs or text directly from the share-menu
|
||||
https://routinehub.co/shortcut/13990/
|
||||
|
||||
[//]: # (Todo: add doku with screenshots)
|
||||
|
||||
|
||||
## Share directly from share menu on Android
|
||||
## Send directly from share menu on Android
|
||||
The [Web Share Target API](https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target) is implemented but not yet tested.
|
||||
When the PWA is installed, it should register itself to the share-menu of the device automatically.
|
||||
|
||||
Please test this feature and create an issue if it does not work.
|
||||
This feature is still under development. Please test this feature and create an issue if it does not work.
|
||||
|
||||
## Send directly via command-line interface
|
||||
Send files or text with PairDrop via command-line interface.
|
||||
|
||||
This opens PairDrop in the default browser where you can choose the receiver.
|
||||
|
||||
### Usage
|
||||
```bash
|
||||
$ pairdrop -h
|
||||
Current domain: https://pairdrop.net/
|
||||
|
||||
Usage:
|
||||
Open PairDrop: pairdrop
|
||||
Send files: pairdrop file/directory
|
||||
Send text: pairdrop -t "text"
|
||||
Specify domain: pairdrop -d "https://pairdrop.net/"
|
||||
Show this help text: pairdrop (-h|--help)
|
||||
```
|
||||
|
||||
On Windows Command Prompt you need to use bash: `bash pairdrop -h`
|
||||
|
||||
|
||||
### Setup
|
||||
Download the bash file: [pairdrop-cli/pairdrop](/pairdrop-cli/pairdrop).
|
||||
|
||||
#### Linux
|
||||
1. Put file in a preferred folder e.g. `/usr/local/bin`
|
||||
2. Make sure the bash file is executable. Otherwise, use `chmod +x pairdrop`
|
||||
3. Add absolute path of the folder to PATH variable to make `pairdrop` available globally by executing
|
||||
`export PATH=$PATH:/opt/pairdrop-cli`
|
||||
|
||||
#### Mac
|
||||
1. add bash file to `/usr/local/bin`
|
||||
|
||||
#### Windows
|
||||
1. Put file in a preferred folder e.g. `C:\Users\Public\pairdrop-cli`
|
||||
2. Search for and open `Edit environment variables for your account`
|
||||
3. Click `Environment Variables...`
|
||||
4. Under *System Variables* select `Path` and click *Edit...*
|
||||
5. Click *New*, insert the preferred folder (`C:\Users\Public\pairdrop-cli`), click *OK* until all windows are closed
|
||||
6. Reopen Command prompt window
|
||||
|
||||
[< Back](/README.md)
|
||||
|
|
199
pairdrop-cli/pairdrop
Normal file
199
pairdrop-cli/pairdrop
Normal file
|
@ -0,0 +1,199 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
############################################################
|
||||
# Help #
|
||||
############################################################
|
||||
help()
|
||||
{
|
||||
# Display Help
|
||||
echo "Send files or text with PairDrop via command-line interface."
|
||||
echo "Current domain: ${DOMAIN}"
|
||||
echo
|
||||
echo "Usage:"
|
||||
echo -e "Open PairDrop:\t\t$(basename "$0")"
|
||||
echo -e "Send files:\t\t$(basename "$0") file/directory"
|
||||
echo -e "Send text:\t\t$(basename "$0") -t \"text\""
|
||||
echo -e "Specify domain:\t\t$(basename "$0") -d \"https://pairdrop.net/\""
|
||||
echo -e "Show this help text:\t$(basename "$0") (-h|--help)"
|
||||
}
|
||||
|
||||
openPairDrop()
|
||||
{
|
||||
url="$DOMAIN"
|
||||
if [[ -n $params ]];then
|
||||
url="${url}?${params}"
|
||||
fi
|
||||
if [[ -n $hash ]];then
|
||||
url="${url}#${hash}"
|
||||
fi
|
||||
|
||||
echo "PairDrop is opening at $DOMAIN"
|
||||
if [[ $OS == "Windows" ]];then
|
||||
start "$url"
|
||||
elif [[ $OS == "Mac" ]];then
|
||||
open "$url"
|
||||
elif [[ $OS == "WSL" || $OS == "WSL2" ]];then
|
||||
powershell.exe /c "Start-Process ${url}"
|
||||
else
|
||||
xdg-open "$url"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
setOs()
|
||||
{
|
||||
unameOut=$(uname -a)
|
||||
case "${unameOut}" in
|
||||
*Microsoft*) OS="WSL";; #must be first since Windows subsystem for linux will have Linux in the name too
|
||||
*microsoft*) OS="WSL2";; #WARNING: My v2 uses ubuntu 20.4 at the moment slightly different name may not always work
|
||||
Linux*) OS="Linux";;
|
||||
Darwin*) OS="Mac";;
|
||||
CYGWIN*) OS="Cygwin";;
|
||||
MINGW*) OS="Windows";;
|
||||
*Msys) OS="Windows";;
|
||||
*) OS="UNKNOWN:${unameOut}"
|
||||
esac
|
||||
}
|
||||
|
||||
specifyDomain()
|
||||
{
|
||||
[[ ! $1 = http* ]] || [[ ! $1 = */ ]] && echo "Incorrect format. Specify domain like https://pairdrop.net/" && exit
|
||||
echo "DOMAIN=${1}" > "$CONFIGPATH"
|
||||
echo -e "Domain is now set to:\n$1\n"
|
||||
}
|
||||
|
||||
sendText()
|
||||
{
|
||||
params="base64text=hash"
|
||||
hash=$(echo -n "${OPTARG}" | base64)
|
||||
|
||||
if [[ $(echo -n "$hash" | wc -m) -gt 32600 ]];then
|
||||
params="base64text=paste"
|
||||
if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then
|
||||
echo -n "$hash" | clip.exe
|
||||
elif [[ $OS == "Mac" ]];then
|
||||
echo -n "$hash" | pbcopy
|
||||
else
|
||||
(echo -n "$hash" | xclip) || echo "You need to install xclip for sending bigger files from cli"
|
||||
fi
|
||||
hash=
|
||||
fi
|
||||
|
||||
openPairDrop
|
||||
exit
|
||||
}
|
||||
|
||||
sendFiles()
|
||||
{
|
||||
params="base64zip=hash"
|
||||
if [[ $1 == */ ]]; then
|
||||
path="${1::-1}"
|
||||
else
|
||||
path=$1
|
||||
fi
|
||||
zipPath="${path}_pairdrop.zip"
|
||||
zipPath=${zipPath// /_}
|
||||
|
||||
[[ -a "$zipPath" ]] && echo "Cannot overwrite $zipPath. Please remove first." && exit
|
||||
|
||||
if [[ -d $path ]]; then
|
||||
zipPathTemp="temp_${zipPath}"
|
||||
[[ -a "$zipPathTemp" ]] && echo "Cannot overwrite $zipPathTemp. Please remove first." && exit
|
||||
echo "Processing directory..."
|
||||
|
||||
# Create zip files temporarily to send directory
|
||||
zip -q -b /tmp/ -r "$zipPath" "$path"
|
||||
zip -q -b /tmp/ "$zipPathTemp" "$zipPath"
|
||||
|
||||
hash=$(base64 -w 0 "$zipPathTemp")
|
||||
|
||||
# remove temporary temp file
|
||||
rm "$zipPathTemp"
|
||||
else
|
||||
echo "Processing file..."
|
||||
|
||||
# Create zip file temporarily to send file
|
||||
zip -q -b /tmp/ "$zipPath" "$path"
|
||||
|
||||
hash=$(base64 -w 0 "$zipPath")
|
||||
fi
|
||||
|
||||
# remove temporary temp file
|
||||
rm "$zipPath"
|
||||
|
||||
if [[ $(echo -n "$hash" | wc -m) -gt 32600 ]];then
|
||||
params="base64zip=paste"
|
||||
if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then
|
||||
echo -n "$hash" | clip.exe
|
||||
elif [[ $OS == "Mac" ]];then
|
||||
echo -n "$hash" | pbcopy
|
||||
else
|
||||
(echo -n "$hash" | xclip) || echo "You need to install xclip for sending bigger files from cli"
|
||||
fi
|
||||
hash=
|
||||
fi
|
||||
|
||||
openPairDrop
|
||||
exit
|
||||
}
|
||||
|
||||
############################################################
|
||||
############################################################
|
||||
# Main program #
|
||||
############################################################
|
||||
############################################################
|
||||
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||
|
||||
pushd . > '/dev/null';
|
||||
SCRIPTPATH="${BASH_SOURCE[0]:-$0}";
|
||||
|
||||
while [ -h "$SCRIPTPATH" ];
|
||||
do
|
||||
cd "$( dirname -- "$SCRIPTPATH"; )";
|
||||
SCRIPTPATH="$( readlink -f -- "$SCRIPTPATH"; )";
|
||||
done
|
||||
|
||||
cd "$( dirname -- "$SCRIPTPATH"; )" > '/dev/null';
|
||||
SCRIPTPATH="$( pwd; )";
|
||||
popd > '/dev/null';
|
||||
|
||||
CONFIGPATH="${SCRIPTPATH}/.pairdrop-cli-config"
|
||||
|
||||
[ ! -f "$CONFIGPATH" ] &&
|
||||
specifyDomain "https://pairdrop.net/" &&
|
||||
[ ! -f "$CONFIGPATH" ] &&
|
||||
echo "Could not create config file. Add 'DOMAIN=https://pairdrop.net/' to a file called .pairdrop-cli-config in the same file as this 'pairdrop' bash file"
|
||||
|
||||
[ ! -f "$CONFIGPATH" ] || export "$(grep -v '^#' "$CONFIGPATH" | xargs)"
|
||||
|
||||
setOs
|
||||
############################################################
|
||||
# Process the input options. Add options as needed. #
|
||||
############################################################
|
||||
# Get the options
|
||||
# open PairDrop if no options are given
|
||||
[[ $# -eq 0 ]] && openPairDrop && exit
|
||||
|
||||
# display help and exit if first argument is "--help" or more than 2 arguments are given
|
||||
[ "$1" == "--help" ] || [[ $# -gt 2 ]] && help && exit
|
||||
|
||||
while getopts "d:ht:*" option; do
|
||||
case $option in
|
||||
d) # specify domain
|
||||
specifyDomain "$2"
|
||||
exit;;
|
||||
t) # Send text
|
||||
sendText
|
||||
exit;;
|
||||
h | ?) # display help and exit
|
||||
help
|
||||
exit;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Send file(s)
|
||||
# display help and exit if 2 arguments are given or if file does not exist
|
||||
[[ $# -eq 2 ]] || [[ ! -a $1 ]] && help && exit
|
||||
|
||||
sendFiles "$1"
|
|
@ -213,11 +213,11 @@
|
|||
</x-paper>
|
||||
</x-background>
|
||||
</x-dialog>
|
||||
<!-- base64ZipDialog Dialog -->
|
||||
<x-dialog id="base64ZipDialog">
|
||||
<!-- base64PasteDialog Dialog -->
|
||||
<x-dialog id="base64PasteDialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<button class="button center" id="base64ZipPasteBtn" title="Paste">Tap here to paste files</button>
|
||||
<button class="button center" id="base64PasteBtn" title="Paste">Tap here to paste files</button>
|
||||
<button class="button center" close>Close</button>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
|
|
@ -143,7 +143,7 @@ class PeersUI {
|
|||
descriptor = `${files[0].name} and ${files.length-1} other files`;
|
||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
||||
} else {
|
||||
descriptor = "pasted text";
|
||||
descriptor = "shared text";
|
||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
||||
}
|
||||
|
||||
|
@ -1081,67 +1081,135 @@ class ReceiveTextDialog extends Dialog {
|
|||
class Base64ZipDialog extends Dialog {
|
||||
|
||||
constructor() {
|
||||
super('base64ZipDialog');
|
||||
super('base64PasteDialog');
|
||||
const urlParams = new URL(window.location).searchParams;
|
||||
const base64Zip = urlParams.get('base64zip');
|
||||
const base64Text = urlParams.get('base64text');
|
||||
this.$pasteBtn = this.$el.querySelector('#base64ZipPasteBtn')
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard())
|
||||
const base64Zip = urlParams.get('base64zip');
|
||||
const base64Hash = window.location.hash.substring(1);
|
||||
|
||||
this.$pasteBtn = this.$el.querySelector('#base64PasteBtn');
|
||||
|
||||
if (base64Text) {
|
||||
this.processBase64Text(base64Text);
|
||||
} else if (base64Zip) {
|
||||
if (!navigator.clipboard.readText) {
|
||||
setTimeout(_ => Events.fire('notify-user', 'This feature is not available on your device.'), 500);
|
||||
this.clearBrowserHistory();
|
||||
return;
|
||||
}
|
||||
this.show();
|
||||
if (base64Text === "paste") {
|
||||
// ?base64text=paste
|
||||
// base64 encoded string is ready to be pasted from clipboard
|
||||
this.$pasteBtn.innerText = 'Tap here to paste text';
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard('text'));
|
||||
} else if (base64Text === "hash") {
|
||||
// ?base64text=hash#BASE64ENCODED
|
||||
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
||||
this.processBase64Text(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
console.log("Text content incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
// ?base64text=BASE64ENCODED
|
||||
// base64 encoded string was part of url param (not recommended)
|
||||
this.processBase64Text(base64Text)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
console.log("Text content incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
} else if (base64Zip) {
|
||||
this.show();
|
||||
if (base64Zip === "hash") {
|
||||
// ?base64zip=hash#BASE64ENCODED
|
||||
// base64 encoded zip file is url hash which is never sent to the server
|
||||
this.processBase64Zip(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'File content is incorrect.');
|
||||
console.log("File content incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
// ?base64zip=paste || ?base64zip=true
|
||||
this.$pasteBtn.innerText = 'Tap here to paste files';
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard('file'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setPasteBtnToProcessing() {
|
||||
this.$pasteBtn.pointerEvents = "none";
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
}
|
||||
|
||||
async processClipboard(type) {
|
||||
if (!navigator.clipboard.readText) {
|
||||
Events.fire('notify-user', 'This feature is not available on your browser.');
|
||||
console.log("navigator.clipboard.readText() is not available on your browser.")
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this._setPasteBtnToProcessing();
|
||||
|
||||
const base64 = await navigator.clipboard.readText();
|
||||
|
||||
if (!base64) return;
|
||||
|
||||
if (type === "text") {
|
||||
this.processBase64Text(base64)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
this.processBase64Zip(base64)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
processBase64Text(base64Text){
|
||||
try {
|
||||
return new Promise((resolve) => {
|
||||
this._setPasteBtnToProcessing();
|
||||
let decodedText = decodeURIComponent(escape(window.atob(base64Text)));
|
||||
Events.fire('activate-paste-mode', {files: [], text: decodedText});
|
||||
} catch (e) {
|
||||
setTimeout(_ => Events.fire('notify-user', 'Content incorrect.'), 500);
|
||||
} finally {
|
||||
this.clearBrowserHistory();
|
||||
this.hide();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
async processClipboard() {
|
||||
this.$pasteBtn.pointerEvents = "none";
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
try {
|
||||
const base64zip = await navigator.clipboard.readText();
|
||||
let bstr = atob(base64zip), n = bstr.length, u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
|
||||
const zipBlob = new File([u8arr], 'archive.zip');
|
||||
|
||||
let files = [];
|
||||
const zipEntries = await zipper.getEntries(zipBlob);
|
||||
for (let i = 0; i < zipEntries.length; i++) {
|
||||
let fileBlob = await zipper.getData(zipEntries[i]);
|
||||
files.push(new File([fileBlob], zipEntries[i].filename));
|
||||
}
|
||||
Events.fire('activate-paste-mode', {files: files, text: ""})
|
||||
} catch (e) {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.')
|
||||
} finally {
|
||||
this.clearBrowserHistory();
|
||||
this.hide();
|
||||
async processBase64Zip(base64zip) {
|
||||
this._setPasteBtnToProcessing();
|
||||
let bstr = atob(base64zip), n = bstr.length, u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
|
||||
const zipBlob = new File([u8arr], 'archive.zip');
|
||||
|
||||
let files = [];
|
||||
const zipEntries = await zipper.getEntries(zipBlob);
|
||||
for (let i = 0; i < zipEntries.length; i++) {
|
||||
let fileBlob = await zipper.getData(zipEntries[i]);
|
||||
files.push(new File([fileBlob], zipEntries[i].filename));
|
||||
}
|
||||
Events.fire('activate-paste-mode', {files: files, text: ""});
|
||||
}
|
||||
|
||||
clearBrowserHistory() {
|
||||
window.history.replaceState({}, "Rewrite URL", '/');
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.clearBrowserHistory();
|
||||
super.hide();
|
||||
}
|
||||
}
|
||||
|
||||
class Toast extends Dialog {
|
||||
|
|
|
@ -605,21 +605,21 @@ x-dialog .row-reverse {
|
|||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
#base64ZipPasteBtn {
|
||||
#base64PasteBtn {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
border: solid 12px #438cff;
|
||||
}
|
||||
|
||||
#base64ZipDialog button {
|
||||
#base64PasteDialog button {
|
||||
margin: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#base64ZipDialog button[close] {
|
||||
#base64PasteDialog button[close] {
|
||||
margin-top: 20px;
|
||||
}
|
||||
#base64ZipDialog button[close]:before {
|
||||
#base64PasteDialog button[close]:before {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
|
|
@ -216,11 +216,11 @@
|
|||
</x-paper>
|
||||
</x-background>
|
||||
</x-dialog>
|
||||
<!-- base64ZipDialog Dialog -->
|
||||
<x-dialog id="base64ZipDialog">
|
||||
<!-- base64PasteDialog Dialog -->
|
||||
<x-dialog id="base64PasteDialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<button class="button center" id="base64ZipPasteBtn" title="Paste">Tap here to paste files</button>
|
||||
<button class="button center" id="base64PasteBtn" title="Paste">Tap here to paste files</button>
|
||||
<button class="button center" close>Close</button>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
|
|
@ -143,7 +143,7 @@ class PeersUI {
|
|||
descriptor = `${files[0].name} and ${files.length-1} other files`;
|
||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
||||
} else {
|
||||
descriptor = "pasted text";
|
||||
descriptor = "shared text";
|
||||
noPeersMessage = `Open PairDrop on other devices to send<br>${descriptor}`;
|
||||
}
|
||||
|
||||
|
@ -1082,67 +1082,135 @@ class ReceiveTextDialog extends Dialog {
|
|||
class Base64ZipDialog extends Dialog {
|
||||
|
||||
constructor() {
|
||||
super('base64ZipDialog');
|
||||
super('base64PasteDialog');
|
||||
const urlParams = new URL(window.location).searchParams;
|
||||
const base64Zip = urlParams.get('base64zip');
|
||||
const base64Text = urlParams.get('base64text');
|
||||
this.$pasteBtn = this.$el.querySelector('#base64ZipPasteBtn')
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard())
|
||||
const base64Zip = urlParams.get('base64zip');
|
||||
const base64Hash = window.location.hash.substring(1);
|
||||
|
||||
this.$pasteBtn = this.$el.querySelector('#base64PasteBtn');
|
||||
|
||||
if (base64Text) {
|
||||
this.processBase64Text(base64Text);
|
||||
} else if (base64Zip) {
|
||||
if (!navigator.clipboard.readText) {
|
||||
setTimeout(_ => Events.fire('notify-user', 'This feature is not available on your device.'), 500);
|
||||
this.clearBrowserHistory();
|
||||
return;
|
||||
}
|
||||
this.show();
|
||||
if (base64Text === "paste") {
|
||||
// ?base64text=paste
|
||||
// base64 encoded string is ready to be pasted from clipboard
|
||||
this.$pasteBtn.innerText = 'Tap here to paste text';
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard('text'));
|
||||
} else if (base64Text === "hash") {
|
||||
// ?base64text=hash#BASE64ENCODED
|
||||
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
||||
this.processBase64Text(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
console.log("Text content incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
// ?base64text=BASE64ENCODED
|
||||
// base64 encoded string was part of url param (not recommended)
|
||||
this.processBase64Text(base64Text)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
console.log("Text content incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
} else if (base64Zip) {
|
||||
this.show();
|
||||
if (base64Zip === "hash") {
|
||||
// ?base64zip=hash#BASE64ENCODED
|
||||
// base64 encoded zip file is url hash which is never sent to the server
|
||||
this.processBase64Zip(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'File content is incorrect.');
|
||||
console.log("File content incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
// ?base64zip=paste || ?base64zip=true
|
||||
this.$pasteBtn.innerText = 'Tap here to paste files';
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard('file'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setPasteBtnToProcessing() {
|
||||
this.$pasteBtn.pointerEvents = "none";
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
}
|
||||
|
||||
async processClipboard(type) {
|
||||
if (!navigator.clipboard.readText) {
|
||||
Events.fire('notify-user', 'This feature is not available on your browser.');
|
||||
console.log("navigator.clipboard.readText() is not available on your browser.")
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this._setPasteBtnToProcessing();
|
||||
|
||||
const base64 = await navigator.clipboard.readText();
|
||||
|
||||
if (!base64) return;
|
||||
|
||||
if (type === "text") {
|
||||
this.processBase64Text(base64)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
this.processBase64Zip(base64)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
processBase64Text(base64Text){
|
||||
try {
|
||||
return new Promise((resolve) => {
|
||||
this._setPasteBtnToProcessing();
|
||||
let decodedText = decodeURIComponent(escape(window.atob(base64Text)));
|
||||
Events.fire('activate-paste-mode', {files: [], text: decodedText});
|
||||
} catch (e) {
|
||||
setTimeout(_ => Events.fire('notify-user', 'Content incorrect.'), 500);
|
||||
} finally {
|
||||
this.clearBrowserHistory();
|
||||
this.hide();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
async processClipboard() {
|
||||
this.$pasteBtn.pointerEvents = "none";
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
try {
|
||||
const base64zip = await navigator.clipboard.readText();
|
||||
let bstr = atob(base64zip), n = bstr.length, u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
|
||||
const zipBlob = new File([u8arr], 'archive.zip');
|
||||
|
||||
let files = [];
|
||||
const zipEntries = await zipper.getEntries(zipBlob);
|
||||
for (let i = 0; i < zipEntries.length; i++) {
|
||||
let fileBlob = await zipper.getData(zipEntries[i]);
|
||||
files.push(new File([fileBlob], zipEntries[i].filename));
|
||||
}
|
||||
Events.fire('activate-paste-mode', {files: files, text: ""})
|
||||
} catch (e) {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.')
|
||||
} finally {
|
||||
this.clearBrowserHistory();
|
||||
this.hide();
|
||||
async processBase64Zip(base64zip) {
|
||||
this._setPasteBtnToProcessing();
|
||||
let bstr = atob(base64zip), n = bstr.length, u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
|
||||
const zipBlob = new File([u8arr], 'archive.zip');
|
||||
|
||||
let files = [];
|
||||
const zipEntries = await zipper.getEntries(zipBlob);
|
||||
for (let i = 0; i < zipEntries.length; i++) {
|
||||
let fileBlob = await zipper.getData(zipEntries[i]);
|
||||
files.push(new File([fileBlob], zipEntries[i].filename));
|
||||
}
|
||||
Events.fire('activate-paste-mode', {files: files, text: ""});
|
||||
}
|
||||
|
||||
clearBrowserHistory() {
|
||||
window.history.replaceState({}, "Rewrite URL", '/');
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.clearBrowserHistory();
|
||||
super.hide();
|
||||
}
|
||||
}
|
||||
|
||||
class Toast extends Dialog {
|
||||
|
|
|
@ -614,21 +614,21 @@ x-dialog .row-reverse {
|
|||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
#base64ZipPasteBtn {
|
||||
#base64PasteBtn {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
border: solid 12px #438cff;
|
||||
}
|
||||
|
||||
#base64ZipDialog button {
|
||||
#base64PasteDialog button {
|
||||
margin: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#base64ZipDialog button[close] {
|
||||
#base64PasteDialog button[close] {
|
||||
margin-top: 20px;
|
||||
}
|
||||
#base64ZipDialog button[close]:before {
|
||||
#base64PasteDialog button[close]:before {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue