mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2026-04-21 22:54:53 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59dca141b6 | ||
|
|
d0046e83cb | ||
|
|
2aeadb44e2 | ||
|
|
7827a47d29 | ||
|
|
4edc9c9b22 | ||
|
|
398a69d7a0 | ||
|
|
dfe69cc873 | ||
|
|
f5fde731b0 | ||
|
|
d6eee480b3 | ||
|
|
f6ad85a744 | ||
|
|
d50480b2f8 | ||
|
|
ac1e88b6a0 | ||
|
|
0fe36e132c | ||
|
|
ab08091f5d | ||
|
|
19a78a5239 | ||
|
|
d0b2c81582 | ||
|
|
10a0aaf896 | ||
|
|
34ebd60304 | ||
|
|
251df2fbff |
7
.github/workflows/github-image.yml
vendored
7
.github/workflows/github-image.yml
vendored
@@ -28,6 +28,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- 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
|
||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -57,9 +57,10 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop)
|
|||||||
* Change your display name permanently to easily differentiate your devices
|
* Change your display name permanently to easily differentiate your devices
|
||||||
* [Paste files/text and choose the recipient afterwords ](https://github.com/RobinLinus/snapdrop/pull/534)
|
* [Paste files/text and choose the recipient afterwords ](https://github.com/RobinLinus/snapdrop/pull/534)
|
||||||
* [Prevent devices from sleeping on file transfer](https://github.com/RobinLinus/snapdrop/pull/413)
|
* [Prevent devices from sleeping on file transfer](https://github.com/RobinLinus/snapdrop/pull/413)
|
||||||
* 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))
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
8
index.js
8
index.js
@@ -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
18
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
2
public_included_ws_fallback/scripts/robots.txt
Normal file
2
public_included_ws_fallback/scripts/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user