Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
77dcea14cc | ||
|
dcbbfde4a5 | ||
|
685c2dc210 | ||
|
3d67f6d5ae | ||
|
e4ca9882b9 | ||
|
9c4a886799 | ||
|
6839090631 | ||
|
1232eea297 | ||
|
b88509d210 | ||
|
2f95583af1 | ||
|
6f6ab38c9f | ||
|
10887b8358 | ||
|
4ce43bdac3 | ||
|
9947bfc3dc | ||
|
b68a85ac2f | ||
|
dfbb0492ac | ||
|
def3cd312d | ||
|
5539ae2c63 | ||
|
53bf782ddc | ||
|
01e9154a33 | ||
|
04ec30066d | ||
|
8a1b2ac65f | ||
|
316f6685e0 | ||
|
d26d82121c | ||
|
cb0cfbe640 | ||
|
31731d5f24 | ||
|
6fece01fa9 | ||
|
afedc14db5 | ||
|
9d337fa315 | ||
|
f04efda911 | ||
|
3a0941c67c | ||
|
08fbe470ad | ||
|
a82d5d67e2 | ||
|
52883d335d | ||
|
93ab0b9731 | ||
|
3e86319bb8 | ||
|
baa6e33fcd | ||
|
cdb9130a3d | ||
|
d7ad3a8812 | ||
|
c28640217b | ||
|
9e2bef0037 | ||
33eb3a931f | |||
6753ed7e79 |
@@ -2,9 +2,9 @@ name: Build and Release bsky-desktop
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ $default-branch ]
|
||||
branches: [$default-branch]
|
||||
pull_request:
|
||||
branches: [ $default-branch ]
|
||||
branches: [$default-branch]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -15,8 +15,6 @@ jobs:
|
||||
build-linux:
|
||||
name: Build bsky-desktop (Linux)
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
artifact: ${{ steps.upload-artifact.outputs.artifact }}
|
||||
env:
|
||||
ext: "AppImage"
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
@@ -39,6 +37,19 @@ jobs:
|
||||
- name: Build (arm64)
|
||||
run: npm run build -- --arch arm64
|
||||
|
||||
- name: Download appimagelint and its deps
|
||||
run: |
|
||||
sudo apt update && sudo apt install fuse -y
|
||||
wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage
|
||||
chmod +x appimagelint-x86_64.AppImage
|
||||
|
||||
- name: Check the appimage(s)
|
||||
run: ./appimagelint-x86_64.AppImage dist/*.AppImage
|
||||
|
||||
- name: Generate checksum
|
||||
run: |
|
||||
sha256sum dist/*.AppImage > dist/sha256sum.txt
|
||||
|
||||
- name: Upload Linux Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
id: upload-artifact
|
||||
@@ -47,12 +58,11 @@ jobs:
|
||||
path: |
|
||||
dist/*.AppImage
|
||||
dist/latest*.yml
|
||||
dist/sha256sum.txt
|
||||
|
||||
build-windows:
|
||||
name: Build bsky-desktop (Windows)
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
artifact: ${{ steps.upload-artifact.outputs.artifact }}
|
||||
env:
|
||||
ext: "exe"
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
@@ -74,6 +84,10 @@ jobs:
|
||||
|
||||
- name: Build (arm64)
|
||||
run: npm run build -- --arch arm64
|
||||
|
||||
- name: Generate checksum
|
||||
run: |
|
||||
sha256sum dist/*.exe > dist/sha256sum.txt
|
||||
|
||||
- name: Upload Windows Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -83,12 +97,11 @@ jobs:
|
||||
path: |
|
||||
dist/*.exe
|
||||
dist/latest*.yml
|
||||
dist/sha256sum.txt
|
||||
|
||||
build-macos:
|
||||
name: Build bsky-desktop (macOS)
|
||||
runs-on: macos-latest
|
||||
outputs:
|
||||
artifact: ${{ steps.upload-artifact.outputs.artifact }}
|
||||
env:
|
||||
ext: "dmg"
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
@@ -110,6 +123,10 @@ jobs:
|
||||
|
||||
- name: Build (arm64)
|
||||
run: npm run build -- --arch arm64
|
||||
|
||||
- name: Generate checksum
|
||||
run: |
|
||||
shasum -a 256 dist/*.dmg > dist/sha256sum.txt
|
||||
|
||||
- name: Upload macOS Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -119,22 +136,25 @@ jobs:
|
||||
path: |
|
||||
dist/*.dmg
|
||||
dist/latest*.yml
|
||||
dist/sha256sum.txt
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-linux, build-windows, build-macos]
|
||||
outputs:
|
||||
version_tag: ${{ steps.version.outputs.version }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get app version
|
||||
id: version
|
||||
uses: pchynoweth/action-get-npm-version@1.1.1
|
||||
|
||||
|
||||
- name: Download Linux Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -152,9 +172,13 @@ jobs:
|
||||
with:
|
||||
name: macos-artifacts
|
||||
path: dist/macos
|
||||
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R dist
|
||||
|
||||
- name: Combine checksums
|
||||
run: |
|
||||
cat dist/linux/sha256sum.txt dist/windows/sha256sum.txt dist/macos/sha256sum.txt > sha256sums.txt
|
||||
|
||||
- name: Upload Release
|
||||
id: create_release
|
||||
@@ -166,4 +190,49 @@ jobs:
|
||||
files: |
|
||||
dist/linux/*.AppImage
|
||||
dist/windows/*.exe
|
||||
dist/macos/*.dmg
|
||||
dist/macos/*.dmg
|
||||
sha256sums.txt
|
||||
|
||||
aur:
|
||||
name: Publish to AUR
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
env:
|
||||
AUR_TOKEN: ${{ secrets.AUR_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download linux artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-artifacts
|
||||
path: dist/linux
|
||||
|
||||
- name: List downloaded files
|
||||
run: ls -R dist
|
||||
|
||||
- name: Show content of sha256sum.txt
|
||||
run: cat dist/linux/sha256sum.txt
|
||||
|
||||
- name: Get app version
|
||||
id: version
|
||||
uses: pchynoweth/action-get-npm-version@1.1.1
|
||||
|
||||
- name: Extract checksum from sha256sum.txt and change build version
|
||||
run: |
|
||||
new_checksum=$(awk 'NR==1 { print $1 }' ./dist/linux/sha256sum.txt)
|
||||
sed -i "s|sha256sums=('SKIP' 'SKIP')|sha256sums=('$new_checksum' 'SKIP')|" ./build/arch-pkg/PKGBUILD
|
||||
sed -i "s/^pkgver=.*$/pkgver=${{ steps.version.outputs.version }}/" ./build/arch-pkg/PKGBUILD
|
||||
|
||||
- name: Publish AUR package
|
||||
uses: KSXGitHub/github-actions-deploy-aur@v3.0.1
|
||||
with:
|
||||
pkgname: bskydesktop
|
||||
pkgbuild: ./build/arch-pkg/PKGBUILD
|
||||
commit_username: ${{ secrets.AUR_USERNAME }}
|
||||
commit_email: ${{ secrets.AUR_EMAIL }}
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
commit_message: New Version
|
||||
ssh_keyscan_types: rsa,ecdsa,ed25519
|
43
README.md
43
README.md
@@ -1,3 +1,44 @@
|
||||
# Bsky Desktop
|
||||
|
||||
Bsky Desktop is an application for Bsky built using Electron. It allows users to manage their Bsky account and feeds from the app instead of the web.
|
||||
Bsky Desktop is an Electron-based application for Bsky that allows users to manage their accounts and feeds directly from the app, rather than through the web interface.
|
||||
|
||||
### Features:
|
||||
- Support for user styles (work in progress; currently only LESS preprocessor is supported)
|
||||
- Compatibility with both Manifest V2 and V3 Chrome extensions, though only a limited set of Chrome extension APIs are supported. For more information, visit: [Electron Extensions API Documentation](https://www.electronjs.org/docs/latest/api/extensions#supported-extensions-apis)
|
||||
|
||||
### Working on:
|
||||
- Auto updates (for all platforms)
|
||||
|
||||
### Build and release status:
|
||||
[](https://github.com/oxmc/bsky-desktop/actions/workflows/build-and-release.yml)
|
||||
|
||||
[](https://repology.org/project/bskydesktop/versions)
|
||||
|
||||
### Build Instructions for Bsky Desktop
|
||||
|
||||
To build and run Bsky Desktop locally, follow these steps:
|
||||
|
||||
1. **Clone the repository:**
|
||||
```sh
|
||||
git clone https://github.com/oxmc/bsky-desktop.git
|
||||
cd bsky-desktop
|
||||
```
|
||||
|
||||
2. **Install dependencies:**
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
**(Optional) Run the application locally:**
|
||||
If you want to test the application locally before building it, use the following command:
|
||||
```sh
|
||||
npm run start
|
||||
```
|
||||
This step is **not required for building** but is useful if you want to see the app in action during development.
|
||||
|
||||
3. **Build the application:**
|
||||
To compile the application, run:
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
This will generate the necessary files for the app.
|
13
build/arch-pkg/.SRCINFO
Normal file
13
build/arch-pkg/.SRCINFO
Normal file
@@ -0,0 +1,13 @@
|
||||
pkgbase = bskydesktop
|
||||
pkgdesc = Bluesky Desktop - A decentralized social networking client distributed as an AppImage
|
||||
pkgver = 1.0.8
|
||||
pkgrel = 1
|
||||
url = https://github.com/oxmc/bsky-desktop
|
||||
arch = x86_64
|
||||
arch = aarch64
|
||||
license = MIT
|
||||
makedepends = curl
|
||||
depends = fuse2
|
||||
options = !strip
|
||||
|
||||
pkgname = bskydesktop
|
82
build/arch-pkg/PKGBUILD
Normal file
82
build/arch-pkg/PKGBUILD
Normal file
@@ -0,0 +1,82 @@
|
||||
# Maintainer: GizzyUwU me@gizzy.pro
|
||||
# Maintainer: oxmc contact@oxmc.is-a.dev
|
||||
|
||||
pkgname=bskydesktop
|
||||
pkgver=""
|
||||
pkgrel=1
|
||||
pkgdesc="Bluesky Desktop - A decentralized social networking client distributed as an AppImage"
|
||||
arch=('x86_64' 'aarch64')
|
||||
url="https://github.com/oxmc/bsky-desktop"
|
||||
license=('AGPL-3.0-only')
|
||||
depends=('fuse2')
|
||||
makedepends=('curl')
|
||||
options=('!strip')
|
||||
icon_url="https://raw.githubusercontent.com/oxmc/bsky-desktop/refs/heads/main/src/ui/images/logo.png"
|
||||
icon_name="bsky-desktop.png"
|
||||
|
||||
prepare() {
|
||||
latest_tag=$(curl -s "https://api.github.com/repos/oxmc/bsky-desktop/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")')
|
||||
latest_sha256=$(curl -Ls "https://github.com/oxmc/bsky-desktop/releases/download/$latest_tag/sha256sums.txt" | grep "AppImage")
|
||||
echo "Latest release tag: $latest_tag"
|
||||
|
||||
case "$CARCH" in
|
||||
x86_64)
|
||||
appimage_name="bskyDesktop-${latest_tag:1}-linux-x64.AppImage"
|
||||
sha256sum=$(echo "$latest_sha256" | grep "x64" | cut -d' ' -f1)
|
||||
;;
|
||||
aarch64)
|
||||
appimage_name="bskyDesktop-${latest_tag:1}-linux-arm64.AppImage"
|
||||
sha256sum=$(echo "$latest_sha256" | grep "arm64" | cut -d' ' -f1)
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $CARCH"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
source=(
|
||||
"https://github.com/oxmc/bsky-desktop/releases/download/$latest_tag/$appimage_name"
|
||||
"$icon_url"
|
||||
)
|
||||
echo "AppImage source: ${source[0]}"
|
||||
sha256sums=("$sha256sum" 'SKIP')
|
||||
curl -L "${source[0]}" -o "$srcdir/bskyDesktop.appimage"
|
||||
curl -L "${source[1]}" -o "$srcdir/$icon_name"
|
||||
}
|
||||
|
||||
package() {
|
||||
appimage_dest="$pkgdir/opt/appimages/bsky-desktop"
|
||||
bin_dest="$pkgdir/usr/bin/bsky-desktop"
|
||||
desktop_file="$pkgdir/usr/share/applications/bsky-desktop.desktop"
|
||||
icon_dest="$pkgdir/usr/share/icons/hicolor/128x128/apps/$icon_name"
|
||||
|
||||
if [[ ! -f "$srcdir/bskyDesktop.appimage" ]]; then
|
||||
echo "Error: AppImage file not found: $srcdir/bskyDesktop.appimage"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$srcdir/$icon_name" ]]; then
|
||||
echo "Error: Icon file not found: $srcdir/$icon_name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -d "$pkgdir/opt/appimages"
|
||||
install -Dm755 "$srcdir/bskyDesktop.appimage" "$appimage_dest"
|
||||
|
||||
install -d "$(dirname "$bin_dest")"
|
||||
ln -sf "/opt/appimages/bsky-desktop" "$bin_dest"
|
||||
|
||||
install -d "$(dirname "$icon_dest")"
|
||||
install -Dm644 "$srcdir/$icon_name" "$icon_dest"
|
||||
|
||||
install -d "$(dirname "$desktop_file")"
|
||||
install -Dm644 /dev/stdin "$desktop_file" <<EOF
|
||||
[Desktop Entry]
|
||||
Name=Bluesky Desktop
|
||||
Comment=Bluesky Desktop Client
|
||||
Exec=/usr/bin/bsky-desktop %u
|
||||
Icon=bsky-desktop
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Network;Social;Application;
|
||||
EOF
|
||||
}
|
295
package-lock.json
generated
295
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bsky-desktop",
|
||||
"version": "1.0.6",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bsky-desktop",
|
||||
"version": "1.0.6",
|
||||
"version": "1.1.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.17",
|
||||
@@ -14,9 +14,11 @@
|
||||
"adm-zip": "^0.5.12",
|
||||
"axios": "^1.6.8",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"less": "^4.2.1",
|
||||
"log4js": "^6.9.1",
|
||||
"node-notifier": "^10.0.0",
|
||||
"semver": "^7.6.3",
|
||||
"usercss-meta": "^0.12.0",
|
||||
"v8-compile-cache": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1324,6 +1326,18 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-anything": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
|
||||
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-what": "^3.14.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
@@ -1711,6 +1725,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/errno": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
|
||||
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"prr": "~1.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"errno": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
@@ -2265,7 +2292,7 @@
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
@@ -2295,6 +2322,19 @@
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/image-size": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"image-size": "bin/image-size.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"license": "ISC",
|
||||
@@ -2343,6 +2383,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-what": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
|
||||
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"license": "MIT",
|
||||
@@ -2522,6 +2568,45 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/less": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/less/-/less-4.2.1.tgz",
|
||||
"integrity": "sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"copy-anything": "^2.0.1",
|
||||
"parse-node-version": "^1.0.1",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"lessc": "bin/lessc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"errno": "^0.1.1",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"image-size": "~0.5.0",
|
||||
"make-dir": "^2.1.0",
|
||||
"mime": "^1.4.1",
|
||||
"needle": "^3.1.0",
|
||||
"source-map": "~0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/less/node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
@@ -2604,6 +2689,30 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/matcher": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||
@@ -2723,6 +2832,23 @@
|
||||
"version": "2.1.2",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/needle": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
|
||||
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.3",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"needle": "bin/needle"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "1.7.2",
|
||||
"dev": true,
|
||||
@@ -2797,6 +2923,15 @@
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/parse-node-version": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
|
||||
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
@@ -2842,6 +2977,16 @@
|
||||
"version": "1.2.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/plist": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
||||
@@ -2894,6 +3039,13 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||
@@ -3063,7 +3215,7 @@
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sanitize-filename": {
|
||||
@@ -3080,7 +3232,7 @@
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
@@ -3189,7 +3341,7 @@
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -3466,6 +3618,12 @@
|
||||
"utf8-byte-length": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
@@ -3517,6 +3675,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/usercss-meta": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/usercss-meta/-/usercss-meta-0.12.0.tgz",
|
||||
"integrity": "sha512-zKrXCKdpeIwtVe87omxGo9URf+7mbozduMZEg79dmT4KB3XJwfIkEi/Uk0PcTwR/nZLtAK1+k7isgbGB/g6E7Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/utf8-byte-length": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||
@@ -4647,6 +4814,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"copy-anything": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
|
||||
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
|
||||
"requires": {
|
||||
"is-what": "^3.14.1"
|
||||
}
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
@@ -4914,6 +5089,15 @@
|
||||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||
"dev": true
|
||||
},
|
||||
"errno": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
|
||||
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"prr": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
@@ -5289,7 +5473,7 @@
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
@@ -5300,6 +5484,12 @@
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true
|
||||
},
|
||||
"image-size": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
|
||||
"optional": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"requires": {
|
||||
@@ -5328,6 +5518,11 @@
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true
|
||||
},
|
||||
"is-what": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
|
||||
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA=="
|
||||
},
|
||||
"is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"requires": {
|
||||
@@ -5462,6 +5657,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"less": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/less/-/less-4.2.1.tgz",
|
||||
"integrity": "sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==",
|
||||
"requires": {
|
||||
"copy-anything": "^2.0.1",
|
||||
"errno": "^0.1.1",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"image-size": "~0.5.0",
|
||||
"make-dir": "^2.1.0",
|
||||
"mime": "^1.4.1",
|
||||
"needle": "^3.1.0",
|
||||
"parse-node-version": "^1.0.1",
|
||||
"source-map": "~0.6.0",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
@@ -5527,6 +5747,24 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"matcher": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||
@@ -5605,6 +5843,16 @@
|
||||
"ms": {
|
||||
"version": "2.1.2"
|
||||
},
|
||||
"needle": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
|
||||
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"iconv-lite": "^0.6.3",
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "1.7.2",
|
||||
"dev": true,
|
||||
@@ -5656,6 +5904,11 @@
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"dev": true
|
||||
},
|
||||
"parse-node-version": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
|
||||
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA=="
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
@@ -5686,6 +5939,12 @@
|
||||
"pend": {
|
||||
"version": "1.2.0"
|
||||
},
|
||||
"pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||
"optional": true
|
||||
},
|
||||
"plist": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
||||
@@ -5724,6 +5983,12 @@
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
|
||||
"optional": true
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||
@@ -5851,7 +6116,7 @@
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"sanitize-filename": {
|
||||
"version": "1.6.3",
|
||||
@@ -5866,7 +6131,7 @@
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.6.3",
|
||||
@@ -5938,7 +6203,7 @@
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.21",
|
||||
@@ -6135,6 +6400,11 @@
|
||||
"utf8-byte-length": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
@@ -6167,6 +6437,11 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"usercss-meta": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/usercss-meta/-/usercss-meta-0.12.0.tgz",
|
||||
"integrity": "sha512-zKrXCKdpeIwtVe87omxGo9URf+7mbozduMZEg79dmT4KB3XJwfIkEi/Uk0PcTwR/nZLtAK1+k7isgbGB/g6E7Q=="
|
||||
},
|
||||
"utf8-byte-length": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||
|
36
package.json
36
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bsky-desktop",
|
||||
"version": "1.0.7",
|
||||
"version": "1.1.0",
|
||||
"description": "A desktop app of bsky.app",
|
||||
"main": "src/app/main.js",
|
||||
"scripts": {
|
||||
@@ -14,6 +14,20 @@
|
||||
"name": "oxmc",
|
||||
"email": "oxmc7769.mail@gmail.com"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "oxmc",
|
||||
"email": "contact@oxmc.is-a.dev"
|
||||
},
|
||||
{
|
||||
"name": "PlOszukiwaczDEV",
|
||||
"email": "ploszukiwacz1@duck.com"
|
||||
},
|
||||
{
|
||||
"name": "GizzyUwU",
|
||||
"email": "me@gizzy.pro"
|
||||
}
|
||||
],
|
||||
"license": "AGPL-3.0-only",
|
||||
"devDependencies": {
|
||||
"electron": "^29.4.6",
|
||||
@@ -25,9 +39,11 @@
|
||||
"adm-zip": "^0.5.12",
|
||||
"axios": "^1.6.8",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"less": "^4.2.1",
|
||||
"log4js": "^6.9.1",
|
||||
"node-notifier": "^10.0.0",
|
||||
"semver": "^7.6.3",
|
||||
"usercss-meta": "^0.12.0",
|
||||
"v8-compile-cache": "^2.3.0"
|
||||
},
|
||||
"build": {
|
||||
@@ -38,14 +54,28 @@
|
||||
],
|
||||
"artifactName": "bsky-desktop.${ext}",
|
||||
"mac": {
|
||||
"icon": "src/ui/images/logo.icns",
|
||||
"target": [
|
||||
"dmg",
|
||||
"pkg"
|
||||
],
|
||||
"icon": "build/icons/mac-icon.icns",
|
||||
"category": "Network"
|
||||
},
|
||||
"pkg": {
|
||||
"scripts": "build/mac-pkg/scripts",
|
||||
"installLocation": "/Applications",
|
||||
"allowAnywhere": true,
|
||||
"allowCurrentUserHome": true,
|
||||
"allowRootDirectory": true,
|
||||
"isVersionChecked": true,
|
||||
"isRelocatable": false,
|
||||
"overwriteAction": "upgrade"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"appimage"
|
||||
],
|
||||
"icon": "src/ui/images/logo.png",
|
||||
"icon": "src/ui/images/icons",
|
||||
"category": "Network"
|
||||
},
|
||||
"win": {
|
||||
|
157
src/app/main.js
157
src/app/main.js
@@ -1,4 +1,4 @@
|
||||
const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, ipcRenderer, Tray, Menu, protocol, session } = require("electron");
|
||||
const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, Tray, Menu, protocol, session } = require("electron");
|
||||
const electronremote = require("@electron/remote/main");
|
||||
//const asar = require('@electron/asar');
|
||||
const windowStateKeeper = require("electron-window-state");
|
||||
@@ -6,8 +6,9 @@ const { setupTitlebar, attachTitlebarToWindow } = require("./titlebar/main");
|
||||
const openAboutWindow = require("./about-window/src/index").default;
|
||||
const badge = require('./badge');
|
||||
const contextMenu = require('./context-menu');
|
||||
const asarUpdater = require('./utils/asarUpdater');
|
||||
//const loadCRX = require('./utils/loadCRX');
|
||||
const autoUpdater = require('./utils/auto-update');
|
||||
const loadCRX = require('./utils/loadCRX');
|
||||
const userStyles = require('./utils/userStyles');
|
||||
const log4js = require("log4js");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
@@ -36,6 +37,8 @@ global.paths = {
|
||||
temp: path.join(os.tmpdir(), global.appInfo.name),
|
||||
};
|
||||
global.paths.updateDir = path.join(global.paths.data, 'update');
|
||||
global.paths.extensions = path.join(global.paths.data, 'extensions');
|
||||
global.paths.userstyles = path.join(global.paths.data, 'userstyles');
|
||||
|
||||
// URLs:
|
||||
global.urls = {
|
||||
@@ -44,11 +47,11 @@ global.urls = {
|
||||
|
||||
// Settings urls:
|
||||
global.settings = {
|
||||
general: `${global.urls.main}/settings`,
|
||||
account: `${global.urls.main}/settings/account`,
|
||||
appearance: `${global.urls.main}/settings/appearance`,
|
||||
privacy: `${global.urls.main}/settings/privacy-and-security`,
|
||||
general: `${global.urls.main}/settings`
|
||||
};
|
||||
global.settings.account = `${global.settings.general}/account`;
|
||||
global.settings.appearance = `${global.settings.general}/appearance`;
|
||||
global.settings.privacy = `${global.settings.general}/privacy-and-security`;
|
||||
|
||||
// Badge options:
|
||||
const badgeOptions = {
|
||||
@@ -119,6 +122,18 @@ if (!fs.existsSync(global.paths.updateDir)) {
|
||||
fs.mkdirSync(global.paths.updateDir, { recursive: true });
|
||||
};
|
||||
|
||||
// Create extensions directory if it does not exist:
|
||||
if (!fs.existsSync(global.paths.extensions)) {
|
||||
logger.info("Creating Extensions Directory");
|
||||
fs.mkdirSync(global.paths.extensions, { recursive: true });
|
||||
};
|
||||
|
||||
// Create userstyles directory if it does not exist:
|
||||
if (!fs.existsSync(global.paths.userstyles)) {
|
||||
logger.info("Creating Userstyles Directory");
|
||||
fs.mkdirSync(global.paths.userstyles, { recursive: true });
|
||||
};
|
||||
|
||||
// improve performance on linux?
|
||||
if (process.platform !== "win32" && process.platform !== "darwin") {
|
||||
logger.log("Disabling Hardware Acceleration and Transparent Visuals");
|
||||
@@ -267,7 +282,7 @@ function createWindow() {
|
||||
};
|
||||
});
|
||||
PageView.webContents.setWindowOpenHandler(({ url }) => {
|
||||
new BrowserWindow({ show: true, autoHideMenuBar: true }).loadURL(url);
|
||||
new BrowserWindow({ show: true, autoHideMenuBar: true, icon: path.join(global.paths.app, 'ui', 'images', 'logo.png') }).loadURL(url);
|
||||
return { action: 'deny' };
|
||||
});
|
||||
// Log PageView navigations:
|
||||
@@ -287,6 +302,7 @@ function showAboutWindow() {
|
||||
//open_devtools: process.env.NODE_ENV !== 'production',
|
||||
use_version_info: [
|
||||
['Application Version', `${global.appInfo.version}`],
|
||||
['Contributors', packageJson.contributors.map((contributor) => contributor.name).join(', ')],
|
||||
],
|
||||
license: `MIT, GPL-2.0, GPL-3.0, ${global.appInfo.license}`,
|
||||
});
|
||||
@@ -370,7 +386,7 @@ function handleDeeplink(commandLine) {
|
||||
break;
|
||||
|
||||
case "notiftest":
|
||||
global.PageView.webContents.send('ui:notif', { title: 'Updater', message: 'Update downloaded', options: { position: 'topRight', timeout: 5000, layout: 2, color: 'blue' } });
|
||||
global.PageView.webContents.send('ui:notif', { title: 'Updater', message: 'Update downloaded', options: { izitoast: { position: 'topRight', timeout: 5000, layout: 2, color: 'blue' } } });
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -483,64 +499,7 @@ app.whenReady().then(() => {
|
||||
|
||||
// Initialize the updater:
|
||||
logger.log("Initializing Updater");
|
||||
asarUpdater.init();
|
||||
|
||||
// updater events:
|
||||
asarUpdater.on('available', (task) => {
|
||||
//console.log('Update availible for', task)
|
||||
logger.log("Update availible for", task.name);
|
||||
global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update', message: 'An update is available' }));
|
||||
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Available', subtitle: 'An update is available! Downloading...' });
|
||||
global.isUpdating = true;
|
||||
});
|
||||
asarUpdater.on('not-available', (task) => {
|
||||
//console.log('not-available', task);
|
||||
logger.log("No Updates Available for", task);
|
||||
});
|
||||
asarUpdater.on('progress', (task, p) => {
|
||||
console.log(task.name, p);
|
||||
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Downloading Update', subtitle: 'Downloading update...' });
|
||||
if (global.splash) global.splash.webContents.send('ui:progbar', { reason: 'update', prog: p });
|
||||
});
|
||||
asarUpdater.on('downloaded', (task) => {
|
||||
//console.log('downloaded', task);
|
||||
logger.log("Downloaded Update for,", task.name);
|
||||
global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update Downloaded', message: 'Restarting to apply update...' }));
|
||||
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Downloaded', subtitle: 'Restarting to apply update...' });
|
||||
});
|
||||
asarUpdater.on('completed', (manifest, tasks) => {
|
||||
console.log('completed', manifest, tasks);
|
||||
if (tasks.length === 0) {
|
||||
setTimeout(() => {
|
||||
logger.log("Quitting and Installing Update");
|
||||
asarUpdater.quitAndInstall();
|
||||
}, 5000);
|
||||
};
|
||||
//app.quit()
|
||||
});
|
||||
asarUpdater.on('error', (err) => {
|
||||
//console.error(err);
|
||||
logger.error(err);
|
||||
//app.quit()
|
||||
});
|
||||
|
||||
// Set the feed URL (only works in packaged app):
|
||||
if (app.isPackaged) {
|
||||
logger.log("Setting Feed URL for app.asar");
|
||||
asarUpdater.setFeedURL(path.join(global.paths.app_root), 'https://cdn.oxmc.me/internal/bsky-desktop/update/core');
|
||||
};
|
||||
|
||||
//Check for updates:
|
||||
logger.log("Checking for Updates");
|
||||
if (app.isPackaged) {
|
||||
const UPDATE_CHECK = 1000 * 60 * 60 * 4 // 4 hours
|
||||
setInterval(() => {
|
||||
//asarUpdater.checkForUpdates();
|
||||
}, UPDATE_CHECK);
|
||||
//asarUpdater.checkForUpdates();
|
||||
} else {
|
||||
logger.warn("Not checking for updates as app is not packaged");
|
||||
};
|
||||
autoUpdater();
|
||||
|
||||
// Handle ipc for render:
|
||||
ipcMain.on('close-app', (event, arg) => {
|
||||
@@ -549,6 +508,70 @@ app.whenReady().then(() => {
|
||||
|
||||
createWindow();
|
||||
createTray();
|
||||
|
||||
// Load extensions (.crx files):
|
||||
logger.log("Checking for extensions");
|
||||
const extensions = fs.readdirSync(global.paths.extensions).filter((file) => file.endsWith('.crx'));
|
||||
if (extensions.length > 0) {
|
||||
logger.log(`Unpacking ${extensions.length} extensions and loading them`);
|
||||
extensions.forEach((extension) => {
|
||||
loadCRX(path.join(global.paths.extensions, extension));
|
||||
});
|
||||
} else {
|
||||
// Check for unpacked extensions:
|
||||
const unpackedExtensions = fs.readdirSync(global.paths.extensions).filter((file) => fs.lstatSync(path.join(global.paths.extensions, file)).isDirectory());
|
||||
|
||||
// Check if the directory contains a manifest.json file
|
||||
unpackedExtensions.forEach((extension) => {
|
||||
const manifestPath = path.join(global.paths.extensions, extension, 'manifest.json');
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
logger.log(`Loading unpacked extension: ${extension}`);
|
||||
session.defaultSession.loadExtension(path.join(global.paths.extensions, extension)).then(({ id }) => {
|
||||
logger.log(`Extension loaded with ID: ${id}`);
|
||||
}).catch((error) => {
|
||||
logger.error(`Failed to load extension: ${error}`);
|
||||
});
|
||||
} else {
|
||||
logger.warn(`Skipping directory ${extension} as it does not contain a manifest.json file`);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Load userstyles
|
||||
logger.log("Checking for userstyles");
|
||||
const userStylesDir = path.join(global.paths.userstyles);
|
||||
if (fs.existsSync(userStylesDir)) {
|
||||
const files = fs.readdirSync(userStylesDir);
|
||||
if (files.length > 0) {
|
||||
const userStylePromises = files.map(async file => {
|
||||
const cssFile = path.join(userStylesDir, file);
|
||||
// Parse the CSS file
|
||||
try {
|
||||
const cssContent = fs.readFileSync(cssFile, 'utf-8');
|
||||
const result = await userStyles.parseCSS(cssContent);
|
||||
|
||||
logger.info(`Loaded userstyle: ${result.metadata.name}`);
|
||||
|
||||
// Compile the userstyle
|
||||
const compiled = await userStyles.compileStyle(result.css, result.metadata);
|
||||
|
||||
// Check if the site 'bsky.app' is defined
|
||||
if (compiled.sites && compiled.sites['bsky.app']) {
|
||||
// Apply the userstyle to the PageView
|
||||
await PageView.webContents.insertCSS(compiled.sites['bsky.app']);
|
||||
|
||||
logger.info(`Applied userstyle: ${result.metadata.name}`);
|
||||
} else {
|
||||
logger.warn(`Userstyle ${result.metadata.name} does not target 'bsky.app'`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error loading userstyle: ${file}`, error);
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all(userStylePromises);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.log("Failed to get singleInstanceLock, Quitting");
|
||||
app.quit();
|
||||
|
157
src/app/utils/auto-update.js
Normal file
157
src/app/utils/auto-update.js
Normal file
@@ -0,0 +1,157 @@
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const childProcess = require('child_process');
|
||||
const { app } = require('electron');
|
||||
const log4js = require('log4js');
|
||||
|
||||
// import the asarUpdater module
|
||||
const asarUpdater = require('./asarUpdater');
|
||||
|
||||
// Get system information
|
||||
const SystemInfo = require('./sysInfo');
|
||||
|
||||
// Get the current system platform
|
||||
const sys = new SystemInfo();
|
||||
|
||||
// Setup the logger
|
||||
const logger = log4js.getLogger("bskydesktop");
|
||||
|
||||
function asarUpdate() {
|
||||
asarUpdater.init();
|
||||
|
||||
// updater events:
|
||||
asarUpdater.on('available', (task) => {
|
||||
//console.log('Update availible for', task)
|
||||
logger.log("Update availible for", task.name);
|
||||
global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update', message: 'An update is available' }));
|
||||
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Available', subtitle: 'An update is available! Downloading...' });
|
||||
global.isUpdating = true;
|
||||
});
|
||||
asarUpdater.on('not-available', (task) => {
|
||||
//console.log('not-available', task);
|
||||
logger.log("No Updates Available for", task);
|
||||
});
|
||||
asarUpdater.on('progress', (task, p) => {
|
||||
console.log(task.name, p);
|
||||
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Downloading Update', subtitle: 'Downloading update...' });
|
||||
if (global.splash) global.splash.webContents.send('ui:progbar', { reason: 'update', prog: p });
|
||||
});
|
||||
asarUpdater.on('downloaded', (task) => {
|
||||
//console.log('downloaded', task);
|
||||
logger.log("Downloaded Update for,", task.name);
|
||||
global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update Downloaded', message: 'Restarting to apply update...' }));
|
||||
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Downloaded', subtitle: 'Restarting to apply update...' });
|
||||
});
|
||||
asarUpdater.on('completed', (manifest, tasks) => {
|
||||
console.log('completed', manifest, tasks);
|
||||
if (tasks.length === 0) {
|
||||
setTimeout(() => {
|
||||
logger.log("Quitting and Installing Update");
|
||||
asarUpdater.quitAndInstall();
|
||||
}, 5000);
|
||||
};
|
||||
//app.quit()
|
||||
});
|
||||
asarUpdater.on('error', (err) => {
|
||||
//console.error(err);
|
||||
logger.error(err);
|
||||
//app.quit()
|
||||
});
|
||||
|
||||
// Set the feed URL (only works in packaged app):
|
||||
if (app.isPackaged) {
|
||||
logger.log("Setting Feed URL for app.asar");
|
||||
asarUpdater.setFeedURL(path.join(global.paths.app_root), 'https://cdn.oxmc.me/internal/bsky-desktop/update/core');
|
||||
};
|
||||
|
||||
//Check for updates:
|
||||
logger.log("Checking for Updates");
|
||||
if (app.isPackaged) {
|
||||
const UPDATE_CHECK = 1000 * 60 * 60 * 4 // 4 hours
|
||||
setInterval(() => {
|
||||
//asarUpdater.checkForUpdates();
|
||||
}, UPDATE_CHECK);
|
||||
//asarUpdater.checkForUpdates();
|
||||
} else {
|
||||
logger.warn("Not checking for updates as app is not packaged");
|
||||
};
|
||||
}
|
||||
|
||||
function checkForUpdates() {
|
||||
// Current system information
|
||||
logger.log('Current system information:', sys.platform, sys.getVersion());
|
||||
|
||||
// Check if the current system is Windows
|
||||
if (sys.isWin()) {
|
||||
// Check if the system is before Windows 10
|
||||
if (sys.earlierThan('10.0.0')) {
|
||||
// Windows 10 and above are supported, but windows 7 and 8 are not supported
|
||||
logger.error('Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not updating...');
|
||||
} else {
|
||||
// Check for updates, and if there are updates, download and install them
|
||||
logger.log('Checking for updates (win)...');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the current system is macOS
|
||||
if (sys.isMac()) {
|
||||
let macArch = '';
|
||||
// Check the current version of macOS, and whether we can use the pkg installer
|
||||
if (sys.laterThan('10.0.0')) {
|
||||
// Check if system is after macOS 10 (11, 12, etc.)
|
||||
if (sys.laterThan('11.0.0')) {
|
||||
// macOS 11 and above support ARM64, check if system is ARM64
|
||||
logger.log('Checkking system architecture...');
|
||||
if (sys.isARM64()) {
|
||||
// System is ARM64 (mac-arm64)
|
||||
macArch = 'arm64';
|
||||
} else {
|
||||
// System is x64 (mac-x64)
|
||||
macArch = 'x64';
|
||||
}
|
||||
} else {
|
||||
// macOS 10 is mostly x64, but some versions are ARM64
|
||||
macArch = 'x64';
|
||||
}
|
||||
logger.log('System architecture:', macArch);
|
||||
// Check for updates, and if there are updates, download and install them
|
||||
logger.log('Checking for updates (mac)...');
|
||||
|
||||
// Run the .pkg installer
|
||||
const pkgPath = path.join(global.paths.updateDir, 'bsky-desktop.pkg');
|
||||
const command = `sudo installer -pkg ${pkgPath} -target /`;
|
||||
|
||||
// Spawn a new shell
|
||||
/*const shellProcess = spawn('sh', ['-c', command], {
|
||||
stdio: 'inherit', // Pipe input/output to/from the shell
|
||||
});
|
||||
|
||||
shellProcess.on('error', (err) => {
|
||||
console.error('Failed to spawn shell:', err);
|
||||
});
|
||||
|
||||
shellProcess.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log('Update installed successfully.');
|
||||
} else {
|
||||
console.error(`Shell process exited with code ${code}.`);
|
||||
}
|
||||
});*/
|
||||
} else {
|
||||
// macOS versions before 10 are not supported
|
||||
logger.error('macOS versions before 10 are not supported, not updating...');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the current system is Linux
|
||||
if (sys.isLinux()) {
|
||||
// Check for updates, and if there are updates, download and install them (no system version check)
|
||||
// Linux versions use AppImage, so we instead need to check for a new asar file so that the app can
|
||||
// load the new asar instead of the packaged one (or even just delete the old appimage and download a new one)
|
||||
logger.log('Checking for updates (linux)...');
|
||||
asarUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = checkForUpdates;
|
@@ -3,30 +3,91 @@ const path = require('path');
|
||||
const { session } = require('electron');
|
||||
const AdmZip = require('adm-zip');
|
||||
|
||||
/**
|
||||
* Converts a CRX file buffer to a ZIP buffer.
|
||||
* @param {Buffer} buf - The CRX file buffer.
|
||||
* @returns {Buffer} - The ZIP buffer extracted from the CRX file.
|
||||
*/
|
||||
function crxToZip(buf) {
|
||||
function calcLength(a, b, c, d) {
|
||||
let length = 0;
|
||||
length += a << 0;
|
||||
length += b << 8;
|
||||
length += c << 16;
|
||||
length += (d << 24) >>> 0;
|
||||
return length;
|
||||
}
|
||||
|
||||
// Check if the file is already a ZIP file
|
||||
if (buf[0] === 80 && buf[1] === 75 && buf[2] === 3 && buf[3] === 4) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Validate CRX magic number
|
||||
if (buf[0] !== 67 || buf[1] !== 114 || buf[2] !== 50 || buf[3] !== 52) {
|
||||
throw new Error('Invalid CRX file: Missing Cr24 magic number');
|
||||
}
|
||||
|
||||
const version = buf[4];
|
||||
const isV2 = version === 2;
|
||||
const isV3 = version === 3;
|
||||
|
||||
if ((!isV2 && !isV3) || buf[5] || buf[6] || buf[7]) {
|
||||
throw new Error('Unsupported CRX format version.');
|
||||
}
|
||||
|
||||
if (isV2) {
|
||||
const publicKeyLength = calcLength(buf[8], buf[9], buf[10], buf[11]);
|
||||
const signatureLength = calcLength(buf[12], buf[13], buf[14], buf[15]);
|
||||
const zipStartOffset = 16 + publicKeyLength + signatureLength;
|
||||
return buf.slice(zipStartOffset);
|
||||
}
|
||||
|
||||
const headerSize = calcLength(buf[8], buf[9], buf[10], buf[11]);
|
||||
const zipStartOffset = 12 + headerSize;
|
||||
return buf.slice(zipStartOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks a .crx file and loads it as an Electron extension.
|
||||
* @param {string} crxPath - Path to the .crx file.
|
||||
* @returns {Promise<string>} - Resolves with the extension ID after loading.
|
||||
*/
|
||||
async function loadCRX(crxPath) {
|
||||
const outputDir = path.join(__dirname, 'extensions', path.basename(crxPath, '.crx'));
|
||||
const outputDir = path.join(global.paths.extensions, path.basename(crxPath, '.crx'));
|
||||
|
||||
// Ensure the output directory exists
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
// Extract the .crx file
|
||||
// Read the CRX file
|
||||
const crxData = fs.readFileSync(crxPath);
|
||||
const crxHeaderSize = crxData.readUInt32LE(8); // Extract header size from CRX
|
||||
const zipData = crxData.slice(crxHeaderSize);
|
||||
|
||||
// Save the ZIP content
|
||||
// Convert CRX to ZIP
|
||||
const zipData = crxToZip(crxData);
|
||||
|
||||
// Extract ZIP using AdmZip
|
||||
const zip = new AdmZip(zipData);
|
||||
zip.extractAllTo(outputDir, true);
|
||||
zip.getEntries().forEach((entry) => {
|
||||
const fullPath = path.join(outputDir, entry.entryName);
|
||||
|
||||
if (entry.isDirectory) {
|
||||
fs.mkdirSync(fullPath, { recursive: true });
|
||||
} else {
|
||||
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||
fs.writeFileSync(fullPath, entry.getData());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load the unpacked extension into Electron
|
||||
try {
|
||||
// Check for manifest.json
|
||||
const manifestPath = path.join(outputDir, 'manifest.json');
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
throw new Error('Extension is missing manifest.json');
|
||||
};
|
||||
// Load the extension
|
||||
const { id } = await session.defaultSession.loadExtension(outputDir);
|
||||
console.log(`Extension loaded with ID: ${id}`);
|
||||
return id;
|
||||
|
@@ -5,6 +5,7 @@ class SystemInfo {
|
||||
constructor() {
|
||||
this.platform = os.platform(); // 'win32', 'darwin', 'linux'
|
||||
this.release = os.release(); // OS version
|
||||
this.arch = os.arch(); // 'arm', 'arm64', 'x64', 'x86'
|
||||
this.versionInfo = this._getVersionInfo(); // Parsed version
|
||||
}
|
||||
|
||||
@@ -23,6 +24,26 @@ class SystemInfo {
|
||||
return this.platform === 'linux';
|
||||
}
|
||||
|
||||
// Check if current system architecture is ARM
|
||||
isARM() {
|
||||
return this.arch === 'arm';
|
||||
}
|
||||
|
||||
// Check if current system architecture is ARM64
|
||||
isARM64() {
|
||||
return this.arch === 'arm64';
|
||||
}
|
||||
|
||||
// Check if current system architecture is x64
|
||||
isX64() {
|
||||
return this.arch === 'x64';
|
||||
}
|
||||
|
||||
// Check if current system architecture is x86
|
||||
isX86() {
|
||||
return this.arch === 'x86';
|
||||
}
|
||||
|
||||
// Compare if current version is later than the given version
|
||||
laterThan(compareVersion) {
|
||||
const current = this.versionInfo;
|
||||
@@ -35,6 +56,11 @@ class SystemInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare if current version is earlier than the given version
|
||||
earlierThan(compareVersion) {
|
||||
return !this.laterThan(compareVersion);
|
||||
}
|
||||
|
||||
// Private: Parse version strings (e.g., "10.0.19045" -> [10, 0, 19045])
|
||||
_parseVersion(version) {
|
||||
return version.split('.').map((num) => parseInt(num, 10) || 0);
|
||||
@@ -57,13 +83,20 @@ class SystemInfo {
|
||||
return [0, 0, 0]; // Unknown system
|
||||
}
|
||||
}
|
||||
|
||||
// Get current version as a string (e.g., "10.15.7")
|
||||
getVersion() {
|
||||
return this.versionInfo.join('.');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SystemInfo;
|
||||
|
||||
// Usage Example
|
||||
//const sys = new SystemInfo();
|
||||
//console.log(`Is Windows: ${sys.isWin()}`);
|
||||
//console.log(`Is macOS: ${sys.isMac()}`);
|
||||
//console.log(`Is Linux: ${sys.isLinux()}`);
|
||||
//console.log(`Current Version Info: ${sys.versionInfo.join('.')}`);
|
||||
//console.log(`Current Version Info: ${sys.getVersion()}`);
|
||||
//console.log(`Later than 10.0.19044: ${sys.laterThan('10.0.19044')}`);
|
||||
//console.log(`Later than 5.15.0 (Linux Kernel): ${sys.laterThan('5.15.0')}`);
|
210
src/app/utils/userStyles.js
Normal file
210
src/app/utils/userStyles.js
Normal file
@@ -0,0 +1,210 @@
|
||||
const usercssMeta = require('usercss-meta');
|
||||
const less = require('less');
|
||||
|
||||
/**
|
||||
* Extracts all global variable and mixin definitions from CSS.
|
||||
* @param {string} css - The CSS string.
|
||||
* @returns {string} The extracted global definitions.
|
||||
*/
|
||||
function extractGlobalDefinitions(css) {
|
||||
const globalDefinitionRegex = /(@[\w-]+\s*(?:{[^}]*}|;))/g;
|
||||
return (css.match(globalDefinitionRegex) || []).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts variable definitions from metadata.
|
||||
* @param {object} metadata - Metadata containing variable definitions.
|
||||
* @returns {object} A map of variable names to their default values.
|
||||
*/
|
||||
function extractMetadataVars(metadata) {
|
||||
if (!metadata?.vars) return {};
|
||||
return Object.fromEntries(
|
||||
Object.entries(metadata.vars).map(([key, value]) => [key, value.default || value.value || null])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts content enclosed within matching braces starting from a given position.
|
||||
* @param {string} css - The CSS string.
|
||||
* @param {number} startPos - The starting position to search for braces.
|
||||
* @returns {object|null} The content and ending position of the matched braces.
|
||||
*/
|
||||
function extractBracedContent(css, startPos) {
|
||||
const braceMatch = matchBraces(css, startPos);
|
||||
if (!braceMatch) return null;
|
||||
|
||||
return {
|
||||
content: css.substring(braceMatch.start + 1, braceMatch.end - 1).trim(),
|
||||
end: braceMatch.end,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a pair of braces in a string starting from a given position.
|
||||
* @param {string} content - The string content.
|
||||
* @param {number} start - The starting position to search for braces.
|
||||
* @returns {object|null} The start and end positions of the matched braces.
|
||||
*/
|
||||
function matchBraces(content, start) {
|
||||
const openBrace = content.indexOf('{', start);
|
||||
if (openBrace === -1) return null;
|
||||
|
||||
let braceCount = 1, pos = openBrace + 1;
|
||||
while (braceCount > 0 && pos < content.length) {
|
||||
if (content[pos] === '{') braceCount++;
|
||||
if (content[pos] === '}') braceCount--;
|
||||
pos++;
|
||||
}
|
||||
|
||||
return braceCount === 0 ? { start: openBrace, end: pos } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses domain rules from the provided CSS string.
|
||||
* @param {string} css - The CSS string.
|
||||
* @param {number} startPos - The starting position to search for domain rules.
|
||||
* @returns {object|null} The domains and the rule start position.
|
||||
*/
|
||||
function parseDomainRule(css, startPos) {
|
||||
const domainRuleRegex = /@-moz-document\s+domain\(\s*'([^']+)'(?:\s*,\s*'([^']+)')*\s*\)/g;
|
||||
domainRuleRegex.lastIndex = startPos;
|
||||
const match = domainRuleRegex.exec(css);
|
||||
if (!match) return null;
|
||||
|
||||
const domains = match[0]
|
||||
.match(/'[^']+'/g) // Extract all single-quoted domain values
|
||||
.map(domain => domain.replace(/'/g, '').trim());
|
||||
|
||||
return {
|
||||
domains,
|
||||
ruleStart: match.index + match[0].length - 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses @-moz-document rules and extracts domain-specific CSS.
|
||||
* @param {string} css - The CSS string.
|
||||
* @returns {object} A map of domains to their associated CSS.
|
||||
*/
|
||||
function parseMozRules(css) {
|
||||
const rules = {};
|
||||
let currentPos = 0;
|
||||
|
||||
const globalCode = extractGlobalDefinitions(css);
|
||||
|
||||
while (true) {
|
||||
const domainRule = parseDomainRule(css, currentPos);
|
||||
if (!domainRule) break;
|
||||
|
||||
const bracedContent = extractBracedContent(css, domainRule.ruleStart);
|
||||
if (!bracedContent) break;
|
||||
|
||||
// Add the CSS to all matched domains
|
||||
for (const domain of domainRule.domains) {
|
||||
rules[domain] = `${globalCode}\n${bracedContent.content}`;
|
||||
}
|
||||
|
||||
currentPos = bracedContent.end;
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses metadata from the provided CSS string.
|
||||
* @param {string} css - The CSS string.
|
||||
* @returns {object} Parsed metadata.
|
||||
*/
|
||||
function parseCSS(css) {
|
||||
try {
|
||||
const normalizedCss = css.replace(/\r\n?/g, '\n');
|
||||
const nocommentsCss = normalizedCss.replace(/\/\*[\s\S]*?\*\//g, ''); // Remove comments
|
||||
return {
|
||||
...usercssMeta.parse(normalizedCss),
|
||||
css: nocommentsCss,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse CSS metadata: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles CSS code with a preprocessor if specified in the metadata.
|
||||
* @param {string} code - The CSS code.
|
||||
* @param {object} metadata - Metadata containing preprocessor information.
|
||||
* @param {object} [userVars={}] - User-defined variables to override defaults.
|
||||
* @returns {Promise<{compiledCss: string, sites: object}>} The compiled CSS code and domain-specific mapping.
|
||||
*/
|
||||
async function compileStyle(code, metadata, userVars = {}) {
|
||||
try {
|
||||
// Extract and merge variables
|
||||
const vars = {
|
||||
...extractMetadataVars(metadata),
|
||||
...userVars
|
||||
};
|
||||
|
||||
// Generate full code with user variables
|
||||
const fullCode = [
|
||||
'// User variables',
|
||||
Object.entries(vars).map(([key, value]) => `@${key}: ${value};`).join('\n'),
|
||||
'// Main code',
|
||||
code
|
||||
].join('\n\n');
|
||||
|
||||
let compiledCode;
|
||||
|
||||
switch (metadata?.preprocessor?.toLowerCase()) {
|
||||
case 'less':
|
||||
compiledCode = await compileLess(fullCode);
|
||||
break;
|
||||
default:
|
||||
compiledCode = code; // Return unmodified for unknown preprocessors
|
||||
}
|
||||
|
||||
// Parse domain rules
|
||||
const domainRules = parseMozRules(compiledCode);
|
||||
|
||||
// Compile each domain's CSS if needed
|
||||
const compiledRules = {};
|
||||
for (const [domain, css] of Object.entries(domainRules)) {
|
||||
if (metadata.preprocessor === 'less') {
|
||||
compiledRules[domain] = await compileLess(css, vars);
|
||||
} else {
|
||||
compiledRules[domain] = css;
|
||||
}
|
||||
}
|
||||
|
||||
// Combine all CSS
|
||||
const combinedCss = Object.entries(compiledRules)
|
||||
.map(([domain, compiledCss]) => `/* ${domain} */\n${compiledCss}`)
|
||||
.join('\n\n');
|
||||
|
||||
return {
|
||||
compiledCss: combinedCss,
|
||||
sites: compiledRules // Map of domains to their CSS
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Style compilation error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles LESS code to CSS.
|
||||
* @param {string} code - The LESS code.
|
||||
* @returns {Promise<string>} The compiled CSS.
|
||||
*/
|
||||
async function compileLess(code) {
|
||||
const { css } = await less.render(code, {
|
||||
math: 'parens-division',
|
||||
javascriptEnabled: true,
|
||||
compress: false
|
||||
});
|
||||
return css;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseCSS,
|
||||
compileStyle
|
||||
};
|
BIN
src/ui/images/icons/128x128.png
Normal file
BIN
src/ui/images/icons/128x128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
src/ui/images/icons/16x16.png
Normal file
BIN
src/ui/images/icons/16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 538 B |
BIN
src/ui/images/icons/256x256.png
Normal file
BIN
src/ui/images/icons/256x256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
BIN
src/ui/images/icons/32x32.png
Normal file
BIN
src/ui/images/icons/32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 939 B |
BIN
src/ui/images/icons/48x48.png
Normal file
BIN
src/ui/images/icons/48x48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
src/ui/images/icons/64x64.png
Normal file
BIN
src/ui/images/icons/64x64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
1
src/ui/lib/confetti-1.9.3-browser.min.js
vendored
Normal file
1
src/ui/lib/confetti-1.9.3-browser.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
src/ui/lib/css/stylelint-bundle.min.js
vendored
Normal file
11
src/ui/lib/css/stylelint-bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
63
src/ui/lib/css/stylus-renderer.min.js
vendored
Normal file
63
src/ui/lib/css/stylus-renderer.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -47,10 +47,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Load jQuery first
|
||||
loadScript('ui:///lib/jquery-3.3.1.min.js', () => {
|
||||
// Load iziToast after jQuery
|
||||
// Load confetti and iziToast after jQuery (ui libs)
|
||||
loadScript('ui:///lib/confetti-1.9.3-browser.min.js');
|
||||
loadScript('ui:///lib/izitoast.min.js', () => {
|
||||
// Load app specific scripts
|
||||
loadScript('ui:///rend/register-handles.js');
|
||||
// Load custom implementations
|
||||
loadScript('ui:///rend/bsky-ext.js');
|
||||
loadScript('ui:///rend/specialAnimations.js');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -374,6 +374,15 @@ const BskyExt = {
|
||||
},
|
||||
"regex": /discord\.com\/invite\/([a-zA-Z0-9_]+)/
|
||||
},
|
||||
"discord_profile": {
|
||||
"name": "Discord",
|
||||
"type": "social",
|
||||
"icon": "fab fa-discord",
|
||||
"brand": {
|
||||
"color": "#7289DA",
|
||||
},
|
||||
"regex": /discord\.com\/users\/([a-zA-Z0-9_]+)/
|
||||
},
|
||||
"discord.gg": {
|
||||
"name": "Discord",
|
||||
"type": "messaging",
|
||||
@@ -473,6 +482,33 @@ const BskyExt = {
|
||||
},
|
||||
"regex": /irc:\/\/([a-zA-Z0-9_]+)/
|
||||
},
|
||||
"itchio": {
|
||||
"name": "Itch.io",
|
||||
"type": "content",
|
||||
"icon": "fab fa-itch-io",
|
||||
"brand": {
|
||||
"color": "#FA5C5C",
|
||||
},
|
||||
"regex": /([a-zA-Z0-9_]+)\.itch\.io/
|
||||
},
|
||||
"etsy_shop": {
|
||||
"name": "Etsy",
|
||||
"type": "content",
|
||||
"icon": "fab fa-etsy",
|
||||
"brand": {
|
||||
"color": "#D5641C",
|
||||
},
|
||||
"regex": /etsy\.com\/shop\/([a-zA-Z0-9_]+)/
|
||||
},
|
||||
"etsy_user": {
|
||||
"name": "Etsy",
|
||||
"type": "content",
|
||||
"icon": "fab fa-etsy",
|
||||
"brand": {
|
||||
"color": "#D5641C",
|
||||
},
|
||||
"regex": /([a-zA-Z0-9_]+)\.etsy\.com/
|
||||
},
|
||||
"email": {
|
||||
"name": "Email",
|
||||
"type": "messaging",
|
||||
@@ -498,7 +534,43 @@ const BskyExt = {
|
||||
"brand": {
|
||||
"color": "#1DA1F2",
|
||||
},
|
||||
"regex": /twitter\.com\/([a-zA-Z0-9_]+)/
|
||||
"regex": /(twitter\.com|x\.com)\/([a-zA-Z0-9_]+)\/?$/
|
||||
},
|
||||
"xbox": {
|
||||
"name": "Xbox",
|
||||
"type": "social",
|
||||
"icon": "fab fa-xbox",
|
||||
"brand": {
|
||||
"color": "#107C10",
|
||||
},
|
||||
"regex": /xbox\.com\/([a-zA-Z0-9_]+)/
|
||||
},
|
||||
"playstation": {
|
||||
"name": "PlayStation",
|
||||
"type": "social",
|
||||
"icon": "fab fa-playstation",
|
||||
"brand": {
|
||||
"color": "#003087",
|
||||
},
|
||||
"regex": /playstation\.com\/([a-zA-Z0-9_]+)/
|
||||
},
|
||||
"steam": {
|
||||
"name": "Steam",
|
||||
"type": "content",
|
||||
"icon": "fab fa-steam",
|
||||
"brand": {
|
||||
"color": "#000000",
|
||||
},
|
||||
"regex": /steamcommunity\.com\/id\/([a-zA-Z0-9_]+)/
|
||||
},
|
||||
"tiktok": {
|
||||
"name": "TikTok",
|
||||
"type": "social",
|
||||
"icon": "fab fa-tiktok",
|
||||
"brand": {
|
||||
"color": "#000000",
|
||||
},
|
||||
"regex": /tiktok\.com\/@([a-zA-Z0-9_]+)/
|
||||
},
|
||||
"instagram": {
|
||||
"name": "Instagram",
|
||||
@@ -729,6 +801,72 @@ const BskyExt = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @constant {Object} linkStylesOverrides - Style overrides for specific link types
|
||||
*/
|
||||
linkStylesOverrides: {
|
||||
"bluesky": {
|
||||
"default": {
|
||||
"color": "rgb(0, 133, 255)",
|
||||
"background": "#0000",
|
||||
"border": "#0000",
|
||||
},
|
||||
"hover": {
|
||||
"color": "#fff",
|
||||
"background": "#0000",
|
||||
"border": "#0000"
|
||||
}
|
||||
},
|
||||
"discord": {
|
||||
"default": {
|
||||
"color": "#FFFFFF",
|
||||
"background": "#7289DA",
|
||||
"border": "#7289DA",
|
||||
},
|
||||
"hover": {
|
||||
"color": "#7289DA",
|
||||
"background": "#FFFFFF",
|
||||
"border": "#7289DA"
|
||||
}
|
||||
},
|
||||
"linktree": {
|
||||
"default": {
|
||||
"color": "#FFFFFF",
|
||||
"background": "#39e09b",
|
||||
"border": "#39e09b",
|
||||
},
|
||||
"hover": {
|
||||
"color": "#39e09b",
|
||||
"background": "#FFFFFF",
|
||||
"border": "#39e09b"
|
||||
}
|
||||
},
|
||||
"carrd": {
|
||||
"default": {
|
||||
"color": "#FFFFFF",
|
||||
"background": "#2C2F33",
|
||||
"border": "#2C2F33",
|
||||
},
|
||||
"hover": {
|
||||
"color": "#2C2F33",
|
||||
"background": "#FFFFFF",
|
||||
"border": "#2C2F33"
|
||||
}
|
||||
},
|
||||
"dribbble": {
|
||||
"default": {
|
||||
"color": "#FFFFFF",
|
||||
"background": "#EA4C89",
|
||||
"border": "#EA4C89",
|
||||
},
|
||||
"hover": {
|
||||
"color": "#EA4C89",
|
||||
"background": "#FFFFFF",
|
||||
"border": "#EA4C89"
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @constant {Object} buttonStyles - The button styles for the supported messaging apps
|
||||
*
|
||||
@@ -1109,7 +1247,7 @@ const BskyExt = {
|
||||
let header = bio.closest("[data-testid='profileView']");
|
||||
|
||||
// Get the profile header button group element
|
||||
let buttonGroup = header.querySelector('[role="button"]').parentElement;
|
||||
let buttonGroup = header.querySelector("div.css-175oi2r.r-2llsf > div:nth-child(1) > div > div:nth-child(2) > div.css-175oi2r.r-12vffkv");
|
||||
|
||||
// Check if the profile header button group element exists
|
||||
if (!buttonGroup) {
|
||||
|
@@ -20,7 +20,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
message: event.message,
|
||||
position: 'topRight',
|
||||
timeout: 5000,
|
||||
...event.options,
|
||||
...event.options.izitoast,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to display notification:', error);
|
||||
|
109
src/ui/rend/specialAnimations.js
Normal file
109
src/ui/rend/specialAnimations.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const anims = {
|
||||
imgs: {
|
||||
// tree shape from https://thenounproject.com/icon/pine-tree-1471679/
|
||||
tree: confetti.shapeFromPath({
|
||||
path: 'M120 240c-41,14 -91,18 -120,1 29,-10 57,-22 81,-40 -18,2 -37,3 -55,-3 25,-14 48,-30 66,-51 -11,5 -26,8 -45,7 20,-14 40,-30 57,-49 -13,1 -26,2 -38,-1 18,-11 35,-25 51,-43 -13,3 -24,5 -35,6 21,-19 40,-41 53,-67 14,26 32,48 54,67 -11,-1 -23,-3 -35,-6 15,18 32,32 51,43 -13,3 -26,2 -38,1 17,19 36,35 56,49 -19,1 -33,-2 -45,-7 19,21 42,37 67,51 -19,6 -37,5 -56,3 25,18 53,30 82,40 -30,17 -79,13 -120,-1l0 41 -31 0 0 -41z',
|
||||
matrix: [0.03597122302158273, 0, 0, 0.03597122302158273, -4.856115107913669, -5.071942446043165]
|
||||
}),
|
||||
// pumpkin shape from https://thenounproject.com/icon/pumpkin-5253388/
|
||||
pumpkin: confetti.shapeFromPath({
|
||||
path: 'M449.4 142c-5 0-10 .3-15 1a183 183 0 0 0-66.9-19.1V87.5a17.5 17.5 0 1 0-35 0v36.4a183 183 0 0 0-67 19c-4.9-.6-9.9-1-14.8-1C170.3 142 105 219.6 105 315s65.3 173 145.7 173c5 0 10-.3 14.8-1a184.7 184.7 0 0 0 169 0c4.9.7 9.9 1 14.9 1 80.3 0 145.6-77.6 145.6-173s-65.3-173-145.7-173zm-220 138 27.4-40.4a11.6 11.6 0 0 1 16.4-2.7l54.7 40.3a11.3 11.3 0 0 1-7 20.3H239a11.3 11.3 0 0 1-9.6-17.5zM444 383.8l-43.7 17.5a17.7 17.7 0 0 1-13 0l-37.3-15-37.2 15a17.8 17.8 0 0 1-13 0L256 383.8a17.5 17.5 0 0 1 13-32.6l37.3 15 37.2-15c4.2-1.6 8.8-1.6 13 0l37.3 15 37.2-15a17.5 17.5 0 0 1 13 32.6zm17-86.3h-82a11.3 11.3 0 0 1-6.9-20.4l54.7-40.3a11.6 11.6 0 0 1 16.4 2.8l27.4 40.4a11.3 11.3 0 0 1-9.6 17.5z',
|
||||
matrix: [0.020491803278688523, 0, 0, 0.020491803278688523, -7.172131147540983, -5.9016393442622945]
|
||||
}),
|
||||
// heart shape from https://thenounproject.com/icon/heart-1545381/
|
||||
heart: confetti.shapeFromPath({
|
||||
path: 'M167 72c19,-38 37,-56 75,-56 42,0 76,33 76,75 0,76 -76,151 -151,227 -76,-76 -151,-151 -151,-227 0,-42 33,-75 75,-75 38,0 57,18 76,56z',
|
||||
matrix: [0.03333333333333333, 0, 0, 0.03333333333333333, -5.566666666666666, -5.533333333333333]
|
||||
})
|
||||
},
|
||||
// Function to animate the snow effect
|
||||
snow: async function () {
|
||||
var duration = 15 * 1000;
|
||||
var animationEnd = Date.now() + duration;
|
||||
var skew = 1;
|
||||
|
||||
function randomInRange(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
// Function to animate the first snow particle
|
||||
async function snowAnimation() {
|
||||
return new Promise(resolve => {
|
||||
var firstSnowDuration = 3000; // 3 seconds
|
||||
var firstAnimationEnd = Date.now() + firstSnowDuration;
|
||||
|
||||
(function firstSnowFrame() {
|
||||
var timeLeft = firstAnimationEnd - Date.now();
|
||||
|
||||
confetti({
|
||||
particleCount: 1,
|
||||
startVelocity: 0,
|
||||
ticks: Math.max(200, 500 * (timeLeft / firstSnowDuration)),
|
||||
origin: {
|
||||
x: Math.random(),
|
||||
y: randomInRange(0.2, 0.4) * skew - 0.4
|
||||
},
|
||||
colors: ['#ffffff'],
|
||||
shapes: ['circle'],
|
||||
gravity: randomInRange(0.4, 0.6),
|
||||
scalar: randomInRange(0.4, 1),
|
||||
drift: randomInRange(-0.4, 0.4)
|
||||
});
|
||||
|
||||
if (timeLeft > 0) {
|
||||
requestAnimationFrame(firstSnowFrame);
|
||||
} else {
|
||||
resolve(); // Resolve the promise when the animation is complete
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
// Function to animate the continuous snow particles
|
||||
function mainSnowAnimation() {
|
||||
(function frame() {
|
||||
var timeLeft = animationEnd - Date.now();
|
||||
var ticks = Math.max(200, 500 * (timeLeft / duration));
|
||||
skew = Math.max(0.8, skew - 0.001);
|
||||
|
||||
confetti({
|
||||
particleCount: 1,
|
||||
startVelocity: 0,
|
||||
ticks: ticks,
|
||||
origin: {
|
||||
x: Math.random(),
|
||||
y: randomInRange(0.2, 0.4) * skew - 0.4
|
||||
},
|
||||
colors: ['#ffffff'],
|
||||
shapes: ['circle'],
|
||||
gravity: randomInRange(0.4, 0.6),
|
||||
scalar: randomInRange(0.4, 1),
|
||||
drift: randomInRange(-0.4, 0.4)
|
||||
});
|
||||
|
||||
confetti({
|
||||
particleCount: 1,
|
||||
startVelocity: 0,
|
||||
ticks: ticks,
|
||||
origin: {
|
||||
x: Math.random(),
|
||||
y: randomInRange(0.2, 0.4) * skew - 0.2
|
||||
},
|
||||
colors: ['#ffffff'],
|
||||
shapes: ['circle'],
|
||||
gravity: randomInRange(0.4, 0.6),
|
||||
scalar: randomInRange(0.4, 1),
|
||||
drift: randomInRange(-0.4, 0.4)
|
||||
});
|
||||
|
||||
if (timeLeft > 0) {
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// Start the animations
|
||||
await snowAnimation(); // Wait for the first snow animation to complete
|
||||
mainSnowAnimation(); // Start the continuous snow animation
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user