23 Commits

Author SHA1 Message Date
oxmc
77dcea14cc Add userStyles support (wip) 2024-12-27 14:01:09 -08:00
oxmc
dcbbfde4a5 Reformat build dir 2024-12-20 09:28:49 -08:00
oxmc
685c2dc210 Fix sha256 method in aur build 2024-12-20 08:48:23 -08:00
oxmc
3d67f6d5ae change sha256 file paths 2024-12-20 08:36:27 -08:00
oxmc
e4ca9882b9 fix macOS sha256 2024-12-20 08:26:41 -08:00
oxmc
9c4a886799 Add extension support and work on auto update 2024-12-20 08:22:49 -08:00
oxmc
6839090631 Merge pull request #12 from GizzyUwU/main
remove a.yml
2024-12-20 05:50:07 -08:00
Gizzy
1232eea297 remove a.yml 2024-12-20 13:21:39 +00:00
oxmc
b88509d210 Merge pull request #10 from PlOszukiwaczDEV/main
fixed yq
2024-12-20 05:14:17 -08:00
oxmc
2f95583af1 Merge pull request #11 from GizzyUwU/main
Fixed version change on aur publish
2024-12-20 05:14:08 -08:00
Gizzy
6f6ab38c9f test 2024-12-20 13:12:39 +00:00
PlOszukiwacz
10887b8358 yq revert 2024-12-20 14:01:43 +01:00
PlOszukiwacz
4ce43bdac3 yq test 2 2024-12-20 13:57:38 +01:00
Gizzy
9947bfc3dc test 2024-12-20 12:52:53 +00:00
PlOszukiwacz
b68a85ac2f yq test 1 2024-12-20 13:50:31 +01:00
oxmc
dfbb0492ac Merge pull request #9 from PlOszukiwaczDEV/main
Added appimagelint to check the appimages
2024-12-20 04:46:50 -08:00
PlOszukiwacz
def3cd312d libfuse dosent exist ig 2024-12-20 13:45:49 +01:00
Gizzy
5539ae2c63 test 2024-12-20 12:43:44 +00:00
PlOszukiwacz
53bf782ddc appimagelint test 3 (libfuse) 2024-12-20 13:43:35 +01:00
Gizzy
01e9154a33 test 2024-12-20 12:37:30 +00:00
PlOszukiwacz
04ec30066d appimagelint test 2 2024-12-20 13:36:57 +01:00
PlOszukiwacz
8a1b2ac65f appimagelint test 1 2024-12-20 13:33:28 +01:00
Gizzy
cb0cfbe640 test 2024-12-20 11:37:36 +00:00
16 changed files with 961 additions and 63 deletions

View File

@@ -37,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
@@ -45,16 +58,7 @@ jobs:
path: |
dist/*.AppImage
dist/latest*.yml
- name: Generate checksum
run: |
sha256sum dist/*.AppImage > sha256sum.txt
- name: Upload checksums
uses: actions/upload-artifact@v4
with:
name: checksums
path: sha256sum.txt
dist/sha256sum.txt
build-windows:
name: Build bsky-desktop (Windows)
@@ -80,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
@@ -89,6 +97,7 @@ jobs:
path: |
dist/*.exe
dist/latest*.yml
dist/sha256sum.txt
build-macos:
name: Build bsky-desktop (macOS)
@@ -114,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
@@ -123,6 +136,7 @@ jobs:
path: |
dist/*.dmg
dist/latest*.yml
dist/sha256sum.txt
release:
name: Create Release
@@ -161,11 +175,10 @@ jobs:
- name: Display structure of downloaded files
run: ls -R dist
- name: Merge latest .ymls
uses: mikefarah/yq@v4.44.6
with:
cmd: yq eval-all '. as $item ireduce ({}; . * $item )' dist/*/*.yml > merged.yml
- 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
@@ -178,7 +191,7 @@ jobs:
dist/linux/*.AppImage
dist/windows/*.exe
dist/macos/*.dmg
merged.yml
sha256sums.txt
aur:
name: Publish to AUR
@@ -191,35 +204,35 @@ jobs:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Install jq
run: sudo apt-get update && sudo apt-get install jq -y
- name: Download checksums
- name: Download linux artifacts
uses: actions/download-artifact@v4
with:
name: checksums
path: .
name: linux-artifacts
path: dist/linux
- name: List downloaded files
run: ls -R
run: ls -R dist
- name: Show content of sha256sum.txt
run: cat 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: |
echo releasever
new_checksum=$(awk 'NR==1 { print $1 }' ./sha256sum.txt)
sed -i "s|sha256sums=('SKIP' 'SKIP')|sha256sums=('$new_checksum' 'SKIP')|" ./build/PKGBUILD
sed -i "s/^pkgver=.*$/pkgver=${{ needs.release.outputs.version_tag }}/" ./build/PKGBUILD
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/PKGBUILD
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
ssh_keyscan_types: rsa,ecdsa,ed25519

View File

@@ -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:
[![Build and Release bsky-desktop](https://github.com/oxmc/bsky-desktop/actions/workflows/build-and-release.yml/badge.svg)](https://github.com/oxmc/bsky-desktop/actions/workflows/build-and-release.yml)
[![Packaging status](https://repology.org/badge/vertical-allrepos/bskydesktop.svg?columns=4&exclude_unsupported=1)](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.

View File

@@ -16,14 +16,17 @@ 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"
@@ -36,7 +39,7 @@ prepare() {
"$icon_url"
)
echo "AppImage source: ${source[0]}"
sha256sums=('SKIP' 'SKIP')
sha256sums=("$sha256sum" 'SKIP')
curl -L "${source[0]}" -o "$srcdir/bskyDesktop.appimage"
curl -L "${source[1]}" -o "$srcdir/$icon_name"
}
@@ -70,7 +73,7 @@ package() {
[Desktop Entry]
Name=Bluesky Desktop
Comment=Bluesky Desktop Client
Exec=/usr/bin/bsky-desktop
Exec=/usr/bin/bsky-desktop %u
Icon=bsky-desktop
Terminal=false
Type=Application

295
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "bsky-desktop",
"version": "1.0.8",
"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": {
@@ -42,9 +58,19 @@
"dmg",
"pkg"
],
"icon": "src/ui/images/logo.icns",
"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"

View File

@@ -7,7 +7,8 @@ const openAboutWindow = require("./about-window/src/index").default;
const badge = require('./badge');
const contextMenu = require('./context-menu');
const autoUpdater = require('./utils/auto-update');
//const loadCRX = require('./utils/loadCRX');
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 = {
@@ -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:
@@ -492,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();

View File

@@ -107,13 +107,14 @@ function checkForUpdates() {
// System is ARM64 (mac-arm64)
macArch = 'arm64';
} else {
// System is Intel (mac-x64)
macArch = 'intel';
// System is x64 (mac-x64)
macArch = 'x64';
}
} else {
// macOS 10 is mostly Intel, but some versions are ARM64
macArch = 'intel';
// 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)...');
@@ -122,7 +123,7 @@ function checkForUpdates() {
const command = `sudo installer -pkg ${pkgPath} -target /`;
// Spawn a new shell
const shellProcess = spawn('sh', ['-c', command], {
/*const shellProcess = spawn('sh', ['-c', command], {
stdio: 'inherit', // Pipe input/output to/from the shell
});
@@ -136,7 +137,7 @@ function checkForUpdates() {
} 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...');

View File

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

210
src/app/utils/userStyles.js Normal file
View 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
};

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

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