Merge pull request #815 from plebbit/development

Development
This commit is contained in:
Tom
2025-12-08 23:46:52 +01:00
committed by GitHub
64 changed files with 1008 additions and 486 deletions
+57 -52
View File
@@ -21,8 +21,21 @@ jobs:
node-version: 22
cache: 'yarn'
- name: Cache electron binaries
uses: actions/cache@v4
with:
path: ~/.cache/electron
key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-electron-
- name: Install dependencies
run: yarn install --frozen-lockfile --ignore-engines
run: |
for i in 1 2 3; do
echo "yarn install attempt $i"
yarn install --frozen-lockfile --ignore-engines --network-timeout 100000 --network-concurrency 1 && break
sleep 5
[ "$i" = "3" ] && exit 1
done
- name: Download IPFS
run: node electron/download-ipfs && chmod +x bin/linux/ipfs
@@ -75,8 +88,21 @@ jobs:
# Use pip with --break-system-packages for CI environment
pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
- name: Cache electron binaries
uses: actions/cache@v4
with:
path: ~/Library/Caches/electron
key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-electron-
- name: Install dependencies
run: yarn install --frozen-lockfile --ignore-engines
run: |
for i in 1 2 3; do
echo "yarn install attempt $i"
yarn install --frozen-lockfile --ignore-engines --network-timeout 100000 --network-concurrency 1 && break
sleep 5
[ "$i" = "3" ] && exit 1
done
- name: Download IPFS
run: node electron/download-ipfs && chmod +x bin/mac/ipfs
@@ -135,8 +161,21 @@ jobs:
# Use pip with --break-system-packages for CI environment
pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
- name: Cache electron binaries
uses: actions/cache@v4
with:
path: ~/Library/Caches/electron
key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-electron-
- name: Install dependencies
run: yarn install --frozen-lockfile --ignore-engines
run: |
for i in 1 2 3; do
echo "yarn install attempt $i"
yarn install --frozen-lockfile --ignore-engines --network-timeout 100000 --network-concurrency 1 && break
sleep 5
[ "$i" = "3" ] && exit 1
done
- name: Download IPFS
run: node electron/download-ipfs && chmod +x bin/mac/ipfs
@@ -195,8 +234,22 @@ jobs:
node-version: 22
cache: 'yarn'
- name: Cache electron binaries
uses: actions/cache@v4
with:
path: ~/AppData/Local/electron/Cache
key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-electron-
- name: Install dependencies
run: yarn install --frozen-lockfile --ignore-engines --network-timeout 100000 --network-concurrency 1
shell: bash
run: |
for i in 1 2 3; do
echo "yarn install attempt $i"
yarn install --frozen-lockfile --ignore-engines --network-timeout 100000 --network-concurrency 1 && break
sleep 5
[ "$i" = "3" ] && exit 1
done
- name: Download IPFS
run: node electron/download-ipfs
@@ -235,51 +288,3 @@ jobs:
ls -R dist
exit 1
fi
test-android:
name: Test Android
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js v22
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'yarn'
- name: Setup Java 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Install dependencies
run: yarn install --frozen-lockfile --ignore-engines
- name: Build React App
run: yarn build
env:
CI: ''
- name: Sync Capacitor
run: npx cap sync android
- name: Build Android APK
run: |
cd android
chmod +x gradlew
./gradlew assembleDebug
- name: Verify APK exists
run: |
APK=$(find android/app/build/outputs/apk -name "*.apk" | head -n 1)
if [ -n "$APK" ]; then
echo "✓ APK built successfully: $APK"
ls -lh "$APK"
else
echo "✗ No APK found"
exit 1
fi
+13 -10
View File
@@ -34,13 +34,13 @@
"lodash": "4.17.21",
"memoizee": "0.4.15",
"prettier": "3.0.3",
"react": "19.1.0",
"react-dom": "19.1.0",
"react": "19.1.2",
"react-dom": "19.1.2",
"react-i18next": "13.2.2",
"react-markdown": "8.0.6",
"react-router-dom": "6.16.0",
"react-router-hash-link": "2.4.3",
"react-virtuoso": "4.12.3",
"react-virtuoso": "4.17.0",
"rehype-raw": "7.0.0",
"rehype-sanitize": "5.0.1",
"remark-gfm": "3.0.1",
@@ -93,9 +93,9 @@
]
},
"devDependencies": {
"@capacitor/android": "7.2.0",
"@capacitor/cli": "7.2.0",
"@capacitor/core": "7.2.0",
"@capacitor/android": "7.4.4",
"@capacitor/cli": "7.4.4",
"@capacitor/core": "7.4.4",
"@electron/rebuild": "4.0.0",
"@react-scan/vite-plugin-react-scan": "0.1.8",
"@types/memoizee": "0.4.9",
@@ -112,9 +112,9 @@
"crypto-browserify": "3.12.1",
"cz-conventional-changelog": "3.3.0",
"decompress": "4.2.1",
"electron": "36.0.0",
"electron": "36.9.5",
"electron-builder": "24.13.2",
"eslint": "8.56.0",
"eslint": "8.57.1",
"eslint-config-react-app": "7.0.1",
"eslint-plugin-react": "7.37.4",
"eslint-plugin-react-compiler": "19.1.0-rc.2",
@@ -126,11 +126,14 @@
"react-grab": "0.0.18",
"react-scan": "0.4.3",
"stream-browserify": "3.0.0",
"vite": "6.2.1",
"vite": "6.4.1",
"vite-plugin-eslint": "1.8.1",
"vite-plugin-node-polyfills": "0.23.0",
"vite-plugin-pwa": "0.21.1",
"wait-on": "7.0.1"
"wait-on": "9.0.3"
},
"resolutions": {
"js-yaml": "4.1.1"
},
"main": "electron/main.js",
"build": {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "المنتديات التي تقوم بالإشراف عليها",
"more_threads_last_year": "{{count}} مواضيع منذ {{currentTimeFilterName}} — [<1>عرض المزيد من المواضيع من السنة الماضية</1>]",
"moderator_of_this_board": "هذا المستخدم هو مشرف على هذا المنتدى",
"administrator_of_this_board": "هذا المستخدم هو مسؤول عن هذا المنتدى"
"administrator_of_this_board": "هذا المستخدم هو مسؤول عن هذا المنتدى",
"copy_content_id": "نسخ معرف المحتوى"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "আপনি যে বোর্ডগুলি পরিচালনা করেন",
"more_threads_last_year": "{{count}} থ্রেড {{currentTimeFilterName}} থেকে — [<1>গত বছরের আরও থ্রেড দেখান</1>]",
"moderator_of_this_board": "এই ব্যবহারকারী এই বোর্ডের একজন মডারেটর",
"administrator_of_this_board": "এই ব্যবহারকারী এই বোর্ডের একজন প্রশাসক"
"administrator_of_this_board": "এই ব্যবহারকারী এই বোর্ডের একজন প্রশাসক",
"copy_content_id": "কনটেন্ট আইডি কপি করুন"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "Fóra, která moderujete",
"more_threads_last_year": "{{count}} příspěvků od {{currentTimeFilterName}} — [<1>Zobrazit více příspěvků z minulého roku</1>]",
"moderator_of_this_board": "Tento uživatel je moderátorem tohoto fóra",
"administrator_of_this_board": "Tento uživatel je administrátorem tohoto fóra"
"administrator_of_this_board": "Tento uživatel je administrátorem tohoto fóra",
"copy_content_id": "kopírovat ID obsahu"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "Boards du modererer",
"more_threads_last_year": "{{count}} tråde siden {{currentTimeFilterName}} — [<1>Vis flere tråde fra sidste år</1>]",
"moderator_of_this_board": "Denne bruger er moderator for dette board",
"administrator_of_this_board": "Denne bruger er administrator for dette board"
"administrator_of_this_board": "Denne bruger er administrator for dette board",
"copy_content_id": "kopier indhold-ID"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "Boards, die Sie moderieren",
"more_threads_last_year": "{{count}} Threads seit {{currentTimeFilterName}} — [<1>Mehr Threads vom letzten Jahr anzeigen</1>]",
"moderator_of_this_board": "Dieser Benutzer ist Moderator dieses Boards",
"administrator_of_this_board": "Dieser Benutzer ist Administrator dieses Boards"
"administrator_of_this_board": "Dieser Benutzer ist Administrator dieses Boards",
"copy_content_id": "Inhalts-ID kopieren"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "Boards που διαχειρίζεστε",
"more_threads_last_year": "{{count}} νήματα από {{currentTimeFilterName}} — [<1>Προβολή περισσότερων νημάτων από το περασμένο έτος</1>]",
"moderator_of_this_board": "Αυτός ο χρήστης είναι συντονιστής αυτού του πίνακα",
"administrator_of_this_board": "Αυτός ο χρήστης είναι διαχειριστής αυτού του πίνακα"
"administrator_of_this_board": "Αυτός ο χρήστης είναι διαχειριστής αυτού του πίνακα",
"copy_content_id": "αντιγραφή ID περιεχομένου"
}
+2 -1
View File
@@ -296,5 +296,6 @@
"boards_you_moderate": "boards you moderate",
"more_threads_last_year": "{{count}} threads since {{currentTimeFilterName}} — [<1>Show More Threads from Last Year</1>]",
"moderator_of_this_board": "This user is a moderator of this board",
"administrator_of_this_board": "This user is an administrator of this board"
"administrator_of_this_board": "This user is an administrator of this board",
"copy_content_id": "Copy content ID"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "tableros que moderas",
"more_threads_last_year": "{{count}} hilos desde {{currentTimeFilterName}} — [<1>Ver más hilos del año pasado</1>]",
"moderator_of_this_board": "Este usuario es moderador de este tablero",
"administrator_of_this_board": "Este usuario es administrador de este tablero"
"administrator_of_this_board": "Este usuario es administrador de este tablero",
"copy_content_id": "Copiar ID de contenido"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "تابلوهایی که مدیریت می‌کنید",
"more_threads_last_year": "{{count}} پست از {{currentTimeFilterName}} — [<1>نمایش بیشتر پست‌های سال گذشته</1>]",
"moderator_of_this_board": "این کاربر مدیر این انجمن است",
"administrator_of_this_board": "این کاربر مدیرکل این انجمن است"
"administrator_of_this_board": "این کاربر مدیرکل این انجمن است",
"copy_content_id": "کپی شناسه محتوا"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "lautakunnat, joita valvot",
"more_threads_last_year": "{{count}} ketjua sitten {{currentTimeFilterName}} — [<1>Näytä lisää viime vuoden ketjuja</1>]",
"moderator_of_this_board": "Tämä käyttäjä on tämän taulun moderaattori",
"administrator_of_this_board": "Tämä käyttäjä on tämän taulun järjestelmänvalvoja"
"administrator_of_this_board": "Tämä käyttäjä on tämän taulun järjestelmänvalvoja",
"copy_content_id": "kopioi sisältö-ID"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "mga board na iyong minomoderate",
"more_threads_last_year": "{{count}} mga thread mula {{currentTimeFilterName}} — [<1>Ipakita ang higit pang mga thread mula noong nakaraang taon</1>]",
"moderator_of_this_board": "Ang user na ito ay moderator ng board na ito",
"administrator_of_this_board": "Ang user na ito ay administrator ng board na ito"
"administrator_of_this_board": "Ang user na ito ay administrator ng board na ito",
"copy_content_id": "kopyahin ang content ID"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards que vous modérez",
"more_threads_last_year": "{{count}} fils depuis {{currentTimeFilterName}} — [<1>Afficher plus de fils de l'année dernière</1>]",
"moderator_of_this_board": "Cet utilisateur est modérateur de ce tableau",
"administrator_of_this_board": "Cet utilisateur est administrateur de ce tableau"
"administrator_of_this_board": "Cet utilisateur est administrateur de ce tableau",
"copy_content_id": "Copier l'ID du contenu"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "לוחות שאתה מפקח עליהם",
"more_threads_last_year": "{{count}} חוטים מאז {{currentTimeFilterName}} — [<1>הצג עוד חוטים מהשנה שעברה</1>]",
"moderator_of_this_board": "משתמש זה הוא מנהל של הלוח הזה",
"administrator_of_this_board": "משתמש זה הוא מנהל מערכת של הלוח הזה"
"administrator_of_this_board": "משתמש זה הוא מנהל מערכת של הלוח הזה",
"copy_content_id": "העתק מזהה תוכן"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "बोर्ड जो आप मॉडरेट करते हैं",
"more_threads_last_year": "{{count}} थ्रेड्स {{currentTimeFilterName}} से — [<1>पिछले साल के अधिक थ्रेड्स दिखाएं</1>]",
"moderator_of_this_board": "यह उपयोगकर्ता इस बोर्ड का मॉडरेटर है",
"administrator_of_this_board": "यह उपयोगकर्ता इस बोर्ड का प्रशासक है"
"administrator_of_this_board": "यह उपयोगकर्ता इस बोर्ड का प्रशासक है",
"copy_content_id": "कंटेंट ID कॉपी करें"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boardok, amelyeket moderálsz",
"more_threads_last_year": "{{count}} szál {{currentTimeFilterName}} óta — [<1>Több szál megjelenítése a múlt évből</1>]",
"moderator_of_this_board": "Ez a felhasználó ennek a táblának a moderátora",
"administrator_of_this_board": "Ez a felhasználó ennek a táblának a rendszergazdája"
"administrator_of_this_board": "Ez a felhasználó ennek a táblának a rendszergazdája",
"copy_content_id": "tartalom ID másolása"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "board yang Anda moderasi",
"more_threads_last_year": "{{count}} thread sejak {{currentTimeFilterName}} — [<1>Tampilkan lebih banyak thread dari tahun lalu</1>]",
"moderator_of_this_board": "Pengguna ini adalah moderator papan ini",
"administrator_of_this_board": "Pengguna ini adalah administrator papan ini"
"administrator_of_this_board": "Pengguna ini adalah administrator papan ini",
"copy_content_id": "salin ID konten"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "board che moderi",
"more_threads_last_year": "{{count}} thread da {{currentTimeFilterName}} — [<1>Mostra altri thread dell'anno scorso</1>]",
"moderator_of_this_board": "Questo utente è moderatore di questa bacheca",
"administrator_of_this_board": "Questo utente è amministratore di questa bacheca"
"administrator_of_this_board": "Questo utente è amministratore di questa bacheca",
"copy_content_id": "Copia ID contenuto"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "あなたが管理するボード",
"more_threads_last_year": "{{count}} スレッド {{currentTimeFilterName}} から — [<1>去年のスレッドをさらに表示</1>]",
"moderator_of_this_board": "このユーザーはこのボードのモデレーターです",
"administrator_of_this_board": "このユーザーはこのボードの管理者です"
"administrator_of_this_board": "このユーザーはこのボードの管理者です",
"copy_content_id": "コンテンツIDをコピー"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "당신이 관리하는 게시판",
"more_threads_last_year": "{{count}} 스레드 {{currentTimeFilterName}} 이후 — [<1>작년의 스레드 더 보기</1>]",
"moderator_of_this_board": "이 사용자는 이 게시판의 관리자입니다",
"administrator_of_this_board": "이 사용자는 이 게시판의 관리자입니다"
"administrator_of_this_board": "이 사용자는 이 게시판의 관리자입니다",
"copy_content_id": "콘텐츠 ID 복사"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "तुम्ही ज्यांचे नियंत्रण करता अशा बोर्ड्स",
"more_threads_last_year": "{{count}} ध糀ं {{currentTimeFilterName}} पासून — [<1>गेल्या वर्षातील आणखी थ्रेड्स दाखवा</1>]",
"moderator_of_this_board": "हा वापरकर्ता या बोर्डचा मॉडरेटर आहे",
"administrator_of_this_board": "हा वापरकर्ता या बोर्डचा प्रशासक आहे"
"administrator_of_this_board": "हा वापरकर्ता या बोर्डचा प्रशासक आहे",
"copy_content_id": "कंटेंट ID कॉपी करा"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards die je beheert",
"more_threads_last_year": "{{count}} threads sinds {{currentTimeFilterName}} — [<1>Meer threads van afgelopen jaar tonen</1>]",
"moderator_of_this_board": "Deze gebruiker is moderator van dit bord",
"administrator_of_this_board": "Deze gebruiker is beheerder van dit bord"
"administrator_of_this_board": "Deze gebruiker is beheerder van dit bord",
"copy_content_id": "kopieer inhoud-ID"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards du modererer",
"more_threads_last_year": "{{count}} tråder siden {{currentTimeFilterName}} — [<1>Vis flere tråder fra forrige år</1>]",
"moderator_of_this_board": "Denne brukeren er moderator for dette tavlet",
"administrator_of_this_board": "Denne brukeren er administrator for dette tavlet"
"administrator_of_this_board": "Denne brukeren er administrator for dette tavlet",
"copy_content_id": "kopier innhold-ID"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards, które moderujesz",
"more_threads_last_year": "{{count}} wątki od {{currentTimeFilterName}} — [<1>Pokaż więcej wątków z zeszłego roku</1>]",
"moderator_of_this_board": "Ten użytkownik jest moderatorem tego forum",
"administrator_of_this_board": "Ten użytkownik jest administratorem tego forum"
"administrator_of_this_board": "Ten użytkownik jest administratorem tego forum",
"copy_content_id": "skopiuj ID treści"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards que você modera",
"more_threads_last_year": "{{count}} tópicos desde {{currentTimeFilterName}} — [<1>Mostrar mais tópicos do ano passado</1>]",
"moderator_of_this_board": "Este usuário é moderador deste quadro",
"administrator_of_this_board": "Este usuário é administrador deste quadro"
"administrator_of_this_board": "Este usuário é administrador deste quadro",
"copy_content_id": "Copiar ID do conteúdo"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards pe care le moderezi",
"more_threads_last_year": "{{count}} subiecte din {{currentTimeFilterName}} — [<1>Afișați mai multe subiecte de anul trecut</1>]",
"moderator_of_this_board": "Acest utilizator este moderator al acestui forum",
"administrator_of_this_board": "Acest utilizator este administrator al acestui forum"
"administrator_of_this_board": "Acest utilizator este administrator al acestui forum",
"copy_content_id": "copiați ID-ul conținutului"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards, которые вы модерируете",
"more_threads_last_year": "{{count}} темы с {{currentTimeFilterName}} — [<1>Показать больше тем с прошлого года</1>]",
"moderator_of_this_board": "Этот пользователь является модератором этого форума",
"administrator_of_this_board": "Этот пользователь является администратором этого форума"
"administrator_of_this_board": "Этот пользователь является администратором этого форума",
"copy_content_id": "копировать ID контента"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards që i moderoni",
"more_threads_last_year": "{{count}} tema nga {{currentTimeFilterName}} — [<1>Shfaq më shumë tema nga viti i kaluar</1>]",
"moderator_of_this_board": "Ky përdorues është moderator i këtij bordi",
"administrator_of_this_board": "Ky përdorues është administrator i këtij bordi"
"administrator_of_this_board": "Ky përdorues është administrator i këtij bordi",
"copy_content_id": "kopjo ID e përmbajtjes"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards du modererar",
"more_threads_last_year": "{{count}} trådar sedan {{currentTimeFilterName}} — [<1>Visa fler trådar från förra året</1>]",
"moderator_of_this_board": "Denna användare är moderator för detta forum",
"administrator_of_this_board": "Denna användare är administratör för detta forum"
"administrator_of_this_board": "Denna användare är administratör för detta forum",
"copy_content_id": "kopiera innehålls-ID"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "మీరు మోడరేట్ చేసే బోర్డులు",
"more_threads_last_year": "{{count}} థ్రెడ్‌లు {{currentTimeFilterName}} నుండి — [<1>గత సంవత్సరం నుండి మరిన్ని థ్రెడ్‌లను చూపించండి</1>]",
"moderator_of_this_board": "ఈ వినియోగదారు ఈ బోర్డ్ యొక్క మోడరేటర్",
"administrator_of_this_board": "ఈ వినియోగదారు ఈ బోర్డ్ యొక్క నిర్వాహకుడు"
"administrator_of_this_board": "ఈ వినియోగదారు ఈ బోర్డ్ యొక్క నిర్వాహకుడు",
"copy_content_id": "కంటెంట్ ID కాపీ చేయండి"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "บอร์ดที่คุณดูแล",
"more_threads_last_year": "{{count}} กระทู้ตั้งแต่ {{currentTimeFilterName}} — [<1>แสดงกระทู้จากปีที่แล้วเพิ่มเติม</1>]",
"moderator_of_this_board": "ผู้ใช้รายนี้เป็นผู้ดูแลของบอร์ดนี้",
"administrator_of_this_board": "ผู้ใช้รายนี้เป็นผู้ดูแลระบบของบอร์ดนี้"
"administrator_of_this_board": "ผู้ใช้รายนี้เป็นผู้ดูแลระบบของบอร์ดนี้",
"copy_content_id": "คัดลอก ID เนื้อหา"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "modere ettiğiniz panolar",
"more_threads_last_year": "{{count}} konu {{currentTimeFilterName}} itibariyle — [<1>Geçen yıldan daha fazla konu göster</1>]",
"moderator_of_this_board": "Bu kullanıcı bu panonun moderatörüdür",
"administrator_of_this_board": "Bu kullanıcı bu panonun yöneticisidir"
"administrator_of_this_board": "Bu kullanıcı bu panonun yöneticisidir",
"copy_content_id": "içerik ID'sini kopyala"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards, які ви модеруватимете",
"more_threads_last_year": "{{count}} тем з {{currentTimeFilterName}} — [<1>Показати більше тем з минулого року</1>]",
"moderator_of_this_board": "Цей користувач є модератором цього форуму",
"administrator_of_this_board": "Цей користувач є адміністратором цього форуму"
"administrator_of_this_board": "Цей користувач є адміністратором цього форуму",
"copy_content_id": "копіювати ID контенту"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "وہ بورڈز جن کی آپ نگرانی کرتے ہیں",
"more_threads_last_year": "{{count}} تھریڈز {{currentTimeFilterName}} سے — [<1>گزشتہ سال کے مزید تھریڈز دکھائیں</1>]",
"moderator_of_this_board": "یہ صارف اس بورڈ کا ماڈریٹر ہے",
"administrator_of_this_board": "یہ صارف اس بورڈ کا منتظم ہے"
"administrator_of_this_board": "یہ صارف اس بورڈ کا منتظم ہے",
"copy_content_id": "مواد کی شناخت کاپی کریں"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "boards bạn điều hành",
"more_threads_last_year": "{{count}} chủ đề từ {{currentTimeFilterName}} — [<1>Hiển thị thêm các chủ đề từ năm ngoái</1>]",
"moderator_of_this_board": "Người dùng này là người điều hành của bảng này",
"administrator_of_this_board": "Người dùng này là quản trị viên của bảng này"
"administrator_of_this_board": "Người dùng này là quản trị viên của bảng này",
"copy_content_id": "sao chép ID nội dung"
}
+2 -1
View File
@@ -295,5 +295,6 @@
"boards_you_moderate": "您管理的板块",
"more_threads_last_year": "{{count}} 主题自 {{currentTimeFilterName}} — [<1>显示更多去年的主题</1>]",
"moderator_of_this_board": "此用户是此版块的版主",
"administrator_of_this_board": "此用户是此版块的管理员"
"administrator_of_this_board": "此用户是此版块的管理员",
"copy_content_id": "复制内容ID"
}
+31 -29
View File
@@ -9,10 +9,8 @@ import useSpecialThemeStore from './stores/use-special-theme-store';
import useIsMobile from './hooks/use-is-mobile';
import useTheme from './hooks/use-theme';
import { useDefaultSubplebbits } from './hooks/use-default-subplebbits';
import { getSubplebbitAddress } from './lib/utils/route-utils';
import { getSubplebbitAddress, isPostRoute, isPendingPostRoute } from './lib/utils/route-utils';
import styles from './app.module.css';
import Board from './views/board';
import Catalog from './views/catalog';
import FAQ from './views/faq';
import Home from './views/home';
import NotFound from './views/not-found';
@@ -22,6 +20,7 @@ import { DesktopBoardButtons, MobileBoardButtons } from './components/board-butt
import BoardHeader from './components/board-header';
import ChallengeModal from './components/challenge-modal';
import CreateBoardModal from './components/create-board-modal';
import FeedCacheContainer from './components/feed-cache-container';
import ReplyModal from './components/reply-modal';
import PostForm from './components/post-form';
import SubplebbitStats from './components/subplebbit-stats';
@@ -43,6 +42,9 @@ const BoardLayout = () => {
const pendingPost = useAccountComment({ commentIndex: accountCommentIndex ? parseInt(accountCommentIndex) : undefined });
const { closeCreateBoardModal } = useCreateBoardModalStore();
const isOnPostRoute = isPostRoute(location.pathname);
const isOnPendingPostRoute = isPendingPostRoute(location.pathname);
// Christmas theme
const { isEnabled: isSpecialEnabled } = useSpecialThemeStore();
useEffect(() => {
@@ -75,9 +77,8 @@ const BoardLayout = () => {
{isMobile
? (subplebbitAddress || isInAllView || isInModView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && (
<>
<PostForm key={key} />
<hr />
<MobileBoardButtons />
<PostForm key={key} />
</>
)
: (subplebbitAddress || isInAllView || isInModView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && (
@@ -87,7 +88,8 @@ const BoardLayout = () => {
<DesktopBoardButtons />
</>
)}
<Outlet />
<FeedCacheContainer />
{(isOnPostRoute || isOnPendingPostRoute) && <Outlet />}
</div>
);
};
@@ -142,36 +144,36 @@ const App = () => (
<Route path='/' element={<Home />} />
<Route path='/faq' element={<FAQ />} />
<Route element={<BoardLayout />}>
<Route path='/all/:timeFilterName?' element={<Board />} />
<Route path='/all/:timeFilterName?/settings' element={<Board />} />
<Route path='/all/:timeFilterName?' element={null} />
<Route path='/all/:timeFilterName?/settings' element={null} />
<Route path='/all/catalog/:timeFilterName?' element={null} />
<Route path='/all/catalog/:timeFilterName?/settings' element={null} />
<Route path='/subs/:timeFilterName?' element={null} />
<Route path='/subs/:timeFilterName?/settings' element={null} />
<Route path='/subs/catalog/:timeFilterName?' element={null} />
<Route path='/subs/catalog/:timeFilterName?/settings' element={null} />
<Route path='/mod/:timeFilterName?' element={null} />
<Route path='/mod/:timeFilterName?/settings' element={null} />
<Route path='/mod/catalog/:timeFilterName?' element={null} />
<Route path='/mod/catalog/:timeFilterName?/settings' element={null} />
<Route path='/:boardIdentifier' element={null} />
<Route path='/:boardIdentifier/settings' element={null} />
<Route path='/:boardIdentifier/catalog' element={null} />
<Route path='/:boardIdentifier/catalog/settings' element={null} />
<Route path='/all/description' element={<Post />} />
<Route path='/all/catalog/:timeFilterName?' element={<Catalog />} />
<Route path='/all/catalog/:timeFilterName?/settings' element={<Catalog />} />
<Route path='/subs/:timeFilterName?' element={<Board />} />
<Route path='/subs/:timeFilterName?/settings' element={<Board />} />
<Route path='/subs/catalog/:timeFilterName?' element={<Catalog />} />
<Route path='/subs/catalog/:timeFilterName?/settings' element={<Catalog />} />
<Route path='/mod/:timeFilterName?' element={<Board />} />
<Route path='/mod/:timeFilterName?/settings' element={<Board />} />
<Route path='/mod/catalog/:timeFilterName?' element={<Catalog />} />
<Route path='/mod/catalog/:timeFilterName?/settings' element={<Catalog />} />
<Route path='/pending/:accountCommentIndex' element={<PendingPost />} />
<Route path='/pending/:accountCommentIndex/settings' element={<PendingPost />} />
<Route path='/:boardIdentifier' element={<Board />} />
<Route path='/:boardIdentifier/settings' element={<Board />} />
<Route path='/:boardIdentifier/catalog' element={<Catalog />} />
<Route path='/:boardIdentifier/catalog/settings' element={<Catalog />} />
<Route path='/:boardIdentifier/thread/:commentCid' element={<Post />} />
<Route path='/:boardIdentifier/thread/:commentCid/settings' element={<Post />} />
<Route path='/:boardIdentifier/description' element={<Post />} />
<Route path='/:boardIdentifier/description/settings' element={<Post />} />
<Route path='/:boardIdentifier/rules' element={<Post />} />
<Route path='/:boardIdentifier/rules/settings' element={<Post />} />
<Route path='/pending/:accountCommentIndex' element={<PendingPost />} />
<Route path='/pending/:accountCommentIndex/settings' element={<PendingPost />} />
</Route>
<Route path='/not-found' element={<NotFound />} />
<Route path='*' element={<NotFound />} />
+4 -2
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
@@ -24,6 +24,7 @@ import { ContentPreview } from '../../views/home/popular-threads-box';
import PostMenuDesktop from '../post-desktop/post-menu-desktop';
import styles from './catalog-row.module.css';
import _ from 'lodash';
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
interface CatalogPostMediaProps {
cid: string;
@@ -142,6 +143,7 @@ const CatalogPost = ({ post }: { post: Comment }) => {
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
const defaultSubplebbits = useDefaultSubplebbits();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]);
const postLink = isInAllView && isDescription ? '/all/description' : `/${boardPath}/${isDescription ? 'description' : isRules ? 'rules' : `thread/${cid}`}`;
@@ -287,7 +289,7 @@ const CatalogPost = ({ post }: { post: Comment }) => {
</span>
)}
<span className={`${styles.postMenu} ${hoveredCid && styles.postMenuVisible}`}>
<PostMenuDesktop post={post} />
<PostMenuDesktop postMenu={postMenuProps} />
</span>
</div>
<div className={styles.postContent}>{(showOPComment || isTextOnlyThread) && (hasThumbnail ? postContent : <Link to={postLink}>{postContent}</Link>)}</div>
@@ -293,7 +293,9 @@ const Challenge = ({ challenge, closeModal }: ChallengeProps) => {
{showIframeConfirmation ? (
<>
<div className={styles.challengeMediaWrapper}>
<div className={`${styles.challengeMedia} ${styles.iframeChallengeWarning}`}>board wants to open {readableUrl || 'an external site'}</div>
<div className={`${styles.challengeMedia} ${styles.iframeChallengeWarning}`}>
{shortSubplebbitAddress || subplebbitAddress || 'unknown board'} wants to open {readableUrl || 'an external site'}
</div>
</div>
<div className={`${styles.challengeFooter} ${styles.iframeFooter}`}>
<span className={styles.buttons}>
@@ -0,0 +1,13 @@
.visible {
}
.hidden {
visibility: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
pointer-events: none;
overflow: hidden;
height: 0;
}
@@ -0,0 +1,99 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import useFeedCacheStore, { CachedFeed } from '../../stores/use-feed-cache-store';
import { getFeedCacheKey, getFeedType, isFeedRoute } from '../../lib/utils/route-utils';
import Board from '../../views/board';
import Catalog from '../../views/catalog';
import styles from './feed-cache-container.module.css';
interface FeedContextFromKey {
viewType: 'all' | 'subs' | 'mod' | 'board';
boardIdentifier?: string;
timeFilterName?: string;
}
const parseFeedKey = (key: string): FeedContextFromKey => {
const segments = key.split('/').filter(Boolean);
const filteredSegments = segments.filter((s) => s !== 'catalog');
if (filteredSegments[0] === 'all') {
return {
viewType: 'all',
timeFilterName: filteredSegments[1],
};
}
if (filteredSegments[0] === 'subs') {
return {
viewType: 'subs',
timeFilterName: filteredSegments[1],
};
}
if (filteredSegments[0] === 'mod') {
return {
viewType: 'mod',
timeFilterName: filteredSegments[1],
};
}
return {
viewType: 'board',
boardIdentifier: filteredSegments[0],
timeFilterName: filteredSegments[1],
};
};
interface CachedFeedWrapperProps {
feed: CachedFeed;
isVisible: boolean;
}
const CachedFeedWrapper = ({ feed, isVisible }: CachedFeedWrapperProps) => {
const context = parseFeedKey(feed.key);
return (
<div className={isVisible ? styles.visible : styles.hidden}>
{feed.type === 'catalog' ? (
<Catalog
feedCacheKey={feed.key}
viewType={context.viewType}
boardIdentifier={context.boardIdentifier}
timeFilterNameFromCache={context.timeFilterName}
isVisible={isVisible}
/>
) : (
<Board
feedCacheKey={feed.key}
viewType={context.viewType}
boardIdentifier={context.boardIdentifier}
timeFilterNameFromCache={context.timeFilterName}
isVisible={isVisible}
/>
)}
</div>
);
};
const FeedCacheContainer = () => {
const location = useLocation();
const { cachedFeeds, accessFeed } = useFeedCacheStore();
const currentFeedKey = getFeedCacheKey(location.pathname);
const isOnFeedRoute = isFeedRoute(location.pathname);
const feedType = getFeedType(location.pathname);
useEffect(() => {
if (isOnFeedRoute && currentFeedKey && feedType) {
accessFeed(currentFeedKey, feedType);
}
}, [currentFeedKey, isOnFeedRoute, feedType, accessFeed]);
return (
<>
{cachedFeeds.map((feed) => (
<CachedFeedWrapper key={feed.key} feed={feed} isVisible={isOnFeedRoute && feed.key === currentFeedKey} />
))}
</>
);
};
export default FeedCacheContainer;
@@ -0,0 +1 @@
export { default } from './feed-cache-container';
+6 -4
View File
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAuthorAvatar, useEditedComment } from '@plebbit/plebbit-react-hooks';
@@ -33,6 +33,7 @@ import { create } from 'zustand';
import _ from 'lodash';
import { shouldShowSnow } from '../../lib/snow';
import useReplyModalStore from '../../stores/use-reply-modal-store';
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
interface ShowOmittedRepliesState {
showOmittedReplies: Record<string, boolean>;
@@ -66,6 +67,7 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
const { hideAvatars } = useAvatarVisibilityStore();
const defaultSubplebbits = useDefaultSubplebbits();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : undefined;
const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]);
const params = useParams();
const location = useLocation();
@@ -133,9 +135,9 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
<span
className={`${styles.capcodeIcon} ${authorRole === 'mod' ? styles.capcodeModIcon : styles.capcodeAdminIcon}`}
title={authorRole === 'mod' ? t('moderator_of_this_board') : t('administrator_of_this_board')}
/>{' '}
/>
</span>
)}
)}{' '}
</span>
{!(isDescription || isRules) && (
<>
@@ -226,7 +228,7 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
</span>
)}
</span>
{!(removed || deleted) && <PostMenuDesktop post={post} />}
{!(removed || deleted) && <PostMenuDesktop postMenu={postMenuProps} />}
{cid &&
parentCid &&
replies &&
@@ -1,31 +1,38 @@
import { useState } from 'react';
import { memo, useState } from 'react';
import { createPortal } from 'react-dom';
import { useLocation, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
import { Comment, useBlock } from '@plebbit/plebbit-react-hooks';
import { useBlock } from '@plebbit/plebbit-react-hooks';
import styles from './post-menu-desktop.module.css';
import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
import { copyShareLinkToClipboard, isValidURL } from '../../../lib/utils/url-utils';
import { copyShareLinkToClipboard, isValidURL, type ShareLinkType } from '../../../lib/utils/url-utils';
import { copyToClipboard } from '../../../lib/utils/clipboard-utils';
import { getBoardPath } from '../../../lib/utils/route-utils';
import { useDefaultSubplebbits } from '../../../hooks/use-default-subplebbits';
import { isAllView, isCatalogView, isPostPageView, isSubscriptionsView } from '../../../lib/utils/view-utils';
import useHide from '../../../hooks/use-hide';
import _ from 'lodash';
import { PostMenuProps } from '../../../lib/utils/post-menu-props';
interface PostMenuDesktopProps {
cid: string;
isDescription?: boolean;
isRules?: boolean;
subplebbitAddress: string;
onClose: () => void;
}
type CopyLinkButtonProps =
| { cid: string; subplebbitAddress: string; linkType: 'thread'; onClose: () => void }
| { subplebbitAddress: string; linkType: Exclude<ShareLinkType, 'thread'>; onClose: () => void; cid?: undefined };
const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuDesktopProps) => {
const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: CopyLinkButtonProps) => {
const { t } = useTranslation();
const defaultSubplebbits = useDefaultSubplebbits();
const boardIdentifier = getBoardPath(subplebbitAddress, defaultSubplebbits);
return (
<div
onClick={() => {
copyShareLinkToClipboard(subplebbitAddress, cid);
onClose();
onClick={async () => {
try {
await copyShareLinkToClipboard(boardIdentifier, linkType, cid);
} catch (error) {
console.error('Failed to copy share link', error);
} finally {
onClose();
}
}}
>
<div className={styles.postMenuItem}>{t('copy_link')}</div>
@@ -33,6 +40,25 @@ const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuDesktopProp
);
};
const CopyContentIdButton = ({ cid, onClose }: { cid: string; onClose: () => void }) => {
const { t } = useTranslation();
return (
<div
onClick={async () => {
try {
await copyToClipboard(cid);
} catch (error) {
console.error('Failed to copy content id', error);
} finally {
onClose();
}
}}
>
<div className={styles.postMenuItem}>{t('copy_content_id')}</div>
</div>
);
};
const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void }) => {
const { t } = useTranslation();
const [isImageSearchMenuOpen, setIsImageSearchMenuOpen] = useState(false);
@@ -88,14 +114,18 @@ const BlockBoardButton = ({ address }: { address: string }) => {
);
};
const PostMenuDesktop = ({ post }: { post: Comment }) => {
type PostMenuDesktopProps = {
postMenu: PostMenuProps;
};
const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
const { t } = useTranslation();
const { author, cid, isDescription, isRules, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = post || {};
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
const { authorAddress, cid, isDescription, isRules, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = postMenu || {};
const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth ?? 0, linkHeight ?? 0);
const { thumbnail, type, url } = commentMediaInfo || {};
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
const { hidden, unhide, hide } = useHide({ cid });
const { hidden, unhide, hide } = useHide({ cid: cid || '' });
const location = useLocation();
const params = useParams();
@@ -118,7 +148,7 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
const headingId = useId();
const handleMenuClick = () => {
if (cid) {
if (cid || isDescription || isRules) {
setMenuBtnRotated((prev) => !prev);
}
};
@@ -142,7 +172,10 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
createPortal(
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{!cid && isDescription && subplebbitAddress && <CopyLinkButton subplebbitAddress={subplebbitAddress} linkType='description' onClose={handleClose} />}
{!cid && isRules && subplebbitAddress && <CopyLinkButton subplebbitAddress={subplebbitAddress} linkType='rules' onClose={handleClose} />}
{!(isInPostPageView && postCid === cid) && !isDescription && !isRules && (
<div
className={styles.postMenuItem}
@@ -155,8 +188,8 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
</div>
)}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
{!isDescription && !isRules && <BlockUserButton address={author?.address} />}
{(isInAllView || isInSubscriptionsView) && <BlockBoardButton address={subplebbitAddress} />}
{!isDescription && !isRules && authorAddress && <BlockUserButton address={authorAddress} />}
{!isDescription && !isRules && (isInAllView || isInSubscriptionsView) && subplebbitAddress && <BlockBoardButton address={subplebbitAddress} />}
</div>
</FloatingFocusManager>,
document.body,
@@ -165,4 +198,4 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
);
};
export default PostMenuDesktop;
export default memo(PostMenuDesktop);
@@ -137,6 +137,15 @@
.postFormTable button {
padding: 3px 6px;
}
.postFormMobile {
margin-bottom: 6px;
}
.postFormTable {
padding-top: 20px;
padding-bottom: 3px;
}
}
@media (min-width: 640px) {
@@ -1,36 +1,46 @@
import { useState } from 'react';
import { memo, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { Comment, useBlock } from '@plebbit/plebbit-react-hooks';
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
import styles from './post-menu-mobile.module.css';
import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
import { copyShareLinkToClipboard, isValidURL } from '../../../lib/utils/url-utils';
import { copyShareLinkToClipboard, isValidURL, type ShareLinkType } from '../../../lib/utils/url-utils';
import { copyToClipboard } from '../../../lib/utils/clipboard-utils';
import { getBoardPath } from '../../../lib/utils/route-utils';
import { useDefaultSubplebbits } from '../../../hooks/use-default-subplebbits';
import useEditCommentPrivileges from '../../../hooks/use-author-privileges';
import useHide from '../../../hooks/use-hide';
import EditMenu from '../../edit-menu/edit-menu';
import { isBoardView, isPostPageView } from '../../../lib/utils/view-utils';
import { useLocation, useParams } from 'react-router-dom';
import { PostMenuProps } from '../../../lib/utils/post-menu-props';
interface PostMenuMobileProps {
cid: string;
isDescription?: boolean;
type HideButtonProps = {
cid?: string;
isReply?: boolean;
isRules?: boolean;
postCid?: string;
subplebbitAddress?: string;
onClose?: () => void;
}
};
const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuMobileProps) => {
type CopyLinkButtonProps =
| { cid: string; subplebbitAddress: string; linkType: 'thread'; onClose: () => void }
| { subplebbitAddress: string; linkType: Exclude<ShareLinkType, 'thread'>; onClose: () => void; cid?: undefined };
const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: CopyLinkButtonProps) => {
const { t } = useTranslation();
const defaultSubplebbits = useDefaultSubplebbits();
const boardIdentifier = getBoardPath(subplebbitAddress, defaultSubplebbits);
return (
<div
onClick={() => {
if (subplebbitAddress) {
copyShareLinkToClipboard(subplebbitAddress, cid);
onClick={async () => {
try {
await copyShareLinkToClipboard(boardIdentifier, linkType, cid);
} catch (error) {
console.error('Failed to copy share link', error);
} finally {
onClose();
}
onClose && onClose();
}}
>
<div className={styles.postMenuItem}>{t('copy_link')}</div>
@@ -38,6 +48,25 @@ const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuMobileProps
);
};
const CopyContentIdButton = ({ cid, onClose }: { cid: string; onClose: () => void }) => {
const { t } = useTranslation();
return (
<div
onClick={async () => {
try {
await copyToClipboard(cid);
} catch (error) {
console.error('Failed to copy content id', error);
} finally {
onClose();
}
}}
>
<div className={styles.postMenuItem}>{t('copy_content_id')}</div>
</div>
);
};
const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void }) => {
const { t } = useTranslation();
return (
@@ -55,7 +84,7 @@ const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void
);
};
const HidePostButton = ({ cid, isReply, onClose, postCid }: PostMenuMobileProps) => {
const HidePostButton = ({ cid, isReply, onClose, postCid }: HideButtonProps) => {
const { t } = useTranslation();
const { hide, hidden, unhide } = useHide({ cid });
const isInPostView = isPostPageView(useLocation().pathname, useParams());
@@ -96,9 +125,15 @@ const BlockBoardButton = ({ address }: { address: string }) => {
);
};
const PostMenuMobile = ({ post }: { post: Comment }) => {
const { author, cid, deleted, isDescription, isRules, link, linkHeight, linkWidth, parentCid, postCid, removed, subplebbitAddress, thumbnailUrl } = post || {};
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: author?.address, subplebbitAddress });
type PostMenuMobileProps = {
postMenu: PostMenuProps;
editMenuPost: Comment;
};
const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
const { authorAddress, cid, deleted, isDescription, isRules, link, linkHeight, linkWidth, parentCid, postCid, removed, subplebbitAddress, thumbnailUrl } =
postMenu || {};
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: authorAddress, subplebbitAddress });
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
const { thumbnail, type, url } = commentMediaInfo || {};
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -116,7 +151,7 @@ const PostMenuMobile = ({ post }: { post: Comment }) => {
const headingId = useId();
const handleMenuClick = () => {
if (cid) {
if (cid || isDescription || isRules) {
setIsMenuOpen((prev) => !prev);
}
};
@@ -137,9 +172,12 @@ const PostMenuMobile = ({ post }: { post: Comment }) => {
createPortal(
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
{cid && subplebbitAddress && <HidePostButton cid={cid} isReply={parentCid} postCid={postCid} onClose={handleClose} />}
{cid && subplebbitAddress && !isDescription && !isRules && <BlockUserButton address={author?.address} />}
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{!cid && isDescription && subplebbitAddress && <CopyLinkButton subplebbitAddress={subplebbitAddress} linkType='description' onClose={handleClose} />}
{!cid && isRules && subplebbitAddress && <CopyLinkButton subplebbitAddress={subplebbitAddress} linkType='rules' onClose={handleClose} />}
{cid && subplebbitAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
{cid && subplebbitAddress && !isDescription && !isRules && authorAddress && <BlockUserButton address={authorAddress} />}
{cid && subplebbitAddress && !isInBoardView && !isDescription && !isRules && <BlockBoardButton address={subplebbitAddress} />}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButtons url={url} onClose={handleClose} />}
</div>
@@ -150,11 +188,11 @@ const PostMenuMobile = ({ post }: { post: Comment }) => {
)}
{(isAccountMod || isAccountCommentAuthor) && cid && (
<span className={styles.checkbox}>
<EditMenu post={post} />
<EditMenu post={editMenuPost} />
</span>
)}
</>
);
};
export default PostMenuMobile;
export default memo(PostMenuMobile);
+4 -2
View File
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAuthorAvatar, useEditedComment } from '@plebbit/plebbit-react-hooks';
@@ -28,6 +28,7 @@ import Tooltip from '../tooltip';
import { PostProps } from '../../views/post/post';
import _ from 'lodash';
import useReplyModalStore from '../../stores/use-reply-modal-store';
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
const { t } = useTranslation();
@@ -71,6 +72,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const stateString = useStateString(post);
const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]);
const handleUserAddressClick = useAuthorAddressClick();
const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length;
@@ -98,7 +100,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
return (
<>
<div className={styles.postInfo}>
<PostMenuMobile post={post} />
<PostMenuMobile postMenu={postMenuProps} editMenuPost={post} />
<span className={(hidden || ((removed || deleted) && !reason)) && parentCid ? styles.postDesktopHidden : ''}>
<span className={styles.nameBlock}>
<span className={`${styles.name} ${authorRole && !(deleted || removed) && (authorRole === 'mod' ? styles.capcodeMod : styles.capcodeAdmin)}`}>
@@ -20,10 +20,11 @@ const SubplebbitDescription = ({ avatarUrl, createdAt, description, replyCount,
const location = useLocation();
const isInAllView = isAllView(location.pathname);
const multisubMetadata = useMultisubMetadata();
const descriptionSubplebbitAddress = isInAllView ? 'all' : subplebbitAddress;
const post = {
isDescription: true,
subplebbitAddress,
subplebbitAddress: descriptionSubplebbitAddress,
timestamp: isInAllView ? multisubMetadata?.createdAt : createdAt,
author: { displayName: _.capitalize(t('anonymous')) },
content: isInAllView ? multisubMetadata?.description : description,
+1 -7
View File
@@ -2,12 +2,6 @@
// Run 'node scripts/generate-asset-manifest.js' to regenerate
// This file is generated from public/assets/ directory
export const BANNERS = [
'assets/banners/banner-1.jpg',
'assets/banners/banner-2.jpg',
'assets/banners/banner-3.gif',
'assets/banners/banner-4.gif',
'assets/banners/banner-5.png',
] as const;
export const BANNERS = ['assets/banners/banner-1.jpg', 'assets/banners/banner-2.jpg', 'assets/banners/banner-3.gif', 'assets/banners/banner-4.png'] as const;
export const NOT_FOUND_IMAGES = ['assets/not-found/not-found-1.png'] as const;
+28
View File
@@ -53,6 +53,34 @@ if (secondsSinceLastVisit > 30 * day) {
export const timeFilterNames = [lastVisitTimeFilterName, '1h', '12h', '24h', '48h', '1w', '1m', '1y', 'all'];
export const timeFilterNameToSeconds = (timeFilterName: string | undefined): number | undefined => {
if (!timeFilterName || timeFilterName === 'all') return undefined;
if (timeFilterName in timeFilterNamesToSeconds) {
return timeFilterNamesToSeconds[timeFilterName as keyof typeof timeFilterNamesToSeconds];
}
const match = timeFilterName.match(/^(\d+)([hdwmy])$/);
if (match) {
const [, value, unit] = match;
const numValue = parseInt(value, 10);
switch (unit) {
case 'h':
return numValue * 60 * 60;
case 'd':
return numValue * 24 * 60 * 60;
case 'w':
return numValue * 7 * 24 * 60 * 60;
case 'm':
return numValue * 30 * 24 * 60 * 60;
case 'y':
return numValue * 365 * 24 * 60 * 60;
}
}
return undefined;
};
function convertTimeStringToSeconds(timeString: string): number {
const match = timeString.match(/^(\d+)([hdwmy])$/);
if (!match) {
+33
View File
@@ -0,0 +1,33 @@
import { Comment } from '@plebbit/plebbit-react-hooks';
export type PostMenuProps = {
cid?: string;
postCid?: string;
parentCid?: string;
subplebbitAddress?: string;
isDescription?: boolean;
isRules?: boolean;
authorAddress?: string;
link?: string;
linkWidth?: number;
linkHeight?: number;
thumbnailUrl?: string;
deleted?: boolean;
removed?: boolean;
};
export const selectPostMenuProps = (post?: Comment): PostMenuProps => ({
cid: post?.cid,
postCid: post?.postCid,
parentCid: post?.parentCid,
subplebbitAddress: post?.subplebbitAddress,
isDescription: post?.isDescription,
isRules: post?.isRules,
authorAddress: post?.author?.address,
link: post?.link,
linkWidth: post?.linkWidth,
linkHeight: post?.linkHeight,
thumbnailUrl: post?.thumbnailUrl,
deleted: post?.deleted,
removed: post?.removed,
});
+82
View File
@@ -100,3 +100,85 @@ export const isDirectoryBoard = (identifier: string, subplebbits: MultisubSubple
const directoryToAddress = getDirectoryToAddressMap(subplebbits);
return directoryToAddress.has(identifier);
};
export const isFeedRoute = (pathname: string): boolean => {
const normalizedPath = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
if (normalizedPath.includes('/thread/')) return false;
if (normalizedPath.endsWith('/description')) return false;
if (normalizedPath.endsWith('/rules')) return false;
if (normalizedPath.startsWith('/pending/')) return false;
const pathWithoutSettings = normalizedPath.replace(/\/settings$/, '');
if (pathWithoutSettings.startsWith('/all')) return true;
if (pathWithoutSettings.startsWith('/subs')) return true;
if (pathWithoutSettings.startsWith('/mod')) return true;
const segments = pathWithoutSettings.split('/').filter(Boolean);
if (segments.length >= 1) {
if (segments.length === 1) return true;
if (segments.length === 2 && segments[1] === 'catalog') return true;
if (segments.length === 2 && /^(?:\d+(?:h|d|w|m|y)|all)$/.test(segments[1])) return true;
if (segments.length === 3 && segments[1] === 'catalog' && /^(?:\d+(?:h|d|w|m|y)|all)$/.test(segments[2])) return true;
}
return false;
};
export const isPostRoute = (pathname: string): boolean => {
const normalizedPath = pathname.replace(/\/settings$/, '');
if (normalizedPath.includes('/thread/')) return true;
if (normalizedPath.endsWith('/description')) return true;
if (normalizedPath.endsWith('/rules')) return true;
return false;
};
export const isPendingPostRoute = (pathname: string): boolean => {
const normalizedPath = pathname.replace(/\/settings$/, '');
return normalizedPath.startsWith('/pending/');
};
export const getFeedCacheKey = (pathname: string): string | null => {
let normalizedPath = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
normalizedPath = normalizedPath.replace(/\/settings$/, '');
if (normalizedPath.includes('/thread/')) {
const parts = normalizedPath.split('/thread/');
return parts[0] || null;
}
if (normalizedPath.endsWith('/description') || normalizedPath.endsWith('/rules')) {
return normalizedPath.replace(/\/(description|rules)$/, '');
}
if (normalizedPath.startsWith('/pending/')) {
return null;
}
if (isFeedRoute(pathname)) {
return normalizedPath;
}
return null;
};
export const getFeedType = (pathname: string): 'board' | 'catalog' | null => {
const normalizedPath = pathname.replace(/\/settings$/, '');
if (normalizedPath.includes('/catalog')) {
return 'catalog';
}
if (isFeedRoute(pathname)) {
return 'board';
}
if (isPostRoute(pathname)) {
return 'board';
}
return null;
};
+17 -3
View File
@@ -17,10 +17,24 @@ export const isValidURL = (url: string) => {
}
};
export const copyShareLinkToClipboard = async (subplebbitAddress: string, cid: string) => {
const shareLink = `https://pleb.bz/p/${subplebbitAddress}/c/${cid}?redirect=5chan.app`;
export type ShareLinkType = 'thread' | 'description' | 'rules';
// Copies a share link to clipboard for a board, thread, description, or rules page
export function copyShareLinkToClipboard(boardIdentifier: string, linkType: 'thread', cid: string): Promise<void>;
export function copyShareLinkToClipboard(boardIdentifier: string, linkType: Exclude<ShareLinkType, 'thread'>, cid?: undefined): Promise<void>;
export async function copyShareLinkToClipboard(boardIdentifier: string, linkType: ShareLinkType, cid?: string): Promise<void> {
if (linkType === 'thread') {
if (!cid) {
throw new Error('copyShareLinkToClipboard: thread links require a cid');
}
const shareLink = `https://5chan.app/${boardIdentifier}/thread/${cid}`;
await copyToClipboard(shareLink);
return;
}
const shareLink = `https://5chan.app/${boardIdentifier}/${linkType}`;
await copyToClipboard(shareLink);
};
}
const CHAN_5_HOSTNAMES = ['pleb.bz', '5chan.app', '5chan.eth.limo', '5chan.eth.link', '5chan.eth.sucks', '5chan.netlify.app'];
+54
View File
@@ -0,0 +1,54 @@
import { create } from 'zustand';
export interface CachedFeed {
key: string;
type: 'board' | 'catalog';
lastAccessed: number;
}
interface FeedCacheState {
cachedFeeds: CachedFeed[];
maxCacheSize: number;
accessFeed: (key: string, type: 'board' | 'catalog') => void;
removeFeed: (key: string) => void;
isFeedCached: (key: string) => boolean;
}
const useFeedCacheStore = create<FeedCacheState>((set, get) => ({
cachedFeeds: [],
maxCacheSize: 2,
accessFeed: (key: string, type: 'board' | 'catalog') => {
const { cachedFeeds, maxCacheSize } = get();
const now = Date.now();
const existingIndex = cachedFeeds.findIndex((feed) => feed.key === key);
if (existingIndex !== -1) {
const updatedFeeds = [...cachedFeeds];
updatedFeeds[existingIndex] = { ...updatedFeeds[existingIndex], lastAccessed: now };
set({ cachedFeeds: updatedFeeds });
} else {
const newFeed: CachedFeed = { key, type, lastAccessed: now };
let updatedFeeds = [...cachedFeeds, newFeed];
if (updatedFeeds.length > maxCacheSize) {
updatedFeeds.sort((a, b) => a.lastAccessed - b.lastAccessed);
updatedFeeds = updatedFeeds.slice(1);
}
set({ cachedFeeds: updatedFeeds });
}
},
removeFeed: (key: string) => {
const { cachedFeeds } = get();
set({ cachedFeeds: cachedFeeds.filter((feed) => feed.key !== key) });
},
isFeedCached: (key: string) => {
const { cachedFeeds } = get();
return cachedFeeds.some((feed) => feed.key === key);
},
}));
export default useFeedCacheStore;
+56 -19
View File
@@ -1,19 +1,19 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
import { Comment, useAccount, useAccountComments, useAccountSubplebbits, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { Trans, useTranslation } from 'react-i18next';
import styles from './board.module.css';
import { shouldShowSnow } from '../../lib/snow';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import { useDefaultSubplebbitAddresses, useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string';
import useTimeFilter from '../../hooks/use-time-filter';
import useTimeFilter, { timeFilterNameToSeconds } from '../../hooks/use-time-filter';
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useSortingStore from '../../stores/use-sorting-store';
import { getSubplebbitAddress } from '../../lib/utils/route-utils';
import ErrorDisplay from '../../components/error-display/error-display';
import LoadingEllipsis from '../../components/loading-ellipsis';
import SubplebbitDescription from '../../components/subplebbit-description';
@@ -33,21 +33,39 @@ const createThreadsWithoutImagesFilter = () => ({
key: 'threads-with-images-only',
});
const Board = () => {
export interface BoardProps {
feedCacheKey?: string;
viewType?: 'all' | 'subs' | 'mod' | 'board';
boardIdentifier?: string;
timeFilterNameFromCache?: string;
isVisible?: boolean;
}
const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, timeFilterNameFromCache, isVisible = true }: BoardProps) => {
const { t } = useTranslation();
const location = useLocation();
const subplebbitAddress = useResolvedSubplebbitAddress();
const boardPath = useBoardPath(subplebbitAddress);
const params = useParams();
const { hideThreadsWithoutImages } = useInterfaceSettingsStore();
const isInAllView = isAllView(location.pathname);
const isInAllView = viewType ? viewType === 'all' : false;
const isInSubscriptionsView = viewType ? viewType === 'subs' : false;
const isInModView = viewType ? viewType === 'mod' : false;
const defaultSubplebbits = useDefaultSubplebbits();
const resolvedAddressFromUrl = useResolvedSubplebbitAddress();
const subplebbitAddress = useMemo(() => {
if (boardIdentifierProp) {
return getSubplebbitAddress(boardIdentifierProp, defaultSubplebbits);
}
return resolvedAddressFromUrl;
}, [boardIdentifierProp, defaultSubplebbits, resolvedAddressFromUrl]);
const boardPath = useBoardPath(subplebbitAddress);
const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses();
const account = useAccount();
const subscriptions = account?.subscriptions;
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const isInModView = isModView(location.pathname);
const { accountSubplebbits } = useAccountSubplebbits();
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
@@ -65,7 +83,9 @@ const Board = () => {
}, [isInAllView, isInSubscriptionsView, isInModView, subplebbitAddress, defaultSubplebbitAddresses, subscriptions, accountSubplebbitAddresses]);
const { sortType } = useSortingStore();
const { timeFilterSeconds, timeFilterName } = useTimeFilter();
const { timeFilterSeconds: timeFilterSecondsFromHook, timeFilterName: timeFilterNameFromHook } = useTimeFilter();
const timeFilterName = timeFilterNameFromCache || timeFilterNameFromHook;
const timeFilterSeconds = timeFilterNameFromCache ? timeFilterNameToSeconds(timeFilterNameFromCache) : timeFilterSecondsFromHook;
const feedOptions = {
subplebbitAddresses,
@@ -82,8 +102,10 @@ const Board = () => {
const setResetFunction = useFeedResetStore((state) => state.setResetFunction);
useEffect(() => {
setResetFunction(reset);
}, [reset, setResetFunction, feed]);
if (isVisible) {
setResetFunction(reset);
}
}, [reset, setResetFunction, feed, isVisible]);
// show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update
const filteredComments = useMemo(
@@ -175,7 +197,6 @@ const Board = () => {
return () => clearTimeout(timer);
}, []);
const params = useParams();
const currentTimeFilterName = params?.timeFilterName || timeFilterName;
const Footer = () => {
@@ -272,26 +293,42 @@ const Board = () => {
);
};
// save the last Virtuoso state to restore it when navigating back
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
const virtuosoStateKey = feedCacheKey ? `${feedCacheKey}-${sortType}-${timeFilterSeconds}` : `${location.pathname}-${sortType}-${timeFilterSeconds}`;
const navigationType = useNavigationType();
const hasBeenVisibleRef = useRef(false);
useEffect(() => {
if (isVisible && !hasBeenVisibleRef.current) {
hasBeenVisibleRef.current = true;
if (navigationType !== 'POP') {
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
}
}
}, [isVisible, navigationType]);
useEffect(() => {
if (!isVisible) return;
const currentKey = virtuosoStateKey;
const setLastVirtuosoState = () => {
virtuosoRef.current?.getState((snapshot: StateSnapshot) => {
if (snapshot?.ranges?.length) {
lastVirtuosoStates[sortType + timeFilterSeconds] = snapshot;
lastVirtuosoStates[currentKey] = snapshot;
}
});
};
window.addEventListener('scroll', setLastVirtuosoState);
return () => window.removeEventListener('scroll', setLastVirtuosoState);
}, [sortType, timeFilterSeconds]);
}, [virtuosoStateKey, isVisible]);
const lastVirtuosoState = lastVirtuosoStates?.[sortType + timeFilterSeconds];
const lastVirtuosoState = navigationType === 'POP' ? lastVirtuosoStates?.[virtuosoStateKey] : undefined;
useEffect(() => {
if (!isVisible) return;
const boardTitle = title ? title : shortAddress || subplebbitAddress;
document.title = boardTitle + ' - 5chan';
}, [title, shortAddress, subplebbitAddress]);
}, [title, shortAddress, subplebbitAddress, isVisible]);
const shouldShowErrorToUser = error?.message && feed.length === 0;
+53 -17
View File
@@ -1,21 +1,21 @@
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
import { Comment, useAccount, useFeed, useSubplebbit, useBlock, useAccountComments } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string';
import useTimeFilter from '../../hooks/use-time-filter';
import useTimeFilter, { timeFilterNameToSeconds } from '../../hooks/use-time-filter';
import useWindowWidth from '../../hooks/use-window-width';
import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
import useSortingStore from '../../stores/use-sorting-store';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import { getSubplebbitAddress } from '../../lib/utils/route-utils';
import CatalogRow from '../../components/catalog-row';
import LoadingEllipsis from '../../components/loading-ellipsis';
import styles from './catalog.module.css';
@@ -123,20 +123,37 @@ const createCombinedFilter = (
};
};
const Catalog = () => {
export interface CatalogProps {
feedCacheKey?: string;
viewType?: 'all' | 'subs' | 'mod' | 'board';
boardIdentifier?: string;
timeFilterNameFromCache?: string;
isVisible?: boolean;
}
const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, timeFilterNameFromCache, isVisible = true }: CatalogProps) => {
const { t } = useTranslation();
const location = useLocation();
const subplebbitAddress = useResolvedSubplebbitAddress();
const boardPath = useBoardPath(subplebbitAddress);
const params = useParams();
const isInAllView = viewType ? viewType === 'all' : false;
const isInSubscriptionsView = viewType ? viewType === 'subs' : false;
const isInAllView = isAllView(location.pathname);
const defaultSubplebbits = useDefaultSubplebbits();
const resolvedAddressFromUrl = useResolvedSubplebbitAddress();
const subplebbitAddress = useMemo(() => {
if (boardIdentifierProp) {
return getSubplebbitAddress(boardIdentifierProp, defaultSubplebbits);
}
return resolvedAddressFromUrl;
}, [boardIdentifierProp, defaultSubplebbits, resolvedAddressFromUrl]);
const boardPath = useBoardPath(subplebbitAddress);
const { hideAdultBoards } = useInterfaceSettingsStore();
const { showTextOnlyThreads, filterItems, searchText, clearMatchedFilters } = useCatalogFiltersStore();
const account = useAccount();
const subscriptions = account?.subscriptions;
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const subplebbitAddresses = useMemo(() => {
const filteredDefaultSubplebbits = defaultSubplebbits
@@ -166,8 +183,10 @@ const Catalog = () => {
const columnCount = Math.floor(useWindowWidth() / columnWidth);
const postsPerPage = columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25;
const { timeFilterSeconds, timeFilterName } = useTimeFilter();
const { timeFilterSeconds: timeFilterSecondsFromHook, timeFilterName: timeFilterNameFromHook } = useTimeFilter();
const { sortType } = useSortingStore();
const timeFilterName = timeFilterNameFromCache || timeFilterNameFromHook;
const timeFilterSeconds = timeFilterNameFromCache ? timeFilterNameToSeconds(timeFilterNameFromCache) : timeFilterSecondsFromHook;
// Create a stable callback for filter matching
const handleFilterMatch = useCallback((filterIndex: number, cid: string, subplebbitAddress: string) => {
@@ -303,8 +322,10 @@ const Catalog = () => {
const setResetFunction = useFeedResetStore((state) => state.setResetFunction);
useEffect(() => {
setResetFunction(reset);
}, [reset, setResetFunction]);
if (isVisible) {
setResetFunction(reset);
}
}, [reset, setResetFunction, isVisible]);
const subplebbit = useSubplebbit({ subplebbitAddress });
const { error, shortAddress, state, title } = subplebbit || {};
@@ -344,7 +365,6 @@ const Catalog = () => {
</div>
);
const params = useParams<{ sortType?: string; timeFilterName?: string }>();
const currentTimeFilterName = params?.timeFilterName || timeFilterName;
const Footer = () => {
@@ -450,27 +470,43 @@ const Catalog = () => {
const rows = useCatalogFeedRows(columnCount, processedFeed, isFeedLoaded, subplebbit);
// save the last Virtuoso state to restore it when navigating back
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
const virtuosoStateKey = feedCacheKey ? `${feedCacheKey}-${sortType}-${timeFilterSeconds}` : `${location.pathname}-${sortType}-${timeFilterSeconds}-catalog`;
const navigationType = useNavigationType();
const hasBeenVisibleRef = useRef(false);
useEffect(() => {
if (isVisible && !hasBeenVisibleRef.current) {
hasBeenVisibleRef.current = true;
if (navigationType !== 'POP') {
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
}
}
}, [isVisible, navigationType]);
useEffect(() => {
if (!isVisible) return;
const currentKey = virtuosoStateKey;
const setLastVirtuosoState = () =>
virtuosoRef.current?.getState((snapshot: StateSnapshot) => {
if (snapshot?.ranges?.length) {
lastVirtuosoStates[sortType + timeFilterSeconds + 'catalog'] = snapshot;
lastVirtuosoStates[currentKey] = snapshot;
}
});
window.addEventListener('scroll', setLastVirtuosoState);
return () => window.removeEventListener('scroll', setLastVirtuosoState);
}, [sortType, timeFilterSeconds]);
}, [virtuosoStateKey, isVisible]);
const lastVirtuosoState = lastVirtuosoStates?.[sortType + timeFilterSeconds + 'catalog'];
const lastVirtuosoState = navigationType === 'POP' ? lastVirtuosoStates?.[virtuosoStateKey] : undefined;
useEffect(() => {
if (!isVisible) return;
let documentTitle = title ? title : shortAddress;
if (isInAllView) documentTitle = t('all');
else if (isInSubscriptionsView) documentTitle = t('subscriptions');
document.title = documentTitle + ` - ${t('catalog')} - 5chan`;
}, [title, shortAddress, isInAllView, isInSubscriptionsView, t]);
}, [title, shortAddress, isInAllView, isInSubscriptionsView, t, isVisible]);
// Clear matched filters when component mounts or when subplebbit changes
useEffect(() => {
+3 -30
View File
@@ -2,7 +2,6 @@ import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useDefaultSubplebbitsState, useDefaultSubplebbits, MultisubSubplebbit } from '../../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../../lib/utils/route-utils';
import LoadingEllipsis from '../../../components/loading-ellipsis';
import useDisclaimerModalStore from '../../../stores/use-disclaimer-modal-store';
import useDirectoryModalStore from '../../../stores/use-directory-modal-store';
import useBoardsFilterStore from '../../../stores/use-boards-filter-store';
@@ -31,7 +30,7 @@ const NSFWBadge = () => {
const BoardsList = ({ multisub }: { multisub: MultisubSubplebbit[] }) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { loading, error } = useDefaultSubplebbitsState();
const { error } = useDefaultSubplebbitsState();
const { showDisclaimerModal } = useDisclaimerModalStore();
const { openDirectoryModal } = useDirectoryModalStore();
const { useCatalogLinks, boardFilter } = useBoardsFilterStore();
@@ -56,37 +55,10 @@ const BoardsList = ({ multisub }: { multisub: MultisubSubplebbit[] }) => {
openDirectoryModal();
};
if (loading) {
return (
<div className={styles.box}>
<div className={`${styles.boxBar} ${styles.color2ColorBar}`}>
<h2 className='capitalize'>{t('boards')}</h2>
<BoardsFilterModal />
</div>
<div className={styles.boxContent}>
<LoadingEllipsis string={t('loading_default_boards')} />
</div>
</div>
);
}
if (error) {
return (
<div className={styles.box}>
<div className={`${styles.boxBar} ${styles.color2ColorBar}`}>
<h2 className='capitalize'>{t('boards')}</h2>
<BoardsFilterModal />
</div>
<div className={styles.boxContent}>
<div className='red'>{error.message}</div>
</div>
</div>
);
}
// Find active boards
const bizAddress = findBoardAddress(multisub, '/biz/ - Business & Finance');
const polAddress = findBoardAddress(multisub, '/pol/ - Politically Incorrect');
const errorMessage = error?.message;
// Filtering logic: determine which categories to show
const showAll = boardFilter === 'all';
@@ -110,6 +82,7 @@ const BoardsList = ({ multisub }: { multisub: MultisubSubplebbit[] }) => {
<BoardsFilterModal />
</div>
<div className={`${styles.boxContent} ${styles.boardsContent}`}>
{errorMessage && <div className='red'>{errorMessage}</div>}
{/* Column 1: Japanese Culture + Video Games */}
{(showJapaneseCulture || showVideoGames) && (
<div className={styles.boardsColumn}>
+11
View File
@@ -190,6 +190,12 @@
text-align: center;
}
.popularThreadsLoading {
text-align: left;
/* Reserve space only while loading: max 8 posts, ~2 rows of 4 cards on desktop */
min-height: 500px;
}
.popularThread {
vertical-align: top;
display: inline-block;
@@ -351,6 +357,11 @@
.boardsColumn a {
font-size: 1.2em;
}
/* Mobile: popular threads cards stack more, so taller min-height needed */
.popularThreadsLoading {
min-height: 1200px;
}
}
@media (min-width: 640px) {
@@ -78,6 +78,7 @@ const PopularThreadsBox = ({ multisub, subplebbits }: { multisub: MultisubSubple
const filteredSubplebbits = useMemo(getFilteredSubplebbits, [subplebbits, showWorksafeContentOnly, showNsfwContentOnly, multisub]);
const { popularPosts } = usePopularPosts(filteredSubplebbits);
const isLoading = popularPosts.length === 0;
return (
<div className={styles.box}>
@@ -85,12 +86,8 @@ const PopularThreadsBox = ({ multisub, subplebbits }: { multisub: MultisubSubple
<h2 className='capitalize'>{t('popular_threads')}</h2>
<BoxModal />
</div>
<div className={`${styles.boxContent} ${popularPosts.length !== 0 ? styles.popularThreads : ''}`}>
{popularPosts.length === 0 ? (
<LoadingEllipsis string={t('loading')} />
) : (
popularPosts.map((post: any) => <PopularThreadCard key={post.cid} post={post} multisub={multisub} />)
)}
<div className={`${styles.boxContent} ${styles.popularThreads} ${isLoading ? styles.popularThreadsLoading : ''}`}>
{isLoading ? <LoadingEllipsis string={t('loading')} /> : popularPosts.map((post: any) => <PopularThreadCard key={post.cid} post={post} multisub={multisub} />)}
</div>
</div>
);
+238 -222
View File
@@ -1199,20 +1199,20 @@
ipaddr.js "^2.2.0"
punycode "^2.3.1"
"@capacitor/android@7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@capacitor/android/-/android-7.2.0.tgz#f30e313315ab92c500bd57d9f0c4716a46b42f40"
integrity sha512-zdhEy3jZPG5Toe/pGzKtDgIiBGywjaoEuQWnGVjBYPlSAEUtAhpZ2At7V0SCb26yluAuzrAUV0Ue+LQeEtHwFQ==
"@capacitor/android@7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@capacitor/android/-/android-7.4.4.tgz#a14a1e844bd5079982427e247fdd17555b5fbedd"
integrity sha512-y8knfV1JXNrd6XZZLZireGT+EBCN0lvOo+HZ/s7L8LkrPBu4nY5UZn0Wxz4yOezItEII9rqYJSHsS5fMJG9gdw==
"@capacitor/app@7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@capacitor/app/-/app-7.0.1.tgz#0d0709fb4dde5046c24853f2d6b77a7ea411f748"
integrity sha512-ArlVZAAla4MwQoKh26x2AaTDOBh5Vhp1VhMKR3RwqZSsZnazKTFGNrPbr9Ez5r1knnEDfApyjwp1uZnXK1WTYQ==
"@capacitor/cli@7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-7.2.0.tgz#ea279f14e575b059edb7fe8dc64a8e3379088616"
integrity sha512-RNW9vtYYYSDmOdguYBSW0VpRnG/d6lGydlc9DLrJ7qbSPxFrotTz9IjkM48O+SruUma61DyuSqJttdbay2xSxg==
"@capacitor/cli@7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-7.4.4.tgz#bfce7db8b15d6f44c9d58dc338a5e3d662fecfbb"
integrity sha512-J7ciBE7GlJ70sr2s8oz1+H4ZdNk4MGG41fsakUlDHWva5UWgFIZYMiEdDvGbYazAYTaxN3lVZpH9zil9FfZj+Q==
dependencies:
"@ionic/cli-framework-output" "^2.2.8"
"@ionic/utils-subprocess" "^3.0.1"
@@ -1232,10 +1232,10 @@
tslib "^2.8.1"
xml2js "^0.6.2"
"@capacitor/core@7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-7.2.0.tgz#fdd1c5d2fbfa257887ce6065f66151a51f0e6d1b"
integrity sha512-2zCnA6RJeZ9ec4470o8QMZEQTWpekw9FNoqm5TLc10jeCrhvHVI8MPgxdZVc3mOdFlyieYu4AS1fNxSqbS57Pw==
"@capacitor/core@7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-7.4.4.tgz#b752dd7a12141e40d1c8ce2e37a79571d9f83518"
integrity sha512-xzjxpr+d2zwTpCaN0k+C6wKSZzWFAb9OVEUtmO72ihjr/NEDoLvsGl4WLfjWPcCO2zOy0b2X52tfRWjECFUjtw==
dependencies:
tslib "^2.1.0"
@@ -1629,10 +1629,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b"
integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==
"@eslint/js@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
"@ethersproject/abi@5.6.4":
version "5.6.4"
@@ -2279,17 +2279,39 @@
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
"@hapi/topo@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
"@hapi/address@^5.1.1":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-5.1.1.tgz#e9925fc1b65f5cc3fbea821f2b980e4652e84cb6"
integrity sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==
dependencies:
"@hapi/hoek" "^9.0.0"
"@hapi/hoek" "^11.0.2"
"@hapi/formula@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-3.0.2.tgz#81b538060ee079481c906f599906d163c4badeaf"
integrity sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==
"@hapi/hoek@^11.0.2", "@hapi/hoek@^11.0.7":
version "11.0.7"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.7.tgz#56a920793e0a42d10e530da9a64cc0d3919c4002"
integrity sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==
"@hapi/pinpoint@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.1.tgz#32077e715655fc00ab8df74b6b416114287d6513"
integrity sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==
"@hapi/tlds@^1.1.1":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@hapi/tlds/-/tlds-1.1.4.tgz#df4a7b59082b54ba4f3b7b38f781e2ac3cbc359a"
integrity sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==
"@hapi/topo@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-6.0.2.tgz#f219c1c60da8430228af4c1f2e40c32a0d84bbb4"
integrity sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==
dependencies:
"@hapi/hoek" "^11.0.2"
"@helia/bitswap@^3.0.4", "@helia/bitswap@^3.0.7":
version "3.0.7"
@@ -2510,12 +2532,12 @@
race-signal "^2.0.0"
uint8arrays "^5.1.0"
"@humanwhocodes/config-array@^0.11.13":
version "0.11.14"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==
"@humanwhocodes/config-array@^0.13.0":
version "0.13.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
dependencies:
"@humanwhocodes/object-schema" "^2.0.2"
"@humanwhocodes/object-schema" "^2.0.3"
debug "^4.3.1"
minimatch "^3.0.5"
@@ -2524,7 +2546,7 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/object-schema@^2.0.2":
"@humanwhocodes/object-schema@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
@@ -4319,115 +4341,115 @@
estree-walker "^2.0.2"
picomatch "^4.0.2"
"@rollup/rollup-android-arm-eabi@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz#59e7478d310f7e6a7c72453978f562483828112f"
integrity sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==
"@rollup/rollup-android-arm-eabi@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz#7e478b66180c5330429dd161bf84dad66b59c8eb"
integrity sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==
"@rollup/rollup-android-arm64@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz#a825192a0b1b2f27a5c950c439e7e37a33c5d056"
integrity sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==
"@rollup/rollup-android-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz#2b025510c53a5e3962d3edade91fba9368c9d71c"
integrity sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==
"@rollup/rollup-darwin-arm64@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz#4ee37078bccd725ae3c5f30ef92efc8e1bf886f3"
integrity sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==
"@rollup/rollup-darwin-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz#3577c38af68ccf34c03e84f476bfd526abca10a0"
integrity sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==
"@rollup/rollup-darwin-x64@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz#43cc08bd05bf9f388f125e7210a544e62d368d90"
integrity sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==
"@rollup/rollup-darwin-x64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz#2bf5f2520a1f3b551723d274b9669ba5b75ed69c"
integrity sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==
"@rollup/rollup-freebsd-arm64@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz#bc8e640e28abe52450baf3fc80d9b26d9bb6587d"
integrity sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==
"@rollup/rollup-freebsd-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz#4bb9cc80252564c158efc0710153c71633f1927c"
integrity sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==
"@rollup/rollup-freebsd-x64@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz#e981a22e057cc8c65bb523019d344d3a66b15bbc"
integrity sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==
"@rollup/rollup-freebsd-x64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz#2301289094d49415a380cf942219ae9d8b127440"
integrity sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==
"@rollup/rollup-linux-arm-gnueabihf@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz#4036b68904f392a20f3499d63b33e055b67eb274"
integrity sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==
"@rollup/rollup-linux-arm-gnueabihf@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz#1d03d776f2065e09fc141df7d143476e94acca88"
integrity sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==
"@rollup/rollup-linux-arm-musleabihf@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz#d3b1b9589606e0ff916801c855b1ace9e733427a"
integrity sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==
"@rollup/rollup-linux-arm-musleabihf@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz#8623de0e040b2fd52a541c602688228f51f96701"
integrity sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==
"@rollup/rollup-linux-arm64-gnu@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz#cbf0943c477e3b96340136dd3448eaf144378cf2"
integrity sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==
"@rollup/rollup-linux-arm64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz#ce2d1999bc166277935dde0301cde3dd0417fb6e"
integrity sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==
"@rollup/rollup-linux-arm64-musl@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz#837f5a428020d5dce1c3b4cc049876075402cf78"
integrity sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==
"@rollup/rollup-linux-arm64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz#88c2523778444da952651a2219026416564a4899"
integrity sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==
"@rollup/rollup-linux-loong64-gnu@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz#532c214ababb32ab4bc21b4054278b9a8979e516"
integrity sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==
"@rollup/rollup-linux-loong64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz#578ca2220a200ac4226c536c10c8cc6e4f276714"
integrity sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==
"@rollup/rollup-linux-ppc64-gnu@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz#93900163b61b49cee666d10ee38257a8b1dd161a"
integrity sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==
"@rollup/rollup-linux-ppc64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz#aa338d3effd4168a20a5023834a74ba2c3081293"
integrity sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==
"@rollup/rollup-linux-riscv64-gnu@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz#f0ffdcc7066ca04bc972370c74289f35c7a7dc42"
integrity sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==
"@rollup/rollup-linux-riscv64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz#16ba582f9f6cff58119aa242782209b1557a1508"
integrity sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==
"@rollup/rollup-linux-riscv64-musl@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz#361695c39dbe96773509745d77a870a32a9f8e48"
integrity sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==
"@rollup/rollup-linux-riscv64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz#e404a77ebd6378483888b8064c703adb011340ab"
integrity sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==
"@rollup/rollup-linux-s390x-gnu@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz#09fc6cc2e266a2324e366486ae5d1bca48c43a6a"
integrity sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==
"@rollup/rollup-linux-s390x-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz#92ad52d306227c56bec43d96ad2164495437ffe6"
integrity sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==
"@rollup/rollup-linux-x64-gnu@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz#aa9d5b307c08f05d3454225bb0a2b4cc87eeb2e1"
integrity sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==
"@rollup/rollup-linux-x64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz#fd0dea3bb9aa07e7083579f25e1c2285a46cb9fa"
integrity sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==
"@rollup/rollup-linux-x64-musl@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz#26949e5b4645502a61daba2f7a8416bd17cb5382"
integrity sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==
"@rollup/rollup-linux-x64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz#37a3efb09f18d555f8afc490e1f0444885de8951"
integrity sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==
"@rollup/rollup-openharmony-arm64@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz#ef493c072f9dac7e0edb6c72d63366846b6ffcd9"
integrity sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==
"@rollup/rollup-openharmony-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz#c489bec9f4f8320d42c9b324cca220c90091c1f7"
integrity sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==
"@rollup/rollup-win32-arm64-msvc@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz#56e1aaa6a630d2202ee7ec0adddd05cf384ffd44"
integrity sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==
"@rollup/rollup-win32-arm64-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz#152832b5f79dc22d1606fac3db946283601b7080"
integrity sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==
"@rollup/rollup-win32-ia32-msvc@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz#0a44bbf933a9651c7da2b8569fa448dec0de7480"
integrity sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==
"@rollup/rollup-win32-ia32-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz#54d91b2bb3bf3e9f30d32b72065a4e52b3a172a5"
integrity sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==
"@rollup/rollup-win32-x64-gnu@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz#730e12f0b60b234a7c02d5d3179ca3ec7972033d"
integrity sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==
"@rollup/rollup-win32-x64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz#df9df03e61a003873efec8decd2034e7f135c71e"
integrity sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==
"@rollup/rollup-win32-x64-msvc@4.52.4":
version "4.52.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz#5b2dd648a960b8fa00d76f2cc4eea2f03daa80f4"
integrity sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==
"@rollup/rollup-win32-x64-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz#38ae84f4c04226c1d56a3b17296ef1e0460ecdfe"
integrity sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==
"@rtsao/scc@^1.1.0":
version "1.1.0"
@@ -4483,23 +4505,6 @@
"@noble/hashes" "~1.8.0"
"@scure/base" "~1.2.5"
"@sideway/address@^4.1.5":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5"
integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==
dependencies:
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
"@sinclair/typebox@^0.27.8":
version "0.27.8"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
@@ -4737,6 +4742,11 @@
rpc-websockets "^9.0.2"
superstruct "^2.0.2"
"@standard-schema/spec@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c"
integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==
"@surma/rollup-plugin-off-main-thread@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
@@ -5917,13 +5927,14 @@ axe-core@^4.10.0:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.0.tgz#16f74d6482e343ff263d4f4503829e9ee91a86b6"
integrity sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==
axios@^0.27.2:
version "0.27.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
axios@^1.13.2:
version "1.13.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687"
integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==
dependencies:
follow-redirects "^1.14.9"
form-data "^4.0.0"
follow-redirects "^1.15.6"
form-data "^4.0.4"
proxy-from-env "^1.1.0"
axios@^1.7.2:
version "1.12.2"
@@ -7896,10 +7907,10 @@ electron-to-chromium@^1.5.227:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz#eacf61cef3f6345d0069ab427585c5a04d7084f0"
integrity sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==
electron@36.0.0:
version "36.0.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-36.0.0.tgz#ffbe7ca9edecaee53617f29a088be10bbed9a45f"
integrity sha512-MhBL5tgzqLsiw++YXxzXRvF5s90gelcEZP4Upz/aaRmmoscmCw/EtimxMSH6EPnKt+8KwBY9RVAdlcffFPYkyw==
electron@36.9.5:
version "36.9.5"
resolved "https://registry.yarnpkg.com/electron/-/electron-36.9.5.tgz#78e117e8cf97d53687f8d5a412e8bf9336229320"
integrity sha512-1UCss2IqxqujSzg/2jkRjuiT3G+EEXgd6UKB5kUekwQW1LJ6d4QCr8YItfC3Rr9VIGRDJ29eOERmnRNO1Eh+NA==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^22.7.7"
@@ -8473,16 +8484,16 @@ eslint-visitor-keys@^4.2.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
eslint@8.56.0:
version "8.56.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15"
integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==
eslint@8.57.1:
version "8.57.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9"
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.4"
"@eslint/js" "8.56.0"
"@humanwhocodes/config-array" "^0.11.13"
"@eslint/js" "8.57.1"
"@humanwhocodes/config-array" "^0.13.0"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
"@ungap/structured-clone" "^1.2.0"
@@ -8839,7 +8850,7 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
fdir@^6.5.0:
fdir@^6.4.4, fdir@^6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
@@ -8960,7 +8971,7 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.11, follow-redirects@^1.15.6:
follow-redirects@^1.0.0, follow-redirects@^1.15.11, follow-redirects@^1.15.6:
version "1.15.11"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340"
integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==
@@ -11007,16 +11018,18 @@ jiti@^2.6.1:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92"
integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==
joi@^17.7.0:
version "17.13.3"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec"
integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==
joi@^18.0.1:
version "18.0.2"
resolved "https://registry.yarnpkg.com/joi/-/joi-18.0.2.tgz#30ced6aed00a7848cc11f92859515258301dc3a4"
integrity sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==
dependencies:
"@hapi/hoek" "^9.3.0"
"@hapi/topo" "^5.1.0"
"@sideway/address" "^4.1.5"
"@sideway/formula" "^3.0.1"
"@sideway/pinpoint" "^2.0.0"
"@hapi/address" "^5.1.1"
"@hapi/formula" "^3.0.2"
"@hapi/hoek" "^11.0.7"
"@hapi/pinpoint" "^2.0.1"
"@hapi/tlds" "^1.1.1"
"@hapi/topo" "^6.0.2"
"@standard-schema/spec" "^1.0.0"
jose@4.11.0:
version "4.11.0"
@@ -11053,10 +11066,10 @@ js-sizeof@0.0.1:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
js-yaml@4.1.1, js-yaml@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
dependencies:
argparse "^2.0.1"
@@ -12273,7 +12286,7 @@ minimist@1.2.7:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.7:
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@@ -13622,10 +13635,10 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-dom@19.1.0:
version "19.1.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.0.tgz#133558deca37fa1d682708df8904b25186793623"
integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
react-dom@19.1.2:
version "19.1.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.2.tgz#18ce6b14f682f4871a69cc6b2568d42af2cd5a33"
integrity sha512-dEoydsCp50i7kS1xHOmPXq4zQYoGWedUsvqv9H6zdif2r7yLHygyfP9qou71TulRN0d6ng9EbRVsQhSqfUc19g==
dependencies:
scheduler "^0.26.0"
@@ -13741,15 +13754,15 @@ react-scan@0.4.3:
optionalDependencies:
unplugin "2.1.0"
react-virtuoso@4.12.3:
version "4.12.3"
resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.12.3.tgz#beecf0582b31058c5a6ed3ec58fc43fd780e5844"
integrity sha512-6X1p/sU7hecmjDZMAwN+r3go9EVjofKhwkUbVlL8lXhBZecPv9XVCkZ/kBPYOr0Mv0Vl5+Ziwgexg9Kh7+NNXQ==
react-virtuoso@4.17.0:
version "4.17.0"
resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.17.0.tgz#e81f2da99792cfd9317e910b243d847ebeb09248"
integrity sha512-od3pi2v13v31uzn5zPXC2u3ouISFCVhjFVFch2VvS2Cx7pWA2F1aJa3XhNTN2F07M3lhfnMnsmGeH+7wZICr7w==
react@19.1.0:
version "19.1.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75"
integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
react@19.1.2:
version "19.1.2"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.2.tgz#fd75c4f684b52fe45101381535c58486f4407545"
integrity sha512-MdWVitvLbQULD+4DP8GYjZUrepGW7d+GQkNVqJEzNxE+e9WIa4egVFE/RDfVb1u9u/Jw7dNMmPB4IqxzbFYJ0w==
read-binary-file-arch@^1.0.6:
version "1.0.6"
@@ -14118,35 +14131,35 @@ rollup@^2.43.1, rollup@^2.77.2:
optionalDependencies:
fsevents "~2.3.2"
rollup@^4.30.1:
version "4.52.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.4.tgz#71e64cce96a865fcbaa6bb62c6e82807f4e378a1"
integrity sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==
rollup@^4.34.9:
version "4.53.3"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.53.3.tgz#dbc8cd8743b38710019fb8297e8d7a76e3faa406"
integrity sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==
dependencies:
"@types/estree" "1.0.8"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.52.4"
"@rollup/rollup-android-arm64" "4.52.4"
"@rollup/rollup-darwin-arm64" "4.52.4"
"@rollup/rollup-darwin-x64" "4.52.4"
"@rollup/rollup-freebsd-arm64" "4.52.4"
"@rollup/rollup-freebsd-x64" "4.52.4"
"@rollup/rollup-linux-arm-gnueabihf" "4.52.4"
"@rollup/rollup-linux-arm-musleabihf" "4.52.4"
"@rollup/rollup-linux-arm64-gnu" "4.52.4"
"@rollup/rollup-linux-arm64-musl" "4.52.4"
"@rollup/rollup-linux-loong64-gnu" "4.52.4"
"@rollup/rollup-linux-ppc64-gnu" "4.52.4"
"@rollup/rollup-linux-riscv64-gnu" "4.52.4"
"@rollup/rollup-linux-riscv64-musl" "4.52.4"
"@rollup/rollup-linux-s390x-gnu" "4.52.4"
"@rollup/rollup-linux-x64-gnu" "4.52.4"
"@rollup/rollup-linux-x64-musl" "4.52.4"
"@rollup/rollup-openharmony-arm64" "4.52.4"
"@rollup/rollup-win32-arm64-msvc" "4.52.4"
"@rollup/rollup-win32-ia32-msvc" "4.52.4"
"@rollup/rollup-win32-x64-gnu" "4.52.4"
"@rollup/rollup-win32-x64-msvc" "4.52.4"
"@rollup/rollup-android-arm-eabi" "4.53.3"
"@rollup/rollup-android-arm64" "4.53.3"
"@rollup/rollup-darwin-arm64" "4.53.3"
"@rollup/rollup-darwin-x64" "4.53.3"
"@rollup/rollup-freebsd-arm64" "4.53.3"
"@rollup/rollup-freebsd-x64" "4.53.3"
"@rollup/rollup-linux-arm-gnueabihf" "4.53.3"
"@rollup/rollup-linux-arm-musleabihf" "4.53.3"
"@rollup/rollup-linux-arm64-gnu" "4.53.3"
"@rollup/rollup-linux-arm64-musl" "4.53.3"
"@rollup/rollup-linux-loong64-gnu" "4.53.3"
"@rollup/rollup-linux-ppc64-gnu" "4.53.3"
"@rollup/rollup-linux-riscv64-gnu" "4.53.3"
"@rollup/rollup-linux-riscv64-musl" "4.53.3"
"@rollup/rollup-linux-s390x-gnu" "4.53.3"
"@rollup/rollup-linux-x64-gnu" "4.53.3"
"@rollup/rollup-linux-x64-musl" "4.53.3"
"@rollup/rollup-openharmony-arm64" "4.53.3"
"@rollup/rollup-win32-arm64-msvc" "4.53.3"
"@rollup/rollup-win32-ia32-msvc" "4.53.3"
"@rollup/rollup-win32-x64-gnu" "4.53.3"
"@rollup/rollup-win32-x64-msvc" "4.53.3"
fsevents "~2.3.2"
rpc-websockets@10.0.0:
@@ -14193,7 +14206,7 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
rxjs@^7.5.5, rxjs@^7.8.0:
rxjs@^7.5.5, rxjs@^7.8.0, rxjs@^7.8.2:
version "7.8.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b"
integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
@@ -15233,7 +15246,7 @@ tinycache@1.1.2:
dependencies:
js-sizeof "0.0.1"
tinyglobby@^0.2.10:
tinyglobby@^0.2.10, tinyglobby@^0.2.13:
version "0.2.15"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
@@ -15998,14 +16011,17 @@ vite-plugin-pwa@0.21.1:
workbox-build "^7.3.0"
workbox-window "^7.3.0"
vite@6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.1.tgz#ae865d4bb93a11844be1bc647c8b2dd1856ea180"
integrity sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==
vite@6.4.1:
version "6.4.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-6.4.1.tgz#afbe14518cdd6887e240a4b0221ab6d0ce733f96"
integrity sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==
dependencies:
esbuild "^0.25.0"
fdir "^6.4.4"
picomatch "^4.0.2"
postcss "^8.5.3"
rollup "^4.30.1"
rollup "^4.34.9"
tinyglobby "^0.2.13"
optionalDependencies:
fsevents "~2.3.3"
@@ -16019,16 +16035,16 @@ void-elements@3.1.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
wait-on@7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.0.1.tgz#5cff9f8427e94f4deacbc2762e6b0a489b19eae9"
integrity sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==
wait-on@9.0.3:
version "9.0.3"
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-9.0.3.tgz#3ea858db0b854039e6aff5f323885aaef25e32bf"
integrity sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==
dependencies:
axios "^0.27.2"
joi "^17.7.0"
axios "^1.13.2"
joi "^18.0.1"
lodash "^4.17.21"
minimist "^1.2.7"
rxjs "^7.8.0"
minimist "^1.2.8"
rxjs "^7.8.2"
wcwidth@^1.0.1:
version "1.0.1"