Compare commits

...

19 Commits

Author SHA1 Message Date
schlagmichdoch
59dca141b6 increase version to v1.6.0 2023-04-17 15:25:52 +02:00
schlagmichdoch
d0046e83cb remove openrelayproject from rtc_config 2023-04-17 15:24:31 +02:00
schlagmichdoch
2aeadb44e2 Merge pull request #68
Increase SEO
2023-04-17 15:21:04 +02:00
schlagmichdoch
7827a47d29 increase seo with recommendations from PageSpeed Insights 2023-04-17 15:19:54 +02:00
schlagmichdoch
4edc9c9b22 Merge pull request #95 from robvanoostenrijk/ghcr-multi-architecture
Build multi-architecture image
2023-04-16 15:35:22 +02:00
schlagmichdoch
398a69d7a0 Merge pull request #97 from schlagmichdoch/dependabot/npm_and_yarn/ua-parser-js-1.0.35
Bump ua-parser-js from 1.0.34 to 1.0.35
2023-04-07 18:07:43 +02:00
dependabot[bot]
dfe69cc873 Bump ua-parser-js from 1.0.34 to 1.0.35
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 1.0.34 to 1.0.35.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/1.0.34...1.0.35)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-03 05:05:12 +00:00
Rob van Oostenrijk
f5fde731b0 Build multi-architecture image
Build image output both for amd64 & arm64
2023-03-31 08:37:11 +04:00
schlagmichdoch
d6eee480b3 Add darkmode/lightmode/auto toggle to "Other changes" 2023-03-29 17:10:20 +02:00
schlagmichdoch
f6ad85a744 increase version to v1.5.3 2023-03-29 16:24:24 +02:00
schlagmichdoch
d50480b2f8 Merge pull request #94 from schlagmichdoch/add_theme_menu
Add theme menu to toggle between auto, light and dark mode.
2023-03-29 16:22:42 +02:00
schlagmichdoch
ac1e88b6a0 Add possibility to reset theme to auto 2023-03-29 01:39:45 +02:00
schlagmichdoch
0fe36e132c Remove the "under development" message from the share-menu section 2023-03-28 20:24:46 +02:00
schlagmichdoch
ab08091f5d increase version to v1.5.2 2023-03-28 20:00:05 +02:00
schlagmichdoch
19a78a5239 Merge pull request #72 from schlagmichdoch/fix_share_target
WIP: Fix share target API
2023-03-28 19:58:34 +02:00
schlagmichdoch
d0b2c81582 Tidy up code 2023-03-28 19:07:33 +02:00
Daniel Pham
10a0aaf896 Fix passed arguments for sharing text 2023-03-28 19:00:15 +02:00
Daniel Pham
34ebd60304 Update service worker
- files array now matches manifest files name
- fixed handling fetch redirect
2023-03-28 19:00:15 +02:00
schlagmichdoch
251df2fbff try to fix share target api 2023-03-28 19:00:05 +02:00
21 changed files with 531 additions and 227 deletions

View File

@@ -29,6 +29,12 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup qemu
uses: docker/setup-qemu-action@v2.1.0
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with: with:
@@ -46,6 +52,7 @@ jobs:
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -60,6 +60,7 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop)
* Warn user before PairDrop is closed on file transfer * Warn user before PairDrop is closed on file transfer
* Open PairDrop on multiple tabs simultaneously (Thanks [@willstott101](https://github.com/willstott101)) * Open PairDrop on multiple tabs simultaneously (Thanks [@willstott101](https://github.com/willstott101))
* [Video and Audio preview](https://github.com/RobinLinus/snapdrop/pull/455) (Thanks [@victorwads](https://github.com/victorwads)) * [Video and Audio preview](https://github.com/RobinLinus/snapdrop/pull/455) (Thanks [@victorwads](https://github.com/victorwads))
* Switch theme back to auto/system after darkmode or lightmode is enabled
* Node-only implementation (Thanks [@Bellisario](https://github.com/Bellisario)) * Node-only implementation (Thanks [@Bellisario](https://github.com/Bellisario))
* Automatic restart on error (Thanks [@KaKi87](https://github.com/KaKi87)) * Automatic restart on error (Thanks [@KaKi87](https://github.com/KaKi87))
* Lots of stability fixes (Thanks [@MWY001](https://github.com/MWY001) [@skiby7](https://github.com/skiby7) and [@willstott101](https://github.com/willstott101)) * Lots of stability fixes (Thanks [@MWY001](https://github.com/MWY001) [@skiby7](https://github.com/skiby7) and [@willstott101](https://github.com/willstott101))

View File

@@ -33,10 +33,10 @@ https://routinehub.co/shortcut/13990/
## Send 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. The [Web Share Target API](https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target) is implemented.
When the PWA is installed, it should register itself to the share-menu of the device automatically.
When the PWA is installed, it will register itself to the share-menu of the device automatically.
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 directly via command-line interface
Send files or text with PairDrop via command-line interface. Send files or text with PairDrop via command-line interface.

View File

@@ -63,14 +63,6 @@ const rtcConfig = process.env.RTC_CONFIG
"iceServers": [ "iceServers": [
{ {
"urls": "stun:stun.l.google.com:19302" "urls": "stun:stun.l.google.com:19302"
},
{
"urls": "stun:openrelay.metered.ca:80"
},
{
"urls": "turn:openrelay.metered.ca:443",
"username": "openrelayproject",
"credential": "openrelayproject"
} }
] ]
}; };

18
package-lock.json generated
View File

@@ -1,17 +1,17 @@
{ {
"name": "pairdrop", "name": "pairdrop",
"version": "1.5.1", "version": "1.6.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pairdrop", "name": "pairdrop",
"version": "1.5.1", "version": "1.6.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"express": "^4.18.2", "express": "^4.18.2",
"express-rate-limit": "^6.7.0", "express-rate-limit": "^6.7.0",
"ua-parser-js": "^1.0.34", "ua-parser-js": "^1.0.35",
"unique-names-generator": "^4.3.0", "unique-names-generator": "^4.3.0",
"ws": "^8.13.0" "ws": "^8.13.0"
}, },
@@ -583,9 +583,9 @@
} }
}, },
"node_modules/ua-parser-js": { "node_modules/ua-parser-js": {
"version": "1.0.34", "version": "1.0.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.34.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
"integrity": "sha512-K9mwJm/DaB6mRLZfw6q8IMXipcrmuT6yfhYmwhAkuh+81sChuYstYA+znlgaflUPaYUa3odxKPKGw6Vw/lANew==", "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -1070,9 +1070,9 @@
} }
}, },
"ua-parser-js": { "ua-parser-js": {
"version": "1.0.34", "version": "1.0.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.34.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
"integrity": "sha512-K9mwJm/DaB6mRLZfw6q8IMXipcrmuT6yfhYmwhAkuh+81sChuYstYA+znlgaflUPaYUa3odxKPKGw6Vw/lANew==" "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA=="
}, },
"unique-names-generator": { "unique-names-generator": {
"version": "4.7.1", "version": "4.7.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "pairdrop", "name": "pairdrop",
"version": "1.5.1", "version": "1.6.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -12,7 +12,7 @@
"dependencies": { "dependencies": {
"express": "^4.18.2", "express": "^4.18.2",
"express-rate-limit": "^6.7.0", "express-rate-limit": "^6.7.0",
"ua-parser-js": "^1.0.34", "ua-parser-js": "^1.0.35",
"unique-names-generator": "^4.3.0", "unique-names-generator": "^4.3.0",
"ws": "^8.13.0" "ws": "^8.13.0"
}, },

View File

@@ -44,32 +44,46 @@
<use xlink:href="#info-outline" /> <use xlink:href="#info-outline" />
</svg> </svg>
</a> </a>
<a id="theme" class="icon-button" title="Switch Darkmode/Lightmode" > <div id="theme-wrapper">
<svg class="icon"> <div id="theme-auto" class="icon-button selected" title="Adapt to System" >
<use xlink:href="#icon-theme" /> <svg class="icon">
</svg> <use xlink:href="#icon-theme-auto" />
</a> </svg>
<a id="notification" class="icon-button" title="Enable Notifications" hidden> </div>
<div>
<div id="theme-light" class="icon-button" title="Always Light" >
<svg class="icon">
<use xlink:href="#icon-theme-light" />
</svg>
</div>
<div id="theme-dark" class="icon-button" title="Always Dark" >
<svg class="icon">
<use xlink:href="#icon-theme-dark" />
</svg>
</div>
</div>
</div>
<div id="notification" class="icon-button" title="Enable Notifications" hidden>
<svg class="icon"> <svg class="icon">
<use xlink:href="#notifications" /> <use xlink:href="#notifications" />
</svg> </svg>
</a> </div>
<a id="install" class="icon-button" title="Install PairDrop" hidden> <div id="install" class="icon-button" title="Install PairDrop" hidden>
<svg class="icon"> <svg class="icon">
<use xlink:href="#homescreen" /> <use xlink:href="#homescreen" />
</svg> </svg>
</a> </div>
<a id="pair-device" class="icon-button" title="Pair Device" hidden> <div id="pair-device" class="icon-button" title="Pair Device" hidden>
<svg class="icon"> <svg class="icon">
<use xlink:href="#pair-device-icon" /> <use xlink:href="#pair-device-icon" />
</svg> </svg>
</a> </div>
<a id="clear-pair-devices" class="icon-button" title="Clear All Paired Devices" hidden> <div id="clear-pair-devices" class="icon-button" title="Clear All Paired Devices" hidden>
<svg class="icon"> <svg class="icon">
<use xlink:href="#clear-pair-devices-icon" /> <use xlink:href="#clear-pair-devices-icon" />
</svg> </svg>
</a> </div>
<a id="cancel-paste-mode" class="button" hidden>Done</a> <div id="cancel-paste-mode" class="button" hidden>Done</div>
</header> </header>
<!-- Center --> <!-- Center -->
<div id="center"> <div id="center">
@@ -112,12 +126,12 @@
<div id="pair-instructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div> <div id="pair-instructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div>
<hr> <hr>
<div id="key-input-container"> <div id="key-input-container">
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-2" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-3" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-4" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-5" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-6" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
</div> </div>
<div class="font-subheading center text-center">Enter key from another device to continue.</div> <div class="font-subheading center text-center">Enter key from another device to continue.</div>
<div class="center row-reverse"> <div class="center row-reverse">
@@ -206,7 +220,7 @@
<span class="display-name"></span> <span class="display-name"></span>
</div> </div>
<div class="row-separator"></div> <div class="row-separator"></div>
<div id="text-input" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div> <div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
<div class="center row-reverse"> <div class="center row-reverse">
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button> <button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
<button class="button" type="button" title="ESCAPE" close>Cancel</button> <button class="button" type="button" title="ESCAPE" close>Cancel</button>
@@ -328,7 +342,13 @@
<path d="M0 0h24v24H0z" fill="none" /> <path d="M0 0h24v24H0z" fill="none" />
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z" /> <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z" />
</symbol> </symbol>
<symbol id="icon-theme" viewBox="0 0 24 24"> <symbol id="icon-theme-auto" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-54 -54 620 620"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M448 256c0-106-86-192-192-192V448c106 0 192-86 192-192zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z"/></svg>
</symbol>
<symbol id="icon-theme-light" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-54 -54 620 620"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"/></svg>
</symbol>
<symbol id="icon-theme-dark" viewBox="0 0 24 24">
<rect fill="none" height="24" width="24"/><path d="M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36c-0.98,1.37-2.58,2.26-4.4,2.26 c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/> <rect fill="none" height="24" width="24"/><path d="M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36c-0.98,1.37-2.58,2.26-4.4,2.26 c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/>
</symbol> </symbol>
<symbol id="pair-device-icon" viewBox="0 0 640 512"> <symbol id="pair-device-icon" viewBox="0 0 640 512">
@@ -345,11 +365,11 @@
</symbol> </symbol>
</svg> </svg>
<!-- Scripts --> <!-- Scripts -->
<script src="scripts/util.js"></script> <script src="scripts/theme.js"></script>
<script src="scripts/network.js"></script> <script src="scripts/network.js"></script>
<script src="scripts/ui.js"></script> <script src="scripts/ui.js"></script>
<script src="scripts/theme.js" async></script> <script src="scripts/util.js" async></script>
<script src="scripts/qrcode.js" async></script> <script src="scripts/QRCode.min.js" async></script>
<script src="scripts/zip.min.js" async></script> <script src="scripts/zip.min.js" async></script>
<script src="scripts/NoSleep.min.js" async></script> <script src="scripts/NoSleep.min.js" async></script>
<!-- Sounds --> <!-- Sounds -->

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@@ -1,39 +1,78 @@
(function(){ (function(){
// Select the button const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
const btnTheme = document.getElementById('theme'); const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)').matches;
// Check for dark mode preference at the OS level
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); const $themeAuto = document.getElementById('theme-auto');
const $themeLight = document.getElementById('theme-light');
// Get the user's theme preference from local storage, if it's available const $themeDark = document.getElementById('theme-dark');
const currentTheme = localStorage.getItem('theme');
// If the user's preference in localStorage is dark... let currentTheme = localStorage.getItem('theme');
if (currentTheme === 'dark') { if (currentTheme === 'dark') {
// ...let's toggle the .dark-theme class on the body setModeToDark();
document.body.classList.toggle('dark-theme');
// Otherwise, if the user's preference in localStorage is light...
} else if (currentTheme === 'light') { } else if (currentTheme === 'light') {
// ...let's toggle the .light-theme class on the body setModeToLight();
document.body.classList.toggle('light-theme');
} }
// Listen for a click on the button $themeAuto.addEventListener('click', _ => {
btnTheme.addEventListener('click', function(e) { if (currentTheme) {
e.preventDefault(); setModeToAuto();
// If the user's OS setting is dark and matches our .dark-theme class...
let theme;
if (prefersDarkScheme.matches) {
// ...then toggle the light mode class
document.body.classList.toggle('light-theme');
// ...but use .dark-theme if the .light-theme class is already on the body,
theme = document.body.classList.contains('light-theme') ? 'light' : 'dark';
} else { } else {
// Otherwise, let's do the same thing, but for .dark-theme setModeToDark();
document.body.classList.toggle('dark-theme'); }
theme = document.body.classList.contains('dark-theme') ? 'dark' : 'light'; });
$themeLight.addEventListener('click', _ => {
if (currentTheme !== 'light') {
setModeToLight();
} else {
setModeToAuto();
}
});
$themeDark.addEventListener('click', _ => {
if (currentTheme !== 'dark') {
setModeToDark();
} else {
setModeToLight();
} }
// Finally, let's save the current preference to localStorage to keep using it
localStorage.setItem('theme', theme);
}); });
function setModeToDark() {
document.body.classList.remove('light-theme');
document.body.classList.add('dark-theme');
localStorage.setItem('theme', 'dark');
currentTheme = 'dark';
$themeAuto.classList.remove("selected");
$themeLight.classList.remove("selected");
$themeDark.classList.add("selected");
}
function setModeToLight() {
document.body.classList.remove('dark-theme');
document.body.classList.add('light-theme');
localStorage.setItem('theme', 'light');
currentTheme = 'light';
$themeAuto.classList.remove("selected");
$themeLight.classList.add("selected");
$themeDark.classList.remove("selected");
}
function setModeToAuto() {
document.body.classList.remove('dark-theme');
document.body.classList.remove('light-theme');
if (prefersDarkTheme) {
document.body.classList.add('dark-theme');
} else if (prefersLightTheme) {
document.body.classList.add('light-theme');
}
localStorage.removeItem('theme');
currentTheme = undefined;
$themeAuto.classList.add("selected");
$themeLight.classList.remove("selected");
$themeDark.classList.remove("selected");
}
})(); })();

View File

@@ -1607,27 +1607,33 @@ class WebShareTargetUI {
let shareTargetText; let shareTargetText;
if (url) { if (url) {
shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable. shareTargetText = url; // we share only the link - no text.
} else if (title && text) { } else if (title && text) {
shareTargetText = title + '\r\n' + text; shareTargetText = title + '\r\n' + text;
} else { } else {
shareTargetText = title + text; shareTargetText = title + text;
} }
console.log('Shared Target Text:', '"' + shareTargetText + '"');
Events.fire('activate-paste-mode', {files: [], text: shareTargetText}) Events.fire('activate-paste-mode', {files: [], text: shareTargetText})
} else if (share_target_type === "files") { } else if (share_target_type === "files") {
const openRequest = window.indexedDB.open('pairdrop_store') let openRequest = window.indexedDB.open('pairdrop_store')
openRequest.onsuccess( db => { openRequest.onsuccess = e => {
const db = e.target.result;
const tx = db.transaction('share_target_files', 'readwrite'); const tx = db.transaction('share_target_files', 'readwrite');
const store = tx.objectStore('share_target_files'); const store = tx.objectStore('share_target_files');
const request = store.getAll(); const request = store.getAll();
request.onsuccess = _ => { request.onsuccess = _ => {
Events.fire('activate-paste-mode', {files: request.result, text: ""}) const fileObjects = request.result;
let filesReceived = [];
for (let i=0; i<fileObjects.length; i++) {
filesReceived.push(new File([fileObjects[i].buffer], fileObjects[i].name));
}
const clearRequest = store.clear() const clearRequest = store.clear()
clearRequest.onsuccess = _ => db.close(); clearRequest.onsuccess = _ => db.close();
Events.fire('activate-paste-mode', {files: filesReceived, text: ""})
} }
}) }
} }
window.history.replaceState({}, "Rewrite URL", '/'); window.history.replaceState({}, "Rewrite URL", '/');
} }
@@ -1684,7 +1690,7 @@ class PersistentStorage {
PersistentStorage.logBrowserNotCapable(); PersistentStorage.logBrowserNotCapable();
return; return;
} }
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 2); const DBOpenRequest = window.indexedDB.open('pairdrop_store', 3);
DBOpenRequest.onerror = (e) => { DBOpenRequest.onerror = (e) => {
PersistentStorage.logBrowserNotCapable(); PersistentStorage.logBrowserNotCapable();
console.log('Error initializing database: '); console.log('Error initializing database: ');
@@ -1710,7 +1716,10 @@ class PersistentStorage {
} }
try { try {
db.createObjectStore('share_target_files'); if (db.objectStoreNames.contains('share_target_files')) {
db.deleteObjectStore('share_target_files');
}
db.createObjectStore('share_target_files', {autoIncrement: true});
} catch (error) { } catch (error) {
console.log("Object store named 'share_target_files' already exists") console.log("Object store named 'share_target_files' already exists")
} }

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.5.1'; const cacheVersion = 'v1.6.0';
const cacheTitle = `pairdrop-cache-${cacheVersion}`; const cacheTitle = `pairdrop-cache-${cacheVersion}`;
const urlsToCache = [ const urlsToCache = [
'index.html', 'index.html',
@@ -71,30 +71,11 @@ const update = request =>
self.addEventListener('fetch', function(event) { self.addEventListener('fetch', function(event) {
if (event.request.method === "POST") { if (event.request.method === "POST") {
// Requests related to Web Share Target. // Requests related to Web Share Target.
event.respondWith( event.respondWith((async () => {
(async () => { let share_url = await evaluateRequestData(event.request);
const formData = await event.request.formData(); share_url = event.request.url + share_url;
const title = formData.get("title"); return Response.redirect(encodeURI(share_url), 302);
const text = formData.get("text"); })());
const url = formData.get("url");
const files = formData.get("files");
let share_url = "/";
if (files.length > 0) {
share_url = "/?share-target=files";
const db = await window.indexedDB.open('pairdrop_store');
const tx = db.transaction('share_target_files', 'readwrite');
const store = tx.objectStore('share_target_files');
for (let i=0; i<files.length; i++) {
await store.add(files[i]);
}
await tx.complete
db.close()
} else if (title.length > 0 || text.length > 0 || url.length) {
share_url = `/?share-target=text&title=${title}&text=${text}&url=${url}`;
}
return Response.redirect(encodeURI(share_url), 303);
})()
);
} else { } else {
// Regular requests not related to Web Share Target. // Regular requests not related to Web Share Target.
event.respondWith( event.respondWith(
@@ -119,3 +100,49 @@ self.addEventListener('activate', evt =>
}) })
) )
); );
const evaluateRequestData = async function (request) {
const formData = await request.formData();
const title = formData.get("title");
const text = formData.get("text");
const url = formData.get("url");
const files = formData.getAll("allfiles");
return new Promise(async (resolve) => {
if (files && files.length > 0) {
let fileObjects = [];
for (let i=0; i<files.length; i++) {
fileObjects.push({
name: files[i].name,
buffer: await files[i].arrayBuffer()
});
}
const DBOpenRequest = indexedDB.open('pairdrop_store');
DBOpenRequest.onsuccess = e => {
const db = e.target.result;
for (let i = 0; i < fileObjects.length; i++) {
const transaction = db.transaction('share_target_files', 'readwrite');
const objectStore = transaction.objectStore('share_target_files');
const objectStoreRequest = objectStore.add(fileObjects[i]);
objectStoreRequest.onsuccess = _ => {
if (i === fileObjects.length - 1) resolve('?share-target=files');
}
}
}
DBOpenRequest.onerror = _ => {
resolve('');
}
} else {
let share_url = '?share-target=text';
if (title) share_url += `&title=${title}`;
if (text) share_url += `&text=${text}`;
if (url) share_url += `&url=${url}`;
resolve(share_url);
}
});
}

View File

@@ -75,11 +75,75 @@ html {
} }
header { header {
position: relative; position: absolute;
height: 56px; align-items: baseline;
align-items: center; padding: 8px 16px;
padding: 16px;
box-sizing: border-box; box-sizing: border-box;
width: 100vw;
z-index: 2;
top: 0;
right: 0;
}
header > a,
header > div {
margin-left: 8px;
}
header > div {
display: flex;
flex-direction: column;
align-self: flex-start;
touch-action: manipulation;
}
header > div .icon-button {
height: 40px;
transition: all 300ms;
}
header > div > div {
display: flex;
flex-direction: column;
}
header > div:not(:hover) .icon-button:not(.selected) {
height: 0;
opacity: 0;
}
header > div:hover::before {
border-radius: 20px;
background: currentColor;
opacity: 0.1;
transition: opacity 300ms;
content: '';
position: absolute;
width: 40px;
top: 0;
bottom: 0;
margin-top: 8px;
margin-bottom: 8px;
}
header > div:hover .icon-button.selected::before {
opacity: 0.1;
}
@media (pointer: coarse) {
header > div:hover .icon-button.selected:hover::before {
opacity: 0.2;
}
header > div .icon-button:not(.selected) {
height: 0;
opacity: 0;
pointer-events: none;
}
header > div > div {
flex-direction: column-reverse;
}
} }
[hidden] { [hidden] {
@@ -192,15 +256,10 @@ x-noscript {
} }
} }
/* Main Header */
body>header a {
margin-left: 8px;
}
#center { #center {
position: relative; position: relative;
display: flex; display: flex;
margin-top: 56px;
flex-direction: column-reverse; flex-direction: column-reverse;
flex-grow: 1; flex-grow: 1;
--footer-height: 132px; --footer-height: 132px;
@@ -974,8 +1033,8 @@ button::-moz-focus-inner {
#about x-background { #about x-background {
position: absolute; position: absolute;
top: calc(32px - 250px); top: calc(28px - 250px);
right: calc(32px - 250px); right: calc(36px - 250px);
width: 500px; width: 500px;
height: 500px; height: 500px;
border-radius: 50%; border-radius: 50%;

View File

@@ -44,32 +44,46 @@
<use xlink:href="#info-outline" /> <use xlink:href="#info-outline" />
</svg> </svg>
</a> </a>
<a id="theme" class="icon-button" title="Switch Darkmode/Lightmode" > <div id="theme-wrapper">
<svg class="icon"> <div id="theme-auto" class="icon-button selected" title="Adapt to System" >
<use xlink:href="#icon-theme" /> <svg class="icon">
</svg> <use xlink:href="#icon-theme-auto" />
</a> </svg>
<a id="notification" class="icon-button" title="Enable Notifications" hidden> </div>
<div>
<div id="theme-light" class="icon-button" title="Always Light" >
<svg class="icon">
<use xlink:href="#icon-theme-light" />
</svg>
</div>
<div id="theme-dark" class="icon-button" title="Always Dark" >
<svg class="icon">
<use xlink:href="#icon-theme-dark" />
</svg>
</div>
</div>
</div>
<div id="notification" class="icon-button" title="Enable Notifications" hidden>
<svg class="icon"> <svg class="icon">
<use xlink:href="#notifications" /> <use xlink:href="#notifications" />
</svg> </svg>
</a> </div>
<a id="install" class="icon-button" title="Install PairDrop" hidden> <div id="install" class="icon-button" title="Install PairDrop" hidden>
<svg class="icon"> <svg class="icon">
<use xlink:href="#homescreen" /> <use xlink:href="#homescreen" />
</svg> </svg>
</a> </div>
<a id="pair-device" class="icon-button" title="Pair Device" hidden> <div id="pair-device" class="icon-button" title="Pair Device" hidden>
<svg class="icon"> <svg class="icon">
<use xlink:href="#pair-device-icon" /> <use xlink:href="#pair-device-icon" />
</svg> </svg>
</a> </div>
<a id="clear-pair-devices" class="icon-button" title="Clear All Paired Devices" hidden> <div id="clear-pair-devices" class="icon-button" title="Clear All Paired Devices" hidden>
<svg class="icon"> <svg class="icon">
<use xlink:href="#clear-pair-devices-icon" /> <use xlink:href="#clear-pair-devices-icon" />
</svg> </svg>
</a> </div>
<a id="cancel-paste-mode" class="button" hidden>Done</a> <div id="cancel-paste-mode" class="button" hidden>Done</div>
</header> </header>
<!-- Center --> <!-- Center -->
<div id="center"> <div id="center">
@@ -115,12 +129,12 @@
<div id="pair-instructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div> <div id="pair-instructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div>
<hr> <hr>
<div id="key-input-container"> <div id="key-input-container">
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-2" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-3" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-4" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-5" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled> <input type="tel" class="textarea center" aria-label="pair-key-6" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
</div> </div>
<div class="font-subheading center text-center">Enter key from another device to continue.</div> <div class="font-subheading center text-center">Enter key from another device to continue.</div>
<div class="center row-reverse"> <div class="center row-reverse">
@@ -209,7 +223,7 @@
<span class="display-name"></span> <span class="display-name"></span>
</div> </div>
<div class="row-separator"></div> <div class="row-separator"></div>
<div id="text-input" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div> <div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
<div class="center row-reverse"> <div class="center row-reverse">
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button> <button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
<button class="button" type="button" title="ESCAPE" close>Cancel</button> <button class="button" type="button" title="ESCAPE" close>Cancel</button>
@@ -331,7 +345,13 @@
<path d="M0 0h24v24H0z" fill="none" /> <path d="M0 0h24v24H0z" fill="none" />
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z" /> <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z" />
</symbol> </symbol>
<symbol id="icon-theme" viewBox="0 0 24 24"> <symbol id="icon-theme-auto" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-54 -54 620 620"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M448 256c0-106-86-192-192-192V448c106 0 192-86 192-192zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z"/></svg>
</symbol>
<symbol id="icon-theme-light" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-54 -54 620 620"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"/></svg>
</symbol>
<symbol id="icon-theme-dark" viewBox="0 0 24 24">
<rect fill="none" height="24" width="24"/><path d="M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36c-0.98,1.37-2.58,2.26-4.4,2.26 c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/> <rect fill="none" height="24" width="24"/><path d="M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36c-0.98,1.37-2.58,2.26-4.4,2.26 c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/>
</symbol> </symbol>
<symbol id="pair-device-icon" viewBox="0 0 640 512"> <symbol id="pair-device-icon" viewBox="0 0 640 512">
@@ -348,11 +368,11 @@
</symbol> </symbol>
</svg> </svg>
<!-- Scripts --> <!-- Scripts -->
<script src="scripts/util.js"></script> <script src="scripts/theme.js"></script>
<script src="scripts/network.js"></script> <script src="scripts/network.js"></script>
<script src="scripts/ui.js"></script> <script src="scripts/ui.js"></script>
<script src="scripts/theme.js" async></script> <script src="scripts/util.js" async></script>
<script src="scripts/qrcode.js" async></script> <script src="scripts/QRCode.min.js" async></script>
<script src="scripts/zip.min.js" async></script> <script src="scripts/zip.min.js" async></script>
<script src="scripts/NoSleep.min.js" async></script> <script src="scripts/NoSleep.min.js" async></script>
<!-- Sounds --> <!-- Sounds -->

View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@@ -1,39 +1,78 @@
(function(){ (function(){
// Select the button const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
const btnTheme = document.getElementById('theme'); const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)').matches;
// Check for dark mode preference at the OS level
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); const $themeAuto = document.getElementById('theme-auto');
const $themeLight = document.getElementById('theme-light');
// Get the user's theme preference from local storage, if it's available const $themeDark = document.getElementById('theme-dark');
const currentTheme = localStorage.getItem('theme');
// If the user's preference in localStorage is dark... let currentTheme = localStorage.getItem('theme');
if (currentTheme === 'dark') { if (currentTheme === 'dark') {
// ...let's toggle the .dark-theme class on the body setModeToDark();
document.body.classList.toggle('dark-theme');
// Otherwise, if the user's preference in localStorage is light...
} else if (currentTheme === 'light') { } else if (currentTheme === 'light') {
// ...let's toggle the .light-theme class on the body setModeToLight();
document.body.classList.toggle('light-theme');
} }
// Listen for a click on the button $themeAuto.addEventListener('click', _ => {
btnTheme.addEventListener('click', function(e) { if (currentTheme) {
e.preventDefault(); setModeToAuto();
// If the user's OS setting is dark and matches our .dark-theme class...
let theme;
if (prefersDarkScheme.matches) {
// ...then toggle the light mode class
document.body.classList.toggle('light-theme');
// ...but use .dark-theme if the .light-theme class is already on the body,
theme = document.body.classList.contains('light-theme') ? 'light' : 'dark';
} else { } else {
// Otherwise, let's do the same thing, but for .dark-theme setModeToDark();
document.body.classList.toggle('dark-theme'); }
theme = document.body.classList.contains('dark-theme') ? 'dark' : 'light'; });
$themeLight.addEventListener('click', _ => {
if (currentTheme !== 'light') {
setModeToLight();
} else {
setModeToAuto();
}
});
$themeDark.addEventListener('click', _ => {
if (currentTheme !== 'dark') {
setModeToDark();
} else {
setModeToLight();
} }
// Finally, let's save the current preference to localStorage to keep using it
localStorage.setItem('theme', theme);
}); });
function setModeToDark() {
document.body.classList.remove('light-theme');
document.body.classList.add('dark-theme');
localStorage.setItem('theme', 'dark');
currentTheme = 'dark';
$themeAuto.classList.remove("selected");
$themeLight.classList.remove("selected");
$themeDark.classList.add("selected");
}
function setModeToLight() {
document.body.classList.remove('dark-theme');
document.body.classList.add('light-theme');
localStorage.setItem('theme', 'light');
currentTheme = 'light';
$themeAuto.classList.remove("selected");
$themeLight.classList.add("selected");
$themeDark.classList.remove("selected");
}
function setModeToAuto() {
document.body.classList.remove('dark-theme');
document.body.classList.remove('light-theme');
if (prefersDarkTheme) {
document.body.classList.add('dark-theme');
} else if (prefersLightTheme) {
document.body.classList.add('light-theme');
}
localStorage.removeItem('theme');
currentTheme = undefined;
$themeAuto.classList.add("selected");
$themeLight.classList.remove("selected");
$themeDark.classList.remove("selected");
}
})(); })();

View File

@@ -1608,27 +1608,33 @@ class WebShareTargetUI {
let shareTargetText; let shareTargetText;
if (url) { if (url) {
shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable. shareTargetText = url; // we share only the link - no text.
} else if (title && text) { } else if (title && text) {
shareTargetText = title + '\r\n' + text; shareTargetText = title + '\r\n' + text;
} else { } else {
shareTargetText = title + text; shareTargetText = title + text;
} }
console.log('Shared Target Text:', '"' + shareTargetText + '"');
Events.fire('activate-paste-mode', {files: [], text: shareTargetText}) Events.fire('activate-paste-mode', {files: [], text: shareTargetText})
} else if (share_target_type === "files") { } else if (share_target_type === "files") {
const openRequest = window.indexedDB.open('pairdrop_store') let openRequest = window.indexedDB.open('pairdrop_store')
openRequest.onsuccess( db => { openRequest.onsuccess = e => {
const db = e.target.result;
const tx = db.transaction('share_target_files', 'readwrite'); const tx = db.transaction('share_target_files', 'readwrite');
const store = tx.objectStore('share_target_files'); const store = tx.objectStore('share_target_files');
const request = store.getAll(); const request = store.getAll();
request.onsuccess = _ => { request.onsuccess = _ => {
Events.fire('activate-paste-mode', {files: request.result, text: ""}) const fileObjects = request.result;
let filesReceived = [];
for (let i=0; i<fileObjects.length; i++) {
filesReceived.push(new File([fileObjects[i].buffer], fileObjects[i].name));
}
const clearRequest = store.clear() const clearRequest = store.clear()
clearRequest.onsuccess = _ => db.close(); clearRequest.onsuccess = _ => db.close();
Events.fire('activate-paste-mode', {files: filesReceived, text: ""})
} }
}) }
} }
window.history.replaceState({}, "Rewrite URL", '/'); window.history.replaceState({}, "Rewrite URL", '/');
} }
@@ -1685,7 +1691,7 @@ class PersistentStorage {
PersistentStorage.logBrowserNotCapable(); PersistentStorage.logBrowserNotCapable();
return; return;
} }
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 2); const DBOpenRequest = window.indexedDB.open('pairdrop_store', 3);
DBOpenRequest.onerror = (e) => { DBOpenRequest.onerror = (e) => {
PersistentStorage.logBrowserNotCapable(); PersistentStorage.logBrowserNotCapable();
console.log('Error initializing database: '); console.log('Error initializing database: ');
@@ -1711,7 +1717,10 @@ class PersistentStorage {
} }
try { try {
db.createObjectStore('share_target_files'); if (db.objectStoreNames.contains('share_target_files')) {
db.deleteObjectStore('share_target_files');
}
db.createObjectStore('share_target_files', {autoIncrement: true});
} catch (error) { } catch (error) {
console.log("Object store named 'share_target_files' already exists") console.log("Object store named 'share_target_files' already exists")
} }

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.5.1'; const cacheVersion = 'v1.6.0';
const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
const urlsToCache = [ const urlsToCache = [
'index.html', 'index.html',
@@ -71,30 +71,11 @@ const update = request =>
self.addEventListener('fetch', function(event) { self.addEventListener('fetch', function(event) {
if (event.request.method === "POST") { if (event.request.method === "POST") {
// Requests related to Web Share Target. // Requests related to Web Share Target.
event.respondWith( event.respondWith((async () => {
(async () => { let share_url = await evaluateRequestData(event.request);
const formData = await event.request.formData(); share_url = event.request.url + share_url;
const title = formData.get("title"); return Response.redirect(encodeURI(share_url), 302);
const text = formData.get("text"); })());
const url = formData.get("url");
const files = formData.get("files");
let share_url = "/";
if (files.length > 0) {
share_url = "/?share-target=files";
const db = await window.indexedDB.open('pairdrop_store');
const tx = db.transaction('share_target_files', 'readwrite');
const store = tx.objectStore('share_target_files');
for (let i=0; i<files.length; i++) {
await store.add(files[i]);
}
await tx.complete
db.close()
} else if (title.length > 0 || text.length > 0 || url.length) {
share_url = `/?share-target=text&title=${title}&text=${text}&url=${url}`;
}
return Response.redirect(encodeURI(share_url), 303);
})()
);
} else { } else {
// Regular requests not related to Web Share Target. // Regular requests not related to Web Share Target.
event.respondWith( event.respondWith(
@@ -119,3 +100,49 @@ self.addEventListener('activate', evt =>
}) })
) )
); );
const evaluateRequestData = async function (request) {
const formData = await request.formData();
const title = formData.get("title");
const text = formData.get("text");
const url = formData.get("url");
const files = formData.getAll("allfiles");
return new Promise(async (resolve) => {
if (files && files.length > 0) {
let fileObjects = [];
for (let i=0; i<files.length; i++) {
fileObjects.push({
name: files[i].name,
buffer: await files[i].arrayBuffer()
});
}
const DBOpenRequest = indexedDB.open('pairdrop_store');
DBOpenRequest.onsuccess = e => {
const db = e.target.result;
for (let i = 0; i < fileObjects.length; i++) {
const transaction = db.transaction('share_target_files', 'readwrite');
const objectStore = transaction.objectStore('share_target_files');
const objectStoreRequest = objectStore.add(fileObjects[i]);
objectStoreRequest.onsuccess = _ => {
if (i === fileObjects.length - 1) resolve('?share-target=files');
}
}
}
DBOpenRequest.onerror = _ => {
resolve('');
}
} else {
let share_url = '?share-target=text';
if (title) share_url += `&title=${title}`;
if (text) share_url += `&text=${text}`;
if (url) share_url += `&url=${url}`;
resolve(share_url);
}
});
}

View File

@@ -76,11 +76,75 @@ html {
} }
header { header {
position: relative; position: absolute;
height: 56px; align-items: baseline;
align-items: center; padding: 8px 16px;
padding: 16px;
box-sizing: border-box; box-sizing: border-box;
width: 100vw;
z-index: 2;
top: 0;
right: 0;
}
header > a,
header > div {
margin-left: 8px;
}
header > div {
display: flex;
flex-direction: column;
align-self: flex-start;
touch-action: manipulation;
}
header > div .icon-button {
height: 40px;
transition: all 300ms;
}
header > div > div {
display: flex;
flex-direction: column;
}
header > div:not(:hover) .icon-button:not(.selected) {
height: 0;
opacity: 0;
}
header > div:hover::before {
border-radius: 20px;
background: currentColor;
opacity: 0.1;
transition: opacity 300ms;
content: '';
position: absolute;
width: 40px;
top: 0;
bottom: 0;
margin-top: 8px;
margin-bottom: 8px;
}
header > div:hover .icon-button.selected::before {
opacity: 0.1;
}
@media (pointer: coarse) {
header > div:hover .icon-button.selected:hover::before {
opacity: 0.2;
}
header > div .icon-button:not(.selected) {
height: 0;
opacity: 0;
pointer-events: none;
}
header > div > div {
flex-direction: column-reverse;
}
} }
[hidden] { [hidden] {
@@ -193,15 +257,10 @@ x-noscript {
} }
} }
/* Main Header */
body>header a {
margin-left: 8px;
}
#center { #center {
position: relative; position: relative;
display: flex; display: flex;
margin-top: 56px;
flex-direction: column-reverse; flex-direction: column-reverse;
flex-grow: 1; flex-grow: 1;
--footer-height: 146px; --footer-height: 146px;

View File

@@ -3,14 +3,6 @@
"iceServers": [ "iceServers": [
{ {
"urls": "stun:stun.l.google.com:19302" "urls": "stun:stun.l.google.com:19302"
},
{
"urls": "stun:openrelay.metered.ca:80"
},
{
"urls": "turn:openrelay.metered.ca:443",
"username": "openrelayproject",
"credential": "openrelayproject"
} }
] ]
} }