mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
@@ -32,7 +32,7 @@ jobs:
|
||||
run: yarn install --frozen-lockfile --ignore-engines
|
||||
# make sure the ipfs executable is executable
|
||||
- name: Download IPFS and set permissions (with Node v22)
|
||||
run: node electron/download-ipfs && sudo chmod +x bin/linux/ipfs
|
||||
run: BUILD_ARCH=${{ matrix.arch }} node electron/download-ipfs && sudo chmod +x bin/linux/ipfs
|
||||
- name: Build React app (with Node v22)
|
||||
run: CI='' NODE_ENV=production yarn build
|
||||
- name: Build Electron app for Linux (arm64)
|
||||
@@ -41,15 +41,26 @@ jobs:
|
||||
- name: Build Electron app for Linux (x64)
|
||||
if: ${{ matrix.arch == 'x64' }}
|
||||
run: yarn electron:build:linux:x64
|
||||
- name: List out directory
|
||||
run: ls -laR out/make
|
||||
- name: Create static HTML release archive
|
||||
run: |
|
||||
VERSION=$(node -e "console.log(require('./package.json').version)")
|
||||
HTML_ARCHIVE_DIR="5chan-html-$VERSION"
|
||||
mkdir -p dist
|
||||
rm -rf "$HTML_ARCHIVE_DIR"
|
||||
cp -R build "$HTML_ARCHIVE_DIR"
|
||||
zip -r "dist/${HTML_ARCHIVE_DIR}.zip" "$HTML_ARCHIVE_DIR"
|
||||
rm -rf "$HTML_ARCHIVE_DIR"
|
||||
- name: List dist directory
|
||||
run: ls dist
|
||||
run: ls -la dist
|
||||
|
||||
# publish version release
|
||||
- name: Generate release body
|
||||
run: node scripts/release-body > release-body.txt
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: 'dist/5chan*.AppImage,dist/5chan*-arm64.AppImage,dist/5chan-html*.zip'
|
||||
artifacts: 'out/make/AppImage/**/*.AppImage,dist/5chan-html*.zip'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
replacesArtifacts: true
|
||||
omitBody: true
|
||||
@@ -89,7 +100,12 @@ jobs:
|
||||
- run: pip install setuptools
|
||||
|
||||
- name: Install dependencies (with Node v22)
|
||||
run: yarn install --frozen-lockfile --ignore-engines
|
||||
run: |
|
||||
for i in 1 2 3; do
|
||||
yarn install --frozen-lockfile --ignore-engines && break
|
||||
echo "Retry $i..."
|
||||
sleep 10
|
||||
done
|
||||
# make sure the ipfs executable is executable
|
||||
- name: Download IPFS and set permissions (with Node v22)
|
||||
run: node electron/download-ipfs && sudo chmod +x bin/mac/ipfs
|
||||
@@ -104,15 +120,23 @@ jobs:
|
||||
else
|
||||
yarn electron:build:mac:x64
|
||||
fi
|
||||
- name: List dist directory
|
||||
run: ls dist
|
||||
- name: List out directory
|
||||
run: ls -laR out/make
|
||||
# Rename DMG to include architecture (ZIPs already include arch from Forge)
|
||||
- name: Rename DMG with architecture suffix
|
||||
run: |
|
||||
for f in out/make/*.dmg; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f" .dmg)
|
||||
mv "$f" "out/make/${base}-${{ matrix.arch }}.dmg"
|
||||
done
|
||||
|
||||
# publish version release
|
||||
- name: Generate release body
|
||||
run: node scripts/release-body > release-body.txt
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: 'dist/5chan*.dmg,dist/5chan*-arm64.dmg'
|
||||
artifacts: 'out/make/*.dmg,out/make/zip/**/*.zip'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
replacesArtifacts: true
|
||||
omitBody: true
|
||||
@@ -138,15 +162,15 @@ jobs:
|
||||
run: npx cross-env NODE_ENV=production yarn build
|
||||
- name: Build Electron app for Windows (x64)
|
||||
run: yarn electron:build:windows
|
||||
- name: List dist directory
|
||||
run: dir dist
|
||||
- name: List out directory
|
||||
run: dir out\make\squirrel.windows\x64
|
||||
|
||||
# publish version release
|
||||
- name: Generate release body
|
||||
run: node scripts/release-body > release-body.txt
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: 'dist/5chan*.exe'
|
||||
artifacts: 'out/make/squirrel.windows/x64/*.exe'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
replacesArtifacts: true
|
||||
omitBody: true
|
||||
@@ -208,6 +232,8 @@ jobs:
|
||||
finalize-release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [linux, mac, windows, android]
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
|
||||
@@ -118,15 +118,17 @@ Set up these hooks for this project:
|
||||
| Hook | Command | Purpose |
|
||||
|------|---------|---------|
|
||||
| `afterFileEdit` | `npx oxfmt <file>` | Auto-format files after AI edits |
|
||||
| `afterFileEdit` | `.cursor/hooks/yarn-install.sh` | Run `yarn install` when `package.json` changes to keep `yarn.lock` in sync |
|
||||
| `stop` | `yarn build && yarn lint && yarn type-check && (yarn audit || true)` | Build, verify code, and check security when agent finishes. Note: `yarn audit` returns non-zero on vulnerabilities, so `|| true` makes it informational only |
|
||||
|
||||
### Why Use Hooks
|
||||
|
||||
- **Consistent formatting** — Every file follows the same style
|
||||
- **Keep lockfile in sync** — `yarn install` runs automatically when `package.json` changes, preventing stale `yarn.lock` files
|
||||
- **Catch build errors** — `yarn build` catches compilation errors that would break production
|
||||
- **Catch issues early** — Lint and type errors are caught before commit/CI
|
||||
- **Security awareness** — `yarn audit` flags known vulnerabilities in dependencies
|
||||
- **Less manual work** — No need to run `yarn build`, `yarn lint`, `yarn type-check`, `yarn audit` manually
|
||||
- **Less manual work** — No need to run `yarn install`, `yarn build`, `yarn lint`, `yarn type-check`, `yarn audit` manually
|
||||
|
||||
### Example Hook Scripts
|
||||
|
||||
@@ -158,6 +160,28 @@ echo "=== yarn audit ===" && (yarn audit || true) # || true makes audit informa
|
||||
exit 0
|
||||
```
|
||||
|
||||
**Yarn install hook** (runs when `package.json` is edited):
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Run yarn install when package.json is changed
|
||||
# Hook receives JSON via stdin with file_path
|
||||
|
||||
input=$(cat)
|
||||
file_path=$(echo "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:.*"\([^"]*\)"/\1/')
|
||||
|
||||
if [ -z "$file_path" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$file_path" = "package.json" ]; then
|
||||
cd "$(dirname "$0")/../.." || exit 0
|
||||
echo "package.json changed - running yarn install to update yarn.lock..."
|
||||
yarn install
|
||||
fi
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
Consult your AI tool's documentation for how to configure hooks (e.g., `hooks.json` for Cursor/Claude Code).
|
||||
|
||||
## Recommended MCP Servers
|
||||
|
||||
@@ -1,3 +1,46 @@
|
||||
## [0.6.4](https://github.com/plebbit/plebchan/compare/v0.6.3...v0.6.4) (2026-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **components:** remove redundant instant scroll from quote links ([1bd1d34](https://github.com/plebbit/plebchan/commit/1bd1d34feebe39cdb319cefc4c67cf681e886032))
|
||||
* include manual post-number quotes in reply backlinks ([702816f](https://github.com/plebbit/plebchan/commit/702816f99b0e31d4d9b59e11831a519caf1c9e97))
|
||||
* **post:** improve user ID display with domain detection and length limits ([9d17213](https://github.com/plebbit/plebchan/commit/9d17213e6606fe42908131cf26cb64e1ed4061b1))
|
||||
* **post:** prevent quote hover highlight on op cards ([b8546a8](https://github.com/plebbit/plebchan/commit/b8546a86c55d25746b0a119e0519d77e36ae25e7))
|
||||
* **release:** remove duplicate architecture suffixes from artifact names ([b30eb15](https://github.com/plebbit/plebchan/commit/b30eb150d05707daee48b0599053c9fee15c1aac))
|
||||
* **release:** restore 5chan html zip artifact in tag releases ([cdd6be9](https://github.com/plebbit/plebchan/commit/cdd6be915ed98ef696b8efe04c2472654c457253))
|
||||
* **reply modal:** restore multiline quote insertion ([db70a91](https://github.com/plebbit/plebchan/commit/db70a9148b6a9d34d93ef8246cd7c062a285e29f))
|
||||
* shorten rendered user ID display from 12 to 8 characters ([24d329a](https://github.com/plebbit/plebchan/commit/24d329a3294cd49045410257472421d79359e990))
|
||||
* show OP badge for number-based quote links ([223ec4c](https://github.com/plebbit/plebchan/commit/223ec4c4943fba789a9e65b38fcbea08764ebfde))
|
||||
* update subscriptions subtitle to present tense ([8636005](https://github.com/plebbit/plebchan/commit/8636005f12eca2273aa8194e1293c80e9eff1888))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add copy user ID menu item and rename copy link to copy direct link ([4a96f88](https://github.com/plebbit/plebchan/commit/4a96f88efd2b21578cfc823b822a7286956bd9a1))
|
||||
* **post:** add backlinks for quotedCids ([c7c8c46](https://github.com/plebbit/plebchan/commit/c7c8c46557f09decee5aadaf0e65a087bd7c725b))
|
||||
* **posts:** support pseudonymityMode per-reply hiding ([c1b0d13](https://github.com/plebbit/plebchan/commit/c1b0d13bb7f26d94513e74b67e79c50cca997783))
|
||||
* **release:** extract one-liner release description ([3be7839](https://github.com/plebbit/plebchan/commit/3be78393d1af103ae6323d8e1615f742d4211a27))
|
||||
* render >>{number} as interactive quote links with hover preview ([c4b6c77](https://github.com/plebbit/plebchan/commit/c4b6c77d4cb49357065fa21b51fbf3209895c58a))
|
||||
* **reply-modal:** insert quoted post numbers at textarea caret ([8a192b4](https://github.com/plebbit/plebchan/commit/8a192b41b465995ce3421503f6811281c1aca3cc))
|
||||
|
||||
|
||||
|
||||
## [0.6.3](https://github.com/plebbit/plebchan/compare/v0.6.2...v0.6.3) (2026-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **electron-forge:** add app icon configuration for all platforms ([a2d0869](https://github.com/plebbit/plebchan/commit/a2d086990484ea4cf823282d997c89719f5c7bc9))
|
||||
* **electron:** fix production build crashes ([388a120](https://github.com/plebbit/plebchan/commit/388a120c8ea387a48fa24152caac5c55a4c33734))
|
||||
* **find-forge-executable:** make appName preference effective ([16012b4](https://github.com/plebbit/plebchan/commit/16012b42395523b8770411c65d82767da41fdb81))
|
||||
* **forge.config.js:** remove malformed iconUrl from Squirrel config ([c4e2f75](https://github.com/plebbit/plebchan/commit/c4e2f753e38a0d69198e7aba83c0b4968671e180))
|
||||
* **package.json:** remove unneeded var ([0c6e03d](https://github.com/plebbit/plebchan/commit/0c6e03d039d0303b3d7e896ff5e9ad169527db80))
|
||||
* **release:** resolve build failures for v0.6.3 ([01bda3b](https://github.com/plebbit/plebchan/commit/01bda3bffb3ba423a4fda447668dc3682263e945))
|
||||
* resolve PR 877 issues - route params, artifact paths, and build configs ([50ee10c](https://github.com/plebbit/plebchan/commit/50ee10cbc9055d9e2aa0e67df66b325b8f355b96))
|
||||
|
||||
|
||||
|
||||
## [0.6.2](https://github.com/plebbit/plebchan/compare/v0.6.1...v0.6.2) (2026-01-26)
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -3,13 +3,13 @@ import { CapacitorConfig } from '@capacitor/cli';
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'fivechan.android',
|
||||
appName: '5chan',
|
||||
webDir: 'dist',
|
||||
webDir: 'build',
|
||||
plugins: {
|
||||
CapacitorHttp: {
|
||||
enabled: true,
|
||||
},
|
||||
FileUploader: {
|
||||
enabled: true
|
||||
enabled: true,
|
||||
},
|
||||
StatusBar: {
|
||||
style: 'Dark',
|
||||
@@ -21,8 +21,8 @@ const config: CapacitorConfig = {
|
||||
},
|
||||
},
|
||||
server: {
|
||||
androidScheme: 'https'
|
||||
}
|
||||
androidScheme: 'https',
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
export default config;
|
||||
|
||||
+1
-1
@@ -319,7 +319,7 @@ const createMainWindow = () => {
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// Set app name and dock icon for development mode on macOS
|
||||
if (process.platform === 'darwin') {
|
||||
if (process.platform === 'darwin' && isDev) {
|
||||
app.setName('seedit');
|
||||
if (app.dock) {
|
||||
const iconPath = path.join(dirname, '..', isDev ? 'public' : 'build', 'icon.png');
|
||||
|
||||
+2
-2
@@ -69,7 +69,7 @@ const config = {
|
||||
config: {
|
||||
name: '5chan',
|
||||
icon: './public/icon.png',
|
||||
format: 'UDZO',
|
||||
format: 'ULFO',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -82,7 +82,7 @@ const config = {
|
||||
platforms: ['win32'],
|
||||
config: {
|
||||
name: '5chan',
|
||||
setupIcon: './public/favicon.ico',
|
||||
setupIcon: './public/windows-icon.ico',
|
||||
},
|
||||
},
|
||||
// Linux
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "5chan",
|
||||
"version": "0.6.2",
|
||||
"version": "0.6.4",
|
||||
"description": "A bitsocial client with a 4chan UI",
|
||||
"type": "module",
|
||||
"author": "Bitsocial Labs",
|
||||
@@ -114,11 +114,11 @@
|
||||
"cz-conventional-changelog": "3.3.0",
|
||||
"decompress": "4.2.1",
|
||||
"electron": "36.9.5",
|
||||
"@electron-forge/cli": "7.6.0",
|
||||
"@electron-forge/maker-dmg": "7.6.0",
|
||||
"@electron-forge/maker-squirrel": "7.6.0",
|
||||
"@electron-forge/maker-zip": "7.6.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.6.0",
|
||||
"@electron-forge/cli": "7.8.0",
|
||||
"@electron-forge/maker-dmg": "7.8.0",
|
||||
"@electron-forge/maker-squirrel": "7.8.0",
|
||||
"@electron-forge/maker-zip": "7.8.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.8.0",
|
||||
"@reforged/maker-appimage": "5.1.1",
|
||||
"husky": "4.3.8",
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
@@ -157,7 +157,7 @@
|
||||
"diff": "^8.0.3",
|
||||
"lodash": "4.17.23",
|
||||
"seroval": "1.4.1",
|
||||
"tar": "7.5.4"
|
||||
"tar": "6.2.1"
|
||||
},
|
||||
"main": "electron/main.js",
|
||||
"lint-staged": {
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "واجهة",
|
||||
"invalid_url": "رابط غير صالح",
|
||||
"image_search": "البحث عن صورة",
|
||||
"copy_link": "نسخ الرابط",
|
||||
"hide_post": "إخفاء المنشور",
|
||||
"hide_thread": "إخفاء الموضوع",
|
||||
"unhide_post": "إظهار المنشور",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "حذف الصورة الرمزية",
|
||||
"updates": "التحديثات",
|
||||
"downloading_board": "جارٍ تنزيل المنتدى",
|
||||
"subscriptions_subtitle": "لقد اشتركت في {{count}} لوحة",
|
||||
"subscriptions_subtitle": "أنت مشترك في {{count}} لوحة",
|
||||
"all_boards": "جميع اللوحات",
|
||||
"nsfw_boards_only": "لوحات NSFW فقط",
|
||||
"worksafe_boards_only": "لوحات آمنة للعمل فقط",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "في انتظار موافقة المشرف، غير مرئي للمستخدمين",
|
||||
"copy_user_id": "نسخ معرف المستخدم",
|
||||
"copy_direct_link": "نسخ الرابط المباشر"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "ইন্টারফেস",
|
||||
"invalid_url": "অবৈধ ইউআরএল",
|
||||
"image_search": "চিত্র অনুসন্ধান",
|
||||
"copy_link": "লিংক অনুলিপি করুন",
|
||||
"hide_post": "পোস্ট লুকাও",
|
||||
"hide_thread": "থ্রেড লুকাও",
|
||||
"unhide_post": "পোস্ট প্রদর্শন করুন",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "অবতার মুছুন",
|
||||
"updates": "আপডেট",
|
||||
"downloading_board": "বোর্ড ডাউনলোড করা হচ্ছে",
|
||||
"subscriptions_subtitle": "আপনি {{count}}টি board-এ সাবস্ক্রাইব করেছেন",
|
||||
"subscriptions_subtitle": "আপনি {{count}}টি board-এ সাবস্ক্রাইব করছেন",
|
||||
"all_boards": "সব বোর্ড",
|
||||
"nsfw_boards_only": "NSFW বোর্ড শুধুমাত্র",
|
||||
"worksafe_boards_only": "কাজের জন্য নিরাপদ বোর্ড শুধুমাত্র",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "মডারেটর অনুমোদনের জন্য অপেক্ষা করছে, ব্যবহারকারীদের কাছে দৃশ্যমান নয়",
|
||||
"copy_user_id": "ইউজার আইডি কপি করুন",
|
||||
"copy_direct_link": "সরাসরি লিংক অনুলিপি করুন"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "rozhraní",
|
||||
"invalid_url": "Neplatná adresa URL",
|
||||
"image_search": "hledání obrázku",
|
||||
"copy_link": "kopírovat odkaz",
|
||||
"hide_post": "skrýt příspěvek",
|
||||
"hide_thread": "skrýt vlákno",
|
||||
"unhide_post": "zobrazit příspěvek",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Smazat avatar",
|
||||
"updates": "Aktualizace",
|
||||
"downloading_board": "stahování fóra",
|
||||
"subscriptions_subtitle": "Přihlásili jste se k {{count}} boardu",
|
||||
"subscriptions_subtitle": "Jste přihlášeni k {{count}} boards",
|
||||
"all_boards": "Všechny desky",
|
||||
"nsfw_boards_only": "Pouze NSFW desky",
|
||||
"worksafe_boards_only": "Pouze bezpečné desky",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Čekání na schválení moderatorem, není viditelné uživatelům",
|
||||
"copy_user_id": "kopírovat ID uživatele",
|
||||
"copy_direct_link": "kopírovat přímý odkaz"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interface",
|
||||
"invalid_url": "Ugyldig webadresse",
|
||||
"image_search": "billedsøgning",
|
||||
"copy_link": "kopier link",
|
||||
"hide_post": "skjul post",
|
||||
"hide_thread": "skjul tråd",
|
||||
"unhide_post": "vis indlæg",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Slet avatar",
|
||||
"updates": "Opdateringer",
|
||||
"downloading_board": "downloader forum",
|
||||
"subscriptions_subtitle": "Du har abonneret på {{count}} board",
|
||||
"subscriptions_subtitle": "Du er abonneret på {{count}} boards",
|
||||
"all_boards": "Alle tavler",
|
||||
"nsfw_boards_only": "Kun NSFW-tavler",
|
||||
"worksafe_boards_only": "Kun arbejdssikre tavler",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Afventer moderatorgodkendelse, ikke synlig for brugere",
|
||||
"copy_user_id": "kopier bruger-ID",
|
||||
"copy_direct_link": "kopier direkte link"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "Schnittstelle",
|
||||
"invalid_url": "Ungültige URL",
|
||||
"image_search": "Bildersuche",
|
||||
"copy_link": "Link kopieren",
|
||||
"hide_post": "Beitrag ausblenden",
|
||||
"hide_thread": "Thread ausblenden",
|
||||
"unhide_post": "Beitrag anzeigen",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Avatar löschen",
|
||||
"updates": "Aktualisierungen",
|
||||
"downloading_board": "Forum wird heruntergeladen",
|
||||
"subscriptions_subtitle": "Sie haben {{count}} Board abonniert",
|
||||
"subscriptions_subtitle": "Sie sind {{count}} Board abonniert",
|
||||
"all_boards": "Alle Boards",
|
||||
"nsfw_boards_only": "Nur NSFW-Boards",
|
||||
"worksafe_boards_only": "Nur arbeitsplatzsichere Boards",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Typ",
|
||||
"yes": "Ja",
|
||||
"no": "Nein",
|
||||
"pending_mod_approval": "Ausstehende Moderatorgenehmigung, für Benutzer nicht sichtbar"
|
||||
"pending_mod_approval": "Ausstehende Moderatorgenehmigung, für Benutzer nicht sichtbar",
|
||||
"copy_user_id": "Benutzer-ID kopieren",
|
||||
"copy_direct_link": "Direktlink kopieren"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "διεπαφή",
|
||||
"invalid_url": "Μη έγκυρη διεύθυνση URL",
|
||||
"image_search": "αναζήτηση εικόνων",
|
||||
"copy_link": "αντιγραφή συνδέσμου",
|
||||
"hide_post": "απόκρυψη ανάρτησης",
|
||||
"hide_thread": "απόκρυψη νήματος",
|
||||
"unhide_post": "εμφάνιση ανάρτησης",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Διαγραφή avatar",
|
||||
"updates": "Ενημερώσεις",
|
||||
"downloading_board": "λήψη φόρουμ",
|
||||
"subscriptions_subtitle": "Έχετε εγγραφεί σε {{count}} board",
|
||||
"subscriptions_subtitle": "Είστε εγγεγραμμένοι σε {{count}} boards",
|
||||
"all_boards": "Όλες οι σανίδες",
|
||||
"nsfw_boards_only": "Μόνο σανίδες NSFW",
|
||||
"worksafe_boards_only": "Μόνο ασφαλείς σανίδες",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Σε αναμονή έγκρισης διαχειριστή, δεν είναι ορατό στους χρήστες",
|
||||
"copy_user_id": "αντιγραφή ID χρήστη",
|
||||
"copy_direct_link": "αντιγραφή άμεσου συνδέσμου"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interface",
|
||||
"invalid_url": "Invalid URL",
|
||||
"image_search": "image search",
|
||||
"copy_link": "Copy link",
|
||||
"hide_post": "Hide post",
|
||||
"hide_thread": "Hide thread",
|
||||
"unhide_post": "Unhide post",
|
||||
@@ -249,7 +248,7 @@
|
||||
"moderator_of_this_board": "This user is a moderator of this board",
|
||||
"administrator_of_this_board": "This user is an administrator of this board",
|
||||
"copy_content_id": "Copy content ID",
|
||||
"subscriptions_subtitle": "You have subscribed to {{count}} boards",
|
||||
"subscriptions_subtitle": "You are subscribed to {{count}} boards",
|
||||
"bitsocial_account": "Bitsocial Account",
|
||||
"p2p_options": "P2P options",
|
||||
"purge": "purge",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users",
|
||||
"copy_user_id": "Copy user ID",
|
||||
"copy_direct_link": "Copy direct link"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interfaz",
|
||||
"invalid_url": "URL no válida",
|
||||
"image_search": "búsqueda de imágenes",
|
||||
"copy_link": "Copiar enlace",
|
||||
"hide_post": "Ocultar publicación",
|
||||
"hide_thread": "Ocultar hilo",
|
||||
"unhide_post": "Mostrar publicación",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Eliminar avatar",
|
||||
"updates": "Actualizaciones",
|
||||
"downloading_board": "descargando foro",
|
||||
"subscriptions_subtitle": "Te has suscrito a {{count}} board",
|
||||
"subscriptions_subtitle": "Estás suscrito a {{count}} boards",
|
||||
"all_boards": "Todas las tablas",
|
||||
"nsfw_boards_only": "Solo tablas NSFW",
|
||||
"worksafe_boards_only": "Solo tablas seguras para el trabajo",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Tipo",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pendiente de aprobación del moderador, no visible para los usuarios"
|
||||
"pending_mod_approval": "Pendiente de aprobación del moderador, no visible para los usuarios",
|
||||
"copy_user_id": "Copiar ID de usuario",
|
||||
"copy_direct_link": "Copiar enlace directo"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "رابط",
|
||||
"invalid_url": "آدرس اینترنتی نامعتبر است",
|
||||
"image_search": "جستجوی تصویر",
|
||||
"copy_link": "رونوشت لینک",
|
||||
"hide_post": "پنهان کردن پست",
|
||||
"hide_thread": "پنهان کردن موضوع",
|
||||
"unhide_post": "نمایش پست",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "حذف آواتار",
|
||||
"updates": "بهروزرسانیها",
|
||||
"downloading_board": "در حال دانلود تالار",
|
||||
"subscriptions_subtitle": "شما به {{count}} board مشترک شدهاید",
|
||||
"subscriptions_subtitle": "شما به {{count}} board مشترک هستید",
|
||||
"all_boards": "همه تابلوها",
|
||||
"nsfw_boards_only": "فقط تابلوهای NSFW",
|
||||
"worksafe_boards_only": "فقط تابلوهای ایمن برای کار",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "در انتظار تایید مدیر، برای کاربران قابل مشاهده نیست",
|
||||
"copy_user_id": "کپی شناسه کاربر",
|
||||
"copy_direct_link": "رونوشت لینک مستقیم"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "liitäntä",
|
||||
"invalid_url": "Virheellinen URL-osoite",
|
||||
"image_search": "kuvahaku",
|
||||
"copy_link": "kopioi linkki",
|
||||
"hide_post": "piilota viesti",
|
||||
"hide_thread": "piilota keskustelu",
|
||||
"unhide_post": "näytä viesti",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Poista avatar",
|
||||
"updates": "Päivitykset",
|
||||
"downloading_board": "ladataan foorumia",
|
||||
"subscriptions_subtitle": "Olet tilannut {{count}} boardia",
|
||||
"subscriptions_subtitle": "Olet tilannut {{count}} boards",
|
||||
"all_boards": "Kaikki taulut",
|
||||
"nsfw_boards_only": "Vain NSFW-taulut",
|
||||
"worksafe_boards_only": "Vain työturvalliset taulut",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Odottaa moderaattorin hyväksyntää, ei näkyvissä käyttäjille",
|
||||
"copy_user_id": "kopioi käyttäjätunnus",
|
||||
"copy_direct_link": "kopioi suora linkki"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interface",
|
||||
"invalid_url": "Hindi wastong URL",
|
||||
"image_search": "paghahanap ng larawan",
|
||||
"copy_link": "kopyahin ang link",
|
||||
"hide_post": "itago ang post",
|
||||
"hide_thread": "itago ang thread",
|
||||
"unhide_post": "ipakita ang post",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Tanggalin ang avatar",
|
||||
"updates": "Mga Update",
|
||||
"downloading_board": "nagda-download ng board",
|
||||
"subscriptions_subtitle": "Nag-subscribe ka sa {{count}} na board",
|
||||
"subscriptions_subtitle": "Naka-subscribe ka sa {{count}} na boards",
|
||||
"all_boards": "Lahat ng boards",
|
||||
"nsfw_boards_only": "NSFW boards lang",
|
||||
"worksafe_boards_only": "Worksafe boards lang",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Naghihintay ng pag-apruba ng moderator, hindi nakikita ng mga gumagamit",
|
||||
"copy_user_id": "kopyahin ang user ID",
|
||||
"copy_direct_link": "kopyahin ang direct link"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interface",
|
||||
"invalid_url": "URL non valide",
|
||||
"image_search": "recherche d'images",
|
||||
"copy_link": "Copier le lien",
|
||||
"hide_post": "Masquer la publication",
|
||||
"hide_thread": "Masquer le fil",
|
||||
"unhide_post": "Afficher la publication",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Supprimer l'avatar",
|
||||
"updates": "Mises à jour",
|
||||
"downloading_board": "téléchargement du forum",
|
||||
"subscriptions_subtitle": "Vous vous êtes abonné à {{count}} board",
|
||||
"subscriptions_subtitle": "Vous êtes abonné à {{count}} boards",
|
||||
"all_boards": "Tous les tableaux",
|
||||
"nsfw_boards_only": "Tableaux NSFW uniquement",
|
||||
"worksafe_boards_only": "Tableaux sûrs uniquement",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Oui",
|
||||
"no": "Non",
|
||||
"pending_mod_approval": "En attente d'approbation du modérateur, non visible pour les utilisateurs"
|
||||
"pending_mod_approval": "En attente d'approbation du modérateur, non visible pour les utilisateurs",
|
||||
"copy_user_id": "Copier l'ID utilisateur",
|
||||
"copy_direct_link": "Copier le lien direct"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "ממשק",
|
||||
"invalid_url": "כתובת אינטרנט לא תקינה",
|
||||
"image_search": "חיפוש תמונות",
|
||||
"copy_link": "העתק קישור",
|
||||
"hide_post": "הסתר פוסט",
|
||||
"hide_thread": "הסתר ת’רד",
|
||||
"unhide_post": "הצג פוסט",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "מחק אווטר",
|
||||
"updates": "עדכונים",
|
||||
"downloading_board": "מוריד פורום",
|
||||
"subscriptions_subtitle": "נרשמת ל-{{count}} board",
|
||||
"subscriptions_subtitle": "נרשמת ל-{{count}} boards",
|
||||
"all_boards": "כל הלוחות",
|
||||
"nsfw_boards_only": "לוחות NSFW בלבד",
|
||||
"worksafe_boards_only": "לוחות בטוחים לעבודה בלבד",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "ממתין לאישור מנהל, לא ניתן לראות למשתמשים",
|
||||
"copy_user_id": "העתק מזהה משתמש",
|
||||
"copy_direct_link": "העתק קישור ישיר"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "इंटरफेस",
|
||||
"invalid_url": "अमान्य URL",
|
||||
"image_search": "चित्र खोज",
|
||||
"copy_link": "लिंक कॉपी करें",
|
||||
"hide_post": "पोस्ट छुपाएं",
|
||||
"hide_thread": "धागा छुपाएं",
|
||||
"unhide_post": "पोस्ट प्रदर्शित करें",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "अवतार हटाएं",
|
||||
"updates": "अपडेट",
|
||||
"downloading_board": "फोरम डाउनलोड हो रहा है",
|
||||
"subscriptions_subtitle": "आपने {{count}} board की सदस्यता ली है",
|
||||
"subscriptions_subtitle": "आप {{count}} boards की सदस्यता ले रहे हैं",
|
||||
"all_boards": "सभी बोर्ड",
|
||||
"nsfw_boards_only": "केवल NSFW बोर्ड",
|
||||
"worksafe_boards_only": "केवल कार्य-सुरक्षित बोर्ड",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "मॉडरेटर की मंजूरी के लिए प्रतीक्षा में, उपयोगकर्ताओं को दिखाई नहीं देता",
|
||||
"copy_user_id": "यूज़र ID कॉपी करें",
|
||||
"copy_direct_link": "डायरेक्ट लिंक कॉपी करें"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "felület",
|
||||
"invalid_url": "Érvénytelen URL-cím",
|
||||
"image_search": "képkeresés",
|
||||
"copy_link": "link másolása",
|
||||
"hide_post": "bejegyzés elrejtése",
|
||||
"hide_thread": "szál elrejtése",
|
||||
"unhide_post": "bejegyzés megjelenítése",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Avatar törlése",
|
||||
"updates": "Frissítések",
|
||||
"downloading_board": "fórum letöltése",
|
||||
"subscriptions_subtitle": "Feliratkoztál {{count}} boardra",
|
||||
"subscriptions_subtitle": "Fel vagy iratkozva {{count}} boardra",
|
||||
"all_boards": "Minden tábla",
|
||||
"nsfw_boards_only": "Csak NSFW táblák",
|
||||
"worksafe_boards_only": "Csak munkavédelmi táblák",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Moderátor jóváhagyása alatt áll, a felhasználók számára nem látható",
|
||||
"copy_user_id": "felhasználói ID másolása",
|
||||
"copy_direct_link": "közvetlen link másolása"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "antarmuka",
|
||||
"invalid_url": "URL tidak valid",
|
||||
"image_search": "pencarian gambar",
|
||||
"copy_link": "salin tautan",
|
||||
"hide_post": "sembunyikan pos",
|
||||
"hide_thread": "sembunyikan utas",
|
||||
"unhide_post": "tampilkan pos",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Hapus avatar",
|
||||
"updates": "Pembaruan",
|
||||
"downloading_board": "mengunduh forum",
|
||||
"subscriptions_subtitle": "Anda telah berlangganan {{count}} board",
|
||||
"subscriptions_subtitle": "Anda berlangganan {{count}} boards",
|
||||
"all_boards": "Semua papan",
|
||||
"nsfw_boards_only": "Hanya papan NSFW",
|
||||
"worksafe_boards_only": "Hanya papan aman untuk kerja",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Menunggu persetujuan moderator, tidak terlihat oleh pengguna",
|
||||
"copy_user_id": "salin ID pengguna",
|
||||
"copy_direct_link": "salin tautan langsung"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interfaccia",
|
||||
"invalid_url": "URL non valido",
|
||||
"image_search": "ricerca immagini",
|
||||
"copy_link": "Copia link",
|
||||
"hide_post": "Nascondi post",
|
||||
"hide_thread": "Nascondi thread",
|
||||
"unhide_post": "Mostra post",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Elimina avatar",
|
||||
"updates": "Aggiornamenti",
|
||||
"downloading_board": "scaricando board",
|
||||
"subscriptions_subtitle": "Ti sei iscritto a {{count}} board",
|
||||
"subscriptions_subtitle": "Sei iscritto a {{count}} board",
|
||||
"all_boards": "Tutte le bacheche",
|
||||
"nsfw_boards_only": "Solo bacheche NSFW",
|
||||
"worksafe_boards_only": "Solo bacheche sicure per il lavoro",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Tipo",
|
||||
"yes": "Sì",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "In attesa di approvazione del moderatore, non visibile agli utenti"
|
||||
"pending_mod_approval": "In attesa di approvazione del moderatore, non visibile agli utenti",
|
||||
"copy_user_id": "Copia ID utente",
|
||||
"copy_direct_link": "Copia link diretto"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "インターフェース",
|
||||
"invalid_url": "無効なURL",
|
||||
"image_search": "画像検索",
|
||||
"copy_link": "リンクをコピー",
|
||||
"hide_post": "投稿を非表示",
|
||||
"hide_thread": "スレッドを非表示",
|
||||
"unhide_post": "投稿を表示",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "タイプ",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"pending_mod_approval": "モデレーターの承認待ち、ユーザーには表示されません"
|
||||
"pending_mod_approval": "モデレーターの承認待ち、ユーザーには表示されません",
|
||||
"copy_user_id": "ユーザーIDをコピー",
|
||||
"copy_direct_link": "直接リンクをコピー"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "인터페이스",
|
||||
"invalid_url": "잘못된 URL",
|
||||
"image_search": "이미지 검색",
|
||||
"copy_link": "링크 복사",
|
||||
"hide_post": "게시물 숨기기",
|
||||
"hide_thread": "스레드 숨기기",
|
||||
"unhide_post": "게시물 숨기기 해제",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "아바타 삭제",
|
||||
"updates": "업데이트",
|
||||
"downloading_board": "게시판 다운로드 중",
|
||||
"subscriptions_subtitle": "{{count}}개의 board를 구독했습니다",
|
||||
"subscriptions_subtitle": "{{count}}개의 board를 구독 중입니다",
|
||||
"all_boards": "모든 게시판",
|
||||
"nsfw_boards_only": "NSFW 게시판만",
|
||||
"worksafe_boards_only": "직장 안전 게시판만",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "중재자 승인 대기 중, 사용자에게 표시되지 않음",
|
||||
"copy_user_id": "사용자 ID 복사",
|
||||
"copy_direct_link": "직접 링크 복사"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "इंटरफेस",
|
||||
"invalid_url": "अवैध URL",
|
||||
"image_search": "प्रतिमा शोध",
|
||||
"copy_link": "लिंक कॉपी करा",
|
||||
"hide_post": "पोस्ट लपवा",
|
||||
"hide_thread": "थ्रेड लपवा",
|
||||
"unhide_post": "पोस्ट दाखवा",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "अवतार हटवा",
|
||||
"updates": "अपडेट्स",
|
||||
"downloading_board": "फोरम डाउनलोड करत आहे",
|
||||
"subscriptions_subtitle": "तुम्ही {{count}} board ला सदस्यता घेतली आहे",
|
||||
"subscriptions_subtitle": "तुम्ही {{count}} boards ला सदस्यता घेतली आहे",
|
||||
"all_boards": "सर्व बोर्ड",
|
||||
"nsfw_boards_only": "फक्त NSFW बोर्ड",
|
||||
"worksafe_boards_only": "फक्त कामासाठी सुरक्षित बोर्ड",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "संचालकाच्या मंजुरीची प्रतीक्षा, वापरकर्त्यांना दिसत नाही",
|
||||
"copy_user_id": "यूजर ID कॉपी करा",
|
||||
"copy_direct_link": "डायरेक्ट लिंक कॉपी करा"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interface",
|
||||
"invalid_url": "Ongeldige URL",
|
||||
"image_search": "afbeeldingen zoeken",
|
||||
"copy_link": "link kopiëren",
|
||||
"hide_post": "bericht verbergen",
|
||||
"hide_thread": "thread verbergen",
|
||||
"unhide_post": "bericht weergeven",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Avatar verwijderen",
|
||||
"updates": "Updates",
|
||||
"downloading_board": "board downloaden",
|
||||
"subscriptions_subtitle": "Je bent geabonneerd op {{count}} board",
|
||||
"subscriptions_subtitle": "Je bent geabonneerd op {{count}} boards",
|
||||
"all_boards": "Alle borden",
|
||||
"nsfw_boards_only": "Alleen NSFW-borden",
|
||||
"worksafe_boards_only": "Alleen werkveilige borden",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "In afwachting van moderatorgoedkeuring, niet zichtbaar voor gebruikers",
|
||||
"copy_user_id": "gebruikers-ID kopiëren",
|
||||
"copy_direct_link": "directe link kopiëren"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "grensesnitt",
|
||||
"invalid_url": "Ugyldig URL",
|
||||
"image_search": "bilde søk",
|
||||
"copy_link": "kopier lenke",
|
||||
"hide_post": "skjul innlegg",
|
||||
"hide_thread": "skjul tråd",
|
||||
"unhide_post": "vis innlegg",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Slett avatar",
|
||||
"updates": "Oppdateringer",
|
||||
"downloading_board": "laster ned forum",
|
||||
"subscriptions_subtitle": "Du har abonnert på {{count}} board",
|
||||
"subscriptions_subtitle": "Du er abonnent på {{count}} boards",
|
||||
"all_boards": "Alle tavler",
|
||||
"nsfw_boards_only": "Kun NSFW-tavler",
|
||||
"worksafe_boards_only": "Kun arbeidssikre tavler",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Venter på moderatorgodkjenning, ikke synlig for brukere",
|
||||
"copy_user_id": "kopier bruker-ID",
|
||||
"copy_direct_link": "kopier direkte lenke"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interfejs",
|
||||
"invalid_url": "Nieprawidłowy adres URL",
|
||||
"image_search": "wyszukiwanie obrazów",
|
||||
"copy_link": "skopiuj link",
|
||||
"hide_post": "ukryj post",
|
||||
"hide_thread": "ukryj wątek",
|
||||
"unhide_post": "pokaż post",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Usuń avatar",
|
||||
"updates": "Aktualizacje",
|
||||
"downloading_board": "pobieranie forum",
|
||||
"subscriptions_subtitle": "Subskrybujesz {{count}} board",
|
||||
"subscriptions_subtitle": "Subskrybujesz {{count}} boards",
|
||||
"all_boards": "Wszystkie tablice",
|
||||
"nsfw_boards_only": "Tylko tablice NSFW",
|
||||
"worksafe_boards_only": "Tylko bezpieczne tablice",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Oczekiwanie na zatwierdzenie moderatora, niewidoczne dla użytkowników",
|
||||
"copy_user_id": "skopiuj ID użytkownika",
|
||||
"copy_direct_link": "skopiuj bezpośredni link"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interface",
|
||||
"invalid_url": "URL inválida",
|
||||
"image_search": "pesquisa de imagens",
|
||||
"copy_link": "Copiar link",
|
||||
"hide_post": "Ocultar postagem",
|
||||
"hide_thread": "Ocultar tópico",
|
||||
"unhide_post": "Mostrar postagem",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Excluir avatar",
|
||||
"updates": "Atualizações",
|
||||
"downloading_board": "baixando fórum",
|
||||
"subscriptions_subtitle": "Você se inscreveu em {{count}} board",
|
||||
"subscriptions_subtitle": "Você está inscrito em {{count}} boards",
|
||||
"all_boards": "Todos os quadros",
|
||||
"nsfw_boards_only": "Apenas quadros NSFW",
|
||||
"worksafe_boards_only": "Apenas quadros seguros para o trabalho",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Tipo",
|
||||
"yes": "Sim",
|
||||
"no": "Não",
|
||||
"pending_mod_approval": "Aguardando aprovação do moderador, não visível para os usuários"
|
||||
"pending_mod_approval": "Aguardando aprovação do moderador, não visível para os usuários",
|
||||
"copy_user_id": "Copiar ID do usuário",
|
||||
"copy_direct_link": "Copiar link direto"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "interfață",
|
||||
"invalid_url": "URL invalid",
|
||||
"image_search": "căutare de imagini",
|
||||
"copy_link": "copiați linkul",
|
||||
"hide_post": "ascundeți postarea",
|
||||
"hide_thread": "ascundeți firul",
|
||||
"unhide_post": "afișează postarea",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Șterge avatar",
|
||||
"updates": "Actualizări",
|
||||
"downloading_board": "se descarcă forumul",
|
||||
"subscriptions_subtitle": "Te-ai abonat la {{count}} board",
|
||||
"subscriptions_subtitle": "Ești abonat la {{count}} boards",
|
||||
"all_boards": "Toate panourile",
|
||||
"nsfw_boards_only": "Doar panouri NSFW",
|
||||
"worksafe_boards_only": "Doar panouri sigure pentru muncă",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "În așteptarea aprobării moderatorului, nu este vizibil pentru utilizatori",
|
||||
"copy_user_id": "copiați ID-ul utilizatorului",
|
||||
"copy_direct_link": "copiați linkul direct"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "интерфейс",
|
||||
"invalid_url": "Недействительный URL",
|
||||
"image_search": "поиск изображений",
|
||||
"copy_link": "копировать ссылку",
|
||||
"hide_post": "скрыть пост",
|
||||
"hide_thread": "скрыть ветку",
|
||||
"unhide_post": "показать пост",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Удалить аватар",
|
||||
"updates": "Обновления",
|
||||
"downloading_board": "загрузка форума",
|
||||
"subscriptions_subtitle": "Вы подписались на {{count}} board",
|
||||
"subscriptions_subtitle": "Вы подписаны на {{count}} boards",
|
||||
"all_boards": "Все доски",
|
||||
"nsfw_boards_only": "Только доски NSFW",
|
||||
"worksafe_boards_only": "Только безопасные для работы доски",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Тип",
|
||||
"yes": "Да",
|
||||
"no": "Нет",
|
||||
"pending_mod_approval": "Ожидает одобрения модератора, не видно пользователям"
|
||||
"pending_mod_approval": "Ожидает одобрения модератора, не видно пользователям",
|
||||
"copy_user_id": "копировать ID пользователя",
|
||||
"copy_direct_link": "копировать прямую ссылку"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "ndërfaqe",
|
||||
"invalid_url": "URL jo e vlefshëm",
|
||||
"image_search": "kërkim imazhesh",
|
||||
"copy_link": "kopjo linkun",
|
||||
"hide_post": "fsheh postën",
|
||||
"hide_thread": "fsheh fijen",
|
||||
"unhide_post": "shfaq postën",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Fshi avatarin",
|
||||
"updates": "Përditësime",
|
||||
"downloading_board": "duke shkarkuar forum",
|
||||
"subscriptions_subtitle": "Jeni abonuar në {{count}} board",
|
||||
"subscriptions_subtitle": "Jeni abonuar në {{count}} boards",
|
||||
"all_boards": "Të gjitha tabelat",
|
||||
"nsfw_boards_only": "Vetëm tabelat NSFW",
|
||||
"worksafe_boards_only": "Vetëm tabelat e sigurta për punë",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Duke pritur miratimin e moderatorit, jo i dukshëm për përdoruesit",
|
||||
"copy_user_id": "kopjo ID e përdoruesit",
|
||||
"copy_direct_link": "kopjo lidhjen e drejtpërdrejtë"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "gränssnitt",
|
||||
"invalid_url": "Ogiltig URL",
|
||||
"image_search": "bildsökning",
|
||||
"copy_link": "kopiera länk",
|
||||
"hide_post": "dölj inlägg",
|
||||
"hide_thread": "dölj tråd",
|
||||
"unhide_post": "visa inlägg",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Ta bort avatar",
|
||||
"updates": "Uppdateringar",
|
||||
"downloading_board": "laddar ner forum",
|
||||
"subscriptions_subtitle": "Du har prenumererat på {{count}} board",
|
||||
"subscriptions_subtitle": "Du är prenumerant på {{count}} boards",
|
||||
"all_boards": "Alla tavlor",
|
||||
"nsfw_boards_only": "Endast NSFW-tavlor",
|
||||
"worksafe_boards_only": "Endast arbetsplats-säkra tavlor",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Väntar på moderatorgodkännande, inte synligt för användare",
|
||||
"copy_user_id": "kopiera användar-ID",
|
||||
"copy_direct_link": "kopiera direktlänk"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "అంతరాఫ్సు",
|
||||
"invalid_url": "చెల్లని URL",
|
||||
"image_search": "చిత్రాల శోధన",
|
||||
"copy_link": "లింక్ ను కాపీ చేయండి",
|
||||
"hide_post": "పోస్ట్ దాచు",
|
||||
"hide_thread": "థ్రెడ్ దాచు",
|
||||
"unhide_post": "పోస్ట్ చూపించు",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "అవతార్ను తొలగించు",
|
||||
"updates": "నవీకరణలు",
|
||||
"downloading_board": "ఫోరం డౌన్లోడ్ చేయడం",
|
||||
"subscriptions_subtitle": "మీరు {{count}} boardకు సభ్యత్వం పొందారు",
|
||||
"subscriptions_subtitle": "మీరు {{count}} boardsకు సభ్యత్వం పొందారు",
|
||||
"all_boards": "అన్ని బోర్డులు",
|
||||
"nsfw_boards_only": "NSFW బోర్డులు మాత్రమే",
|
||||
"worksafe_boards_only": "పని కోసం సురక్షితమైన బోర్డులు మాత్రమే",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "మాడరేటర్ ఆమోదం కోసం చేతనిస్తోంది, వినియోగదారులకు కనిపించవు",
|
||||
"copy_user_id": "యూజర్ ID కాపీ చేయండి",
|
||||
"copy_direct_link": "డైరెక్ట్ లింక్ కాపీ చేయండి"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "อินเทอร์เฟซ",
|
||||
"invalid_url": "URL ไม่ถูกต้อง",
|
||||
"image_search": "ค้นหารูปภาพ",
|
||||
"copy_link": "คัดลอกลิงก์",
|
||||
"hide_post": "ซ่อนโพสต์",
|
||||
"hide_thread": "ซ่อนเธรด",
|
||||
"unhide_post": "แสดงโพสต์",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "ลบอวตาร",
|
||||
"updates": "อัปเดต",
|
||||
"downloading_board": "กำลังดาวน์โหลดกระดาน",
|
||||
"subscriptions_subtitle": "คุณได้สมัครสมาชิก {{count}} board แล้ว",
|
||||
"subscriptions_subtitle": "คุณกำลังสมัครสมาชิก {{count}} boards",
|
||||
"all_boards": "บอร์ดทั้งหมด",
|
||||
"nsfw_boards_only": "บอร์ด NSFW เท่านั้น",
|
||||
"worksafe_boards_only": "บอร์ดที่ปลอดภัยสำหรับงานเท่านั้น",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "รอการอนุมัติจากผู้ดูแล ไม่มองเห็นสำหรับผู้ใช้",
|
||||
"copy_user_id": "คัดลอก ID ผู้ใช้",
|
||||
"copy_direct_link": "คัดลอกลิงก์โดยตรง"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "arayüz",
|
||||
"invalid_url": "Geçersiz URL",
|
||||
"image_search": "resim arama",
|
||||
"copy_link": "bağlantıyı kopyala",
|
||||
"hide_post": "gönderiyi gizle",
|
||||
"hide_thread": "konuyu gizle",
|
||||
"unhide_post": "gönderiyi göster",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Avatarı sil",
|
||||
"updates": "Güncellemeler",
|
||||
"downloading_board": "forum indiriliyor",
|
||||
"subscriptions_subtitle": "{{count}} board'a abone oldunuz",
|
||||
"subscriptions_subtitle": "{{count}} board'a abonesiniz",
|
||||
"all_boards": "Tüm panolar",
|
||||
"nsfw_boards_only": "Sadece NSFW panoları",
|
||||
"worksafe_boards_only": "Sadece iş için güvenli panolar",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Moderatör onayı beklemede, kullanıcılara görünmüyor",
|
||||
"copy_user_id": "kullanıcı ID'sini kopyala",
|
||||
"copy_direct_link": "doğrudan bağlantıyı kopyala"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "інтерфейс",
|
||||
"invalid_url": "Недійсний URL",
|
||||
"image_search": "пошук зображень",
|
||||
"copy_link": "копіювати посилання",
|
||||
"hide_post": "сховати допис",
|
||||
"hide_thread": "сховати нитку",
|
||||
"unhide_post": "показати допис",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Видалити аватар",
|
||||
"updates": "Оновлення",
|
||||
"downloading_board": "завантаження форуму",
|
||||
"subscriptions_subtitle": "Ви підписались на {{count}} board",
|
||||
"subscriptions_subtitle": "Ви підписані на {{count}} boards",
|
||||
"all_boards": "Всі дошки",
|
||||
"nsfw_boards_only": "Тільки дошки NSFW",
|
||||
"worksafe_boards_only": "Тільки безпечні для роботи дошки",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Очікує затвердження модератора, не видно користувачам",
|
||||
"copy_user_id": "копіювати ID користувача",
|
||||
"copy_direct_link": "копіювати пряме посилання"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "انٹرفیس",
|
||||
"invalid_url": "غیر درست یو آر ایل",
|
||||
"image_search": "تصویر کی تلاش",
|
||||
"copy_link": "لنک کاپی کریں",
|
||||
"hide_post": "پوسٹ چھپائیں",
|
||||
"hide_thread": "دھاگہ چھپائیں",
|
||||
"unhide_post": "پوسٹ دکھائیں",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "اوتار حذف کریں",
|
||||
"updates": "اپڈیٹس",
|
||||
"downloading_board": "فورم ڈاؤن لوڈ ہو رہا ہے",
|
||||
"subscriptions_subtitle": "آپ نے {{count}} board کی سبسکرپشن لی ہے",
|
||||
"subscriptions_subtitle": "آپ {{count}} boards کی سبسکرپشن لے رہے ہیں",
|
||||
"all_boards": "تمام بورڈز",
|
||||
"nsfw_boards_only": "صرف NSFW بورڈز",
|
||||
"worksafe_boards_only": "صرف کام کے لیے محفوظ بورڈز",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "ماڈریٹر کی منظوری کا انتظار، صارفین کے لیے نظر نہیں آتا",
|
||||
"copy_user_id": "صارف کی شناخت کاپی کریں",
|
||||
"copy_direct_link": "براہ راست لنک کاپی کریں"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "giao diện",
|
||||
"invalid_url": "URL không hợp lệ",
|
||||
"image_search": "tìm kiếm hình ảnh",
|
||||
"copy_link": "sao chép liên kết",
|
||||
"hide_post": "Ẩn bài viết",
|
||||
"hide_thread": "Ẩn chủ đề",
|
||||
"unhide_post": "Hiện bài viết",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "Xóa avatar",
|
||||
"updates": "Cập nhật",
|
||||
"downloading_board": "đang tải diễn đàn",
|
||||
"subscriptions_subtitle": "Bạn đã đăng ký {{count}} board",
|
||||
"subscriptions_subtitle": "Bạn đang đăng ký {{count}} boards",
|
||||
"all_boards": "Tất cả bảng",
|
||||
"nsfw_boards_only": "Chỉ bảng NSFW",
|
||||
"worksafe_boards_only": "Chỉ bảng an toàn cho công việc",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "Type",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"pending_mod_approval": "Pending mod approval, not visible to users"
|
||||
"pending_mod_approval": "Chờ duyệt của người điều hành, không hiển thị cho người dùng",
|
||||
"copy_user_id": "sao chép ID người dùng",
|
||||
"copy_direct_link": "sao chép liên kết trực tiếp"
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"interface": "界面",
|
||||
"invalid_url": "无效的网址",
|
||||
"image_search": "图像搜索",
|
||||
"copy_link": "复制链接",
|
||||
"hide_post": "隐藏帖子",
|
||||
"hide_thread": "隐藏主题",
|
||||
"unhide_post": "显示帖子",
|
||||
@@ -247,7 +246,7 @@
|
||||
"delete_avatar": "删除头像",
|
||||
"updates": "更新",
|
||||
"downloading_board": "正在下载论坛",
|
||||
"subscriptions_subtitle": "您已订阅 {{count}} 个 board",
|
||||
"subscriptions_subtitle": "您已订阅 {{count}} 个 boards",
|
||||
"all_boards": "所有板块",
|
||||
"nsfw_boards_only": "仅NSFW板块",
|
||||
"worksafe_boards_only": "仅工作安全板块",
|
||||
@@ -276,5 +275,7 @@
|
||||
"type": "类型",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"pending_mod_approval": "等待版主批准,用户不可见"
|
||||
"pending_mod_approval": "等待版主批准,用户不可见",
|
||||
"copy_user_id": "复制用户ID",
|
||||
"copy_direct_link": "复制直接链接"
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
@@ -1,6 +1,7 @@
|
||||
import { readdirSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -56,6 +57,13 @@ mkdirSync(generatedDir, { recursive: true });
|
||||
const outputPath = join(generatedDir, 'asset-manifest.ts');
|
||||
writeFileSync(outputPath, output, 'utf8');
|
||||
|
||||
// Format with oxfmt so output matches committed (formatted) version
|
||||
try {
|
||||
execSync(`npx oxfmt ${outputPath}`, { stdio: 'ignore' });
|
||||
} catch {
|
||||
// oxfmt not available (e.g. CI without devDependencies), skip formatting
|
||||
}
|
||||
|
||||
console.log(
|
||||
`✅ Generated asset manifest with ${banners.length} banners, ${notFoundImages.length} not-found images, ${buttonImages.length} button images, and ${themeBackgrounds.length} background images`,
|
||||
);
|
||||
|
||||
@@ -101,7 +101,11 @@ const htmlSection = section('Static HTML build', [htmlZip && `- 5chan-html (zip)
|
||||
|
||||
const downloads = [macSection, winSection, linuxSection, androidSection, htmlSection].filter(Boolean).join('\n\n');
|
||||
|
||||
const releaseBody = `This version rebrands the app to 5chan, introducing a set of new features and several performance improvements.
|
||||
// One-liner summary of what changed in this release. Update before each release.
|
||||
const oneLinerDescription =
|
||||
'This version adds multiple-replying support, 4chan-like user anonymity with or without user IDs depending on the board, and several bug fixes.';
|
||||
|
||||
const releaseBody = `${oneLinerDescription}
|
||||
|
||||
- Web app: https://5chan.app
|
||||
- Decentralized web app via IPFS/IPNS gateways (works on any browser): [5chan.eth.limo](https://5chan.eth.limo), [5chan.eth.link](https://5chan.eth.link), [dweb.link/ipfs.io](https://dweb.link/ipns/5chan.eth)
|
||||
|
||||
+4
-4
@@ -170,16 +170,16 @@ const App = () => (
|
||||
<Route path='/mod/catalog/:timeFilterName?' element={null} />
|
||||
<Route path='/mod/catalog/:timeFilterName?/settings' element={null} />
|
||||
|
||||
<Route path='/mod/queue' element={<ModQueueView />} />
|
||||
<Route path='/mod/queue/settings' element={<ModQueueView />} />
|
||||
<Route path='/mod/modqueue' element={<ModQueueView />} />
|
||||
<Route path='/mod/modqueue/settings' element={<ModQueueView />} />
|
||||
|
||||
<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='/:boardIdentifier/queue' element={<ModQueueView />} />
|
||||
<Route path='/:boardIdentifier/queue/settings' element={<ModQueueView />} />
|
||||
<Route path='/:boardIdentifier/modqueue' element={<ModQueueView />} />
|
||||
<Route path='/:boardIdentifier/modqueue/settings' element={<ModQueueView />} />
|
||||
|
||||
<Route path='/:boardIdentifier/thread/:commentCid' element={<Post />} />
|
||||
<Route path='/:boardIdentifier/thread/:commentCid/settings' element={<Post />} />
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { Fragment, useMemo, useState } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Comment, useComment } from '@plebbit/plebbit-react-hooks';
|
||||
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
|
||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import Plebbit from '@plebbit/plebbit-js';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isPostPageView } from '../../lib/utils/view-utils';
|
||||
@@ -15,6 +16,15 @@ import Tooltip from '../../components/tooltip';
|
||||
import styles from '../../views/post/post.module.css';
|
||||
import _ from 'lodash';
|
||||
|
||||
const QuotedCidLink = ({ cid, postCid }: { cid: string; postCid: string }) => {
|
||||
const commentFromStore = useSubplebbitsPagesStore((state) => state.comments[cid]);
|
||||
const commentFromHook = useComment({ commentCid: cid, onlyIfCached: true });
|
||||
// Prefer hook version to ensure 'number' property is populated for deeper nested replies in Virtuoso
|
||||
const quotedComment = commentFromHook?.number !== undefined ? commentFromHook : commentFromStore;
|
||||
const isOP = cid === postCid;
|
||||
return <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotedComment} isOP={isOP} />;
|
||||
};
|
||||
|
||||
const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
||||
const { t } = useTranslation();
|
||||
const params = useParams();
|
||||
@@ -23,7 +33,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
||||
const [showOriginal, setShowOriginal] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { cid, content, deleted, edit, original, parentCid, postCid, pendingApproval, reason, removed, state, subplebbitAddress } = post || {};
|
||||
const { cid, content, deleted, edit, original, parentCid, postCid, pendingApproval, quotedCids, reason, removed, state, subplebbitAddress } = post || {};
|
||||
const banned = !!post?.author?.subplebbit?.banExpiresAt;
|
||||
|
||||
const [showFullComment, setShowFullComment] = useState(false);
|
||||
@@ -43,13 +53,35 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
||||
const isReply = !!parentCid;
|
||||
const isReplyingToReply = isReply && parentCid !== postCid;
|
||||
|
||||
const contentNumbers = useMemo(() => {
|
||||
if (!content) return new Set<number>();
|
||||
const matches = content.matchAll(/(?<![>/\w])>>(\d+)(?![\d/])/g);
|
||||
return new Set([...matches].map((m) => parseInt(m[1], 10)));
|
||||
}, [content]);
|
||||
|
||||
const cidToNumber = usePostNumberStore((state) => state.cidToNumber);
|
||||
const filteredQuotedCids = useMemo(() => {
|
||||
if (!quotedCids?.length) return [];
|
||||
return quotedCids.filter((cid: string) => {
|
||||
const num = cidToNumber[cid];
|
||||
return num === undefined || !contentNumbers.has(num);
|
||||
});
|
||||
}, [quotedCids, cidToNumber, contentNumbers]);
|
||||
|
||||
const shouldShowReplyingToReply = isReplyingToReply && (parentCid ? !contentNumbers.has(cidToNumber[parentCid] ?? -1) : true);
|
||||
|
||||
const stateString = useStateString(post);
|
||||
|
||||
const loadingString = <div className={styles.stateString}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>;
|
||||
|
||||
return (
|
||||
<blockquote className={`${styles.postMessage} ${!isReply && isMobile && styles.clampLines}`}>
|
||||
{isReply && state !== 'failed' && isReplyingToReply && !(deleted || removed) && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
|
||||
{isReply &&
|
||||
state !== 'failed' &&
|
||||
!(deleted || removed) &&
|
||||
(filteredQuotedCids.length > 0
|
||||
? filteredQuotedCids.map((cid: string) => <QuotedCidLink key={cid} cid={cid} postCid={postCid} />)
|
||||
: shouldShowReplyingToReply && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />)}
|
||||
{removed ? (
|
||||
reason ? (
|
||||
<>
|
||||
@@ -72,7 +104,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{!showOriginal && <Markdown content={displayContent} />}
|
||||
{!showOriginal && <Markdown content={displayContent} postCid={postCid} />}
|
||||
{pendingApproval && (
|
||||
<>
|
||||
<br />
|
||||
@@ -88,7 +120,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
||||
)}
|
||||
{edit && original?.content !== content && (
|
||||
<span className={styles.editedInfo}>
|
||||
{showOriginal && <Markdown content={original?.content} />}
|
||||
{showOriginal && <Markdown content={original?.content} postCid={postCid} />}
|
||||
<br />
|
||||
<br />
|
||||
<Trans
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
text-decoration: var(--post-content-link-text-decoration-hover);
|
||||
}
|
||||
|
||||
.markdown .inlineQuoteLink a {
|
||||
color: var(--post-quotelink-text-color);
|
||||
text-decoration: var(--post-quotelink-text-decoration);
|
||||
}
|
||||
|
||||
.markdown .inlineQuoteLink a:hover {
|
||||
color: var(--post-quotelink-text-color-hover);
|
||||
text-decoration: var(--post-quotelink-text-decoration-hover);
|
||||
}
|
||||
|
||||
.hrWrapper {
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ import styles from './markdown.module.css';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { canEmbed } from '../embed';
|
||||
import { is5chanLink, transform5chanLinkToInternal, preprocess5chanPatterns } from '../../lib/utils/url-utils';
|
||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
|
||||
import { useComment } from '@plebbit/plebbit-react-hooks';
|
||||
import ReplyQuotePreview from '../reply-quote-preview';
|
||||
|
||||
interface ContentLinkEmbedProps {
|
||||
children: any;
|
||||
@@ -159,13 +163,40 @@ const spoilerTransform = () => (tree: any) => {
|
||||
interface MarkdownProps {
|
||||
content: string;
|
||||
title?: string;
|
||||
postCid?: string;
|
||||
}
|
||||
|
||||
const renderAnchorLink = (children: React.ReactNode, href: string) => {
|
||||
const NUMBER_QUOTE_HREF_REGEX = /^#q-(\d+)$/;
|
||||
|
||||
const NumberQuoteLink = ({ number, threadPostCid }: { number: number; threadPostCid?: string }) => {
|
||||
const cid = usePostNumberStore((state) => state.numberToCid[number]);
|
||||
const commentFromStore = useSubplebbitsPagesStore((state) => (cid ? state.comments[cid] : undefined));
|
||||
const commentFromHook = useComment({ commentCid: cid, onlyIfCached: true });
|
||||
const comment = commentFromHook?.number !== undefined ? commentFromHook : commentFromStore;
|
||||
const isOP = Boolean(threadPostCid && cid === threadPostCid);
|
||||
|
||||
if (!comment) {
|
||||
return <span>{`>>${number}`}</span>;
|
||||
}
|
||||
|
||||
return <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={comment} isOP={isOP} />;
|
||||
};
|
||||
|
||||
const renderAnchorLink = (children: React.ReactNode, href: string, threadPostCid?: string) => {
|
||||
if (!href) {
|
||||
return <span>{children}</span>;
|
||||
}
|
||||
|
||||
const numberQuoteMatch = href.match(NUMBER_QUOTE_HREF_REGEX);
|
||||
if (numberQuoteMatch) {
|
||||
const number = parseInt(numberQuoteMatch[1], 10);
|
||||
return (
|
||||
<span className={styles.inlineQuoteLink}>
|
||||
<NumberQuoteLink number={number} threadPostCid={threadPostCid} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Check if this is a valid 5chan link that should be handled internally
|
||||
if (is5chanLink(href)) {
|
||||
const internalPath = transform5chanLinkToInternal(href);
|
||||
@@ -215,7 +246,7 @@ const renderAnchorLink = (children: React.ReactNode, href: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Markdown = ({ content, title }: MarkdownProps) => {
|
||||
const Markdown = ({ content, title, postCid }: MarkdownProps) => {
|
||||
const remarkPlugins: any[] = [[supersub]];
|
||||
|
||||
if (content && content.length <= MAX_LENGTH_FOR_GFM) {
|
||||
@@ -285,10 +316,10 @@ const Markdown = ({ content, title }: MarkdownProps) => {
|
||||
console.debug('Invalid URL:', href);
|
||||
}
|
||||
|
||||
return renderAnchorLink(children, href);
|
||||
return renderAnchorLink(children, href, postCid);
|
||||
}
|
||||
|
||||
return renderAnchorLink(children, href || '');
|
||||
return renderAnchorLink(children, href || '', postCid);
|
||||
},
|
||||
} as ExtendedComponents
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import styles from '../../views/post/post.module.css';
|
||||
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isValidURL } from '../../lib/utils/url-utils';
|
||||
import { isValidURL, QUOTE_NUMBER_REGEX } from '../../lib/utils/url-utils';
|
||||
import { isAllView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { formatUserIDForDisplay } from '../../lib/utils/string-utils';
|
||||
import useModQueueStore from '../../stores/use-mod-queue-store';
|
||||
import { useDirectories } from '../../hooks/use-directories';
|
||||
import { getBoardPath } from '../../lib/utils/route-utils';
|
||||
@@ -21,6 +22,7 @@ import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
||||
import useHide from '../../hooks/use-hide';
|
||||
import useStateString from '../../hooks/use-state-string';
|
||||
import useScrollToReply from '../../hooks/use-scroll-to-reply';
|
||||
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
||||
import CommentContent from '../comment-content';
|
||||
import CommentMedia from '../comment-media';
|
||||
import EditMenu from '../edit-menu/edit-menu';
|
||||
@@ -36,6 +38,7 @@ import { shouldShowSnow } from '../../lib/snow';
|
||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
|
||||
import useChallengesStore from '../../stores/use-challenges-store';
|
||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||
import { usePublishCommentModeration } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
@@ -72,6 +75,7 @@ const PostInfo = ({
|
||||
isPublishing,
|
||||
onApprove,
|
||||
onReject,
|
||||
quotedByMap,
|
||||
}: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, deleted, locked, pinned, parentCid, postCid, reason, removed, state, subplebbitAddress, timestamp } = post || {};
|
||||
@@ -195,13 +199,16 @@ const PostInfo = ({
|
||||
const alertThresholdSeconds = getAlertThresholdSeconds();
|
||||
const isOverThreshold = isAwaitingApproval && timeWaiting > alertThresholdSeconds;
|
||||
|
||||
const userID = address && Plebbit.getShortAddress({ address }); // should not be shortened to less than 12 characters, because users can create unlimited addresses/IDs before authenticating or passing challenges, so if the ID is short enough they can spoof it to troll users with the same ID
|
||||
const userID = address && Plebbit.getShortAddress({ address }); // shortened to 8 chars for display; users can verify the full user ID via "Copy user ID" in the post menu to guard against spoofing
|
||||
const userIDBackgroundColor = hashStringToColor(userID);
|
||||
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);
|
||||
|
||||
const handleUserAddressClick = useAuthorAddressClick();
|
||||
const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length;
|
||||
|
||||
const pseudonymityMode = useSubplebbitField(subplebbitAddress, (sub) => sub?.features?.pseudonymityMode);
|
||||
const showUserID = pseudonymityMode !== 'per-reply';
|
||||
|
||||
const { hidden } = useHide(post);
|
||||
|
||||
const { openReplyModal } = useReplyModalStore();
|
||||
@@ -265,28 +272,32 @@ const PostInfo = ({
|
||||
<img src={avatarImageUrl} alt='' />
|
||||
</span>
|
||||
) : null}
|
||||
(ID:{' '}
|
||||
{deleted ? (
|
||||
t('deleted')
|
||||
) : removed ? (
|
||||
t('removed')
|
||||
) : (
|
||||
<Tooltip
|
||||
children={
|
||||
<span
|
||||
title={t('highlight_posts')}
|
||||
className={styles.userAddress}
|
||||
onClick={() => handleUserAddressClick(userID, postCid)}
|
||||
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
|
||||
>
|
||||
{userID}
|
||||
</span>
|
||||
}
|
||||
content={`${numberOfPostsByAuthor === 1 ? t('1_post_by_this_id') : t('x_posts_by_this_id', { number: numberOfPostsByAuthor })}`}
|
||||
showTooltip={isInPostPageView || showOmittedReplies[postCid] || (postReplyCount < 6 && !pinned)}
|
||||
/>
|
||||
{showUserID && (
|
||||
<>
|
||||
(ID:{' '}
|
||||
{deleted ? (
|
||||
t('deleted')
|
||||
) : removed ? (
|
||||
t('removed')
|
||||
) : (
|
||||
<Tooltip
|
||||
children={
|
||||
<span
|
||||
title={t('highlight_posts')}
|
||||
className={styles.userAddress}
|
||||
onClick={() => handleUserAddressClick(userID, postCid)}
|
||||
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
|
||||
>
|
||||
{formatUserIDForDisplay(userID)}
|
||||
</span>
|
||||
}
|
||||
content={`${numberOfPostsByAuthor === 1 ? t('1_post_by_this_id') : t('x_posts_by_this_id', { number: numberOfPostsByAuthor })}`}
|
||||
showTooltip={isInPostPageView || showOmittedReplies[postCid] || (postReplyCount < 6 && !pinned)}
|
||||
/>
|
||||
)}
|
||||
){' '}
|
||||
</>
|
||||
)}
|
||||
){' '}
|
||||
</span>
|
||||
<span className={styles.dateTime}>
|
||||
{isInModQueueView && isOverThreshold ? (
|
||||
@@ -417,6 +428,16 @@ const PostInfo = ({
|
||||
reply?.cid &&
|
||||
!(reply?.deleted || reply?.removed) && <ReplyQuotePreview key={index} isBacklinkReply={true} backlinkReply={reply} />,
|
||||
)}
|
||||
{cid &&
|
||||
parentCid &&
|
||||
quotedByMap
|
||||
?.get(cid)
|
||||
?.map(
|
||||
(reply: Comment, index: number) =>
|
||||
reply?.parentCid !== cid &&
|
||||
reply?.cid &&
|
||||
!(reply?.deleted || reply?.removed) && <ReplyQuotePreview key={`qb-${index}`} isBacklinkReply={true} backlinkReply={reply} />,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@@ -519,7 +540,7 @@ const PostMedia = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => {
|
||||
const Reply = ({ postReplyCount, reply, roles, threadNumber, quotedByMap }: PostProps) => {
|
||||
let post = reply;
|
||||
// handle pending mod or author edit
|
||||
const { editedComment } = useEditedComment({ comment: reply });
|
||||
@@ -547,7 +568,7 @@ const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => {
|
||||
<div className={styles.replyDesktop}>
|
||||
<div className={styles.sideArrows}>{'>>'}</div>
|
||||
<div className={`${styles.reply} ${isRouteLinkToReply && styles.highlight}`} data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid}>
|
||||
<PostInfo post={post} postReplyCount={postReplyCount} roles={roles} isHidden={hidden} threadNumber={threadNumber} />
|
||||
<PostInfo post={post} postReplyCount={postReplyCount} roles={roles} isHidden={hidden} threadNumber={threadNumber} quotedByMap={quotedByMap} />
|
||||
{link && !hidden && !(deleted || removed) && isValidURL(link) && (
|
||||
<PostMedia
|
||||
commentMediaInfo={commentMediaInfo}
|
||||
@@ -598,6 +619,21 @@ const PostDesktop = ({
|
||||
const isHidden = hidden && !isInPostPageView;
|
||||
|
||||
const { replies, hasMore, loadMore } = useReplies({ comment: post, flat: true, accountComments: { newerThan: Infinity } });
|
||||
const registerComments = usePostNumberStore((s) => s.registerComments);
|
||||
const numberToCid = usePostNumberStore((s) => s.numberToCid);
|
||||
const prevCidsRef = useRef<string>('');
|
||||
useEffect(() => {
|
||||
const all = post ? [post, ...(replies || [])] : replies || [];
|
||||
if (!all.length) return;
|
||||
const cidsKey = all
|
||||
.map((c) => c?.cid)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.join(',');
|
||||
if (cidsKey === prevCidsRef.current) return;
|
||||
prevCidsRef.current = cidsKey;
|
||||
registerComments(all);
|
||||
}, [post, replies, registerComments]);
|
||||
const visiblelinksCount = useCountLinksInReplies(post, 5);
|
||||
const totalLinksCount = useCountLinksInReplies(post);
|
||||
const replyCount = replies?.length;
|
||||
@@ -614,6 +650,37 @@ const PostDesktop = ({
|
||||
// Filter out deleted replies with no children for both virtuoso and non-virtuoso rendering
|
||||
const filteredReplies = useMemo(() => (replies || []).filter((reply) => !(reply.deleted && (reply.replyCount === 0 || !reply.replyCount))), [replies]);
|
||||
|
||||
// Compute quotedByMap: map each quoted CID to array of replies that quote it
|
||||
const quotedByMap = useMemo(() => {
|
||||
const map = new Map<string, Comment[]>();
|
||||
for (const reply of filteredReplies) {
|
||||
const cidSet = new Set<string>();
|
||||
|
||||
if (reply.quotedCids?.length) {
|
||||
for (const quotedCid of reply.quotedCids) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
|
||||
if (reply.content) {
|
||||
for (const match of reply.content.matchAll(QUOTE_NUMBER_REGEX)) {
|
||||
const postNumber = parseInt(match[1], 10);
|
||||
const quotedCid = numberToCid[postNumber];
|
||||
if (quotedCid) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const quotedCid of cidSet) {
|
||||
const arr = map.get(quotedCid);
|
||||
if (arr) arr.push(reply);
|
||||
else map.set(quotedCid, [reply]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [filteredReplies, numberToCid]);
|
||||
|
||||
// Virtuoso scroll position management for infinite replies
|
||||
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
|
||||
const virtuosoStateKey = `replies-desktop-${cid}`;
|
||||
@@ -739,7 +806,7 @@ const PostDesktop = ({
|
||||
data={filteredReplies}
|
||||
itemContent={(index, reply) => (
|
||||
<div className={styles.replyContainer}>
|
||||
<Reply reply={reply} roles={roles} postReplyCount={replyCount} threadNumber={post?.number} />
|
||||
<Reply reply={reply} roles={roles} postReplyCount={replyCount} threadNumber={post?.number} quotedByMap={quotedByMap} />
|
||||
</div>
|
||||
)}
|
||||
useWindowScroll={true}
|
||||
@@ -758,7 +825,7 @@ const PostDesktop = ({
|
||||
!hasMore &&
|
||||
filteredReplies.map((reply, index) => (
|
||||
<div key={index} className={styles.replyContainer}>
|
||||
<Reply reply={reply} roles={roles} postReplyCount={replyCount} threadNumber={post?.number} />
|
||||
<Reply reply={reply} roles={roles} postReplyCount={replyCount} threadNumber={post?.number} quotedByMap={quotedByMap} />
|
||||
</div>
|
||||
))}
|
||||
{/* Non-virtualized rendering for board view (last 5 replies or show omitted) */}
|
||||
@@ -770,7 +837,7 @@ const PostDesktop = ({
|
||||
showReplies &&
|
||||
(showOmittedReplies[cid] ? filteredReplies : filteredReplies.slice(-5)).map((reply, index) => (
|
||||
<div key={index} className={styles.replyContainer}>
|
||||
<Reply reply={reply} roles={roles} postReplyCount={replyCount} threadNumber={post?.number} />
|
||||
<Reply reply={reply} roles={roles} postReplyCount={replyCount} threadNumber={post?.number} quotedByMap={quotedByMap} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@ const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: CopyLinkB
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={styles.postMenuItem}>{t('copy_link')}</div>
|
||||
<div className={styles.postMenuItem}>{t('copy_direct_link')}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -63,6 +63,25 @@ const CopyContentIdButton = ({ cid, onClose }: { cid: string; onClose: () => voi
|
||||
);
|
||||
};
|
||||
|
||||
const CopyUserIdButton = ({ address, onClose }: { address: string; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
onClick={async () => {
|
||||
try {
|
||||
await copyToClipboard(address);
|
||||
} catch (error) {
|
||||
console.error('Failed to copy user id', error);
|
||||
} finally {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={styles.postMenuItem}>{t('copy_user_id')}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [isImageSearchMenuOpen, setIsImageSearchMenuOpen] = useState(false);
|
||||
@@ -178,6 +197,7 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
||||
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
||||
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
|
||||
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
|
||||
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
|
||||
{!(isInPostPageView && postCid === cid) && (
|
||||
<div
|
||||
className={styles.postMenuItem}
|
||||
|
||||
@@ -47,7 +47,7 @@ const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: CopyLinkB
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={styles.postMenuItem}>{t('copy_link')}</div>
|
||||
<div className={styles.postMenuItem}>{t('copy_direct_link')}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -71,6 +71,25 @@ const CopyContentIdButton = ({ cid, onClose }: { cid: string; onClose: () => voi
|
||||
);
|
||||
};
|
||||
|
||||
const CopyUserIdButton = ({ address, onClose }: { address: string; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
onClick={async () => {
|
||||
try {
|
||||
await copyToClipboard(address);
|
||||
} catch (error) {
|
||||
console.error('Failed to copy user id', error);
|
||||
} finally {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={styles.postMenuItem}>{t('copy_user_id')}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
@@ -180,6 +199,7 @@ const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
|
||||
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
||||
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
|
||||
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
|
||||
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
|
||||
{cid && subplebbitAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
|
||||
{cid && subplebbitAddress && authorAddress && <BlockUserButton address={authorAddress} />}
|
||||
{cid && subplebbitAddress && !isInBoardView && <BlockBoardButton address={subplebbitAddress} />}
|
||||
|
||||
@@ -9,7 +9,9 @@ import { shouldShowSnow } from '../../lib/snow';
|
||||
import { getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { QUOTE_NUMBER_REGEX } from '../../lib/utils/url-utils';
|
||||
import { isAllView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { formatUserIDForDisplay } from '../../lib/utils/string-utils';
|
||||
import useModQueueStore from '../../stores/use-mod-queue-store';
|
||||
import { useDirectories } from '../../hooks/use-directories';
|
||||
import { getBoardPath } from '../../lib/utils/route-utils';
|
||||
@@ -20,6 +22,7 @@ import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||
import useHide from '../../hooks/use-hide';
|
||||
import useStateString from '../../hooks/use-state-string';
|
||||
import useScrollToReply from '../../hooks/use-scroll-to-reply';
|
||||
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
||||
import CommentContent from '../comment-content';
|
||||
import CommentMedia from '../comment-media';
|
||||
import LoadingEllipsis from '../loading-ellipsis';
|
||||
@@ -31,6 +34,7 @@ import _ from 'lodash';
|
||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
|
||||
import useChallengesStore from '../../stores/use-challenges-store';
|
||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||
|
||||
const { addChallenge } = useChallengesStore.getState();
|
||||
@@ -169,7 +173,10 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
||||
const handleUserAddressClick = useAuthorAddressClick();
|
||||
const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length;
|
||||
|
||||
const userID = address && Plebbit.getShortAddress({ address }); // should not be shortened to less than 12 characters, because users can create unlimited addresses/IDs before authenticating or passing challenges, so if the ID is short enough they can spoof it to troll users with the same ID
|
||||
const pseudonymityMode = useSubplebbitField(subplebbitAddress, (sub) => sub?.features?.pseudonymityMode);
|
||||
const showUserID = pseudonymityMode !== 'per-reply';
|
||||
|
||||
const userID = address && Plebbit.getShortAddress({ address }); // shortened to 8 chars for display; users can verify the full user ID via "Copy user ID" in the post menu to guard against spoofing
|
||||
const userIDBackgroundColor = hashStringToColor(userID);
|
||||
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);
|
||||
|
||||
@@ -231,28 +238,32 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
||||
<img src={avatarImageUrl} alt='' />
|
||||
</span>
|
||||
) : null}
|
||||
(ID: {''}
|
||||
{removed ? (
|
||||
_.lowerCase(t('removed'))
|
||||
) : deleted ? (
|
||||
_.lowerCase(t('deleted'))
|
||||
) : (
|
||||
<Tooltip
|
||||
children={
|
||||
<span
|
||||
title={t('highlight_posts')}
|
||||
className={styles.userAddress}
|
||||
onClick={() => handleUserAddressClick(userID, postCid)}
|
||||
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
|
||||
>
|
||||
{userID}
|
||||
</span>
|
||||
}
|
||||
content={`${numberOfPostsByAuthor === 1 ? t('1_post_by_this_id') : t('x_posts_by_this_id', { number: numberOfPostsByAuthor })}`}
|
||||
showTooltip={isInPostPageView || postReplyCount < 6}
|
||||
/>
|
||||
{showUserID && (
|
||||
<>
|
||||
(ID: {''}
|
||||
{removed ? (
|
||||
_.lowerCase(t('removed'))
|
||||
) : deleted ? (
|
||||
_.lowerCase(t('deleted'))
|
||||
) : (
|
||||
<Tooltip
|
||||
children={
|
||||
<span
|
||||
title={t('highlight_posts')}
|
||||
className={styles.userAddress}
|
||||
onClick={() => handleUserAddressClick(userID, postCid)}
|
||||
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
|
||||
>
|
||||
{formatUserIDForDisplay(userID)}
|
||||
</span>
|
||||
}
|
||||
content={`${numberOfPostsByAuthor === 1 ? t('1_post_by_this_id') : t('x_posts_by_this_id', { number: numberOfPostsByAuthor })}`}
|
||||
showTooltip={isInPostPageView || postReplyCount < 6}
|
||||
/>
|
||||
)}
|
||||
){' '}
|
||||
</>
|
||||
)}
|
||||
){' '}
|
||||
{pinned && (
|
||||
<span className={styles.stickyIconWrapper}>
|
||||
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
||||
@@ -369,14 +380,14 @@ const PostMediaContent = ({ post, link }: { post: any; link: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ReplyBacklinks = ({ post }: PostProps) => {
|
||||
const ReplyBacklinks = ({ post, quotedByMap }: PostProps) => {
|
||||
const { cid, parentCid } = post || {};
|
||||
const { replies } = useReplies({ comment: post, flat: true, accountComments: { newerThan: Infinity } });
|
||||
|
||||
return (
|
||||
cid &&
|
||||
parentCid &&
|
||||
replies.length > 0 && (
|
||||
(replies.length > 0 || quotedByMap?.get(cid)?.length) && (
|
||||
<div className={styles.mobileReplyBacklinks}>
|
||||
{replies.map(
|
||||
(reply: Comment, index: number) =>
|
||||
@@ -384,12 +395,20 @@ const ReplyBacklinks = ({ post }: PostProps) => {
|
||||
reply?.cid &&
|
||||
!(reply?.deleted || reply?.removed) && <ReplyQuotePreview key={index} isBacklinkReply={true} backlinkReply={reply} />,
|
||||
)}
|
||||
{quotedByMap
|
||||
?.get(cid)
|
||||
?.map(
|
||||
(reply: Comment, index: number) =>
|
||||
reply?.parentCid !== cid &&
|
||||
reply?.cid &&
|
||||
!(reply?.deleted || reply?.removed) && <ReplyQuotePreview key={`qb-${index}`} isBacklinkReply={true} backlinkReply={reply} />,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => {
|
||||
const Reply = ({ postReplyCount, reply, roles, threadNumber, quotedByMap }: PostProps) => {
|
||||
let post = reply;
|
||||
// handle pending mod or author edit
|
||||
const { editedComment } = useEditedComment({ comment: reply });
|
||||
@@ -415,7 +434,7 @@ const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => {
|
||||
>
|
||||
<PostInfoAndMedia post={post} postReplyCount={postReplyCount} roles={roles} threadNumber={threadNumber} />
|
||||
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <CommentContent comment={post} />}
|
||||
<ReplyBacklinks post={reply} />
|
||||
<ReplyBacklinks post={reply} quotedByMap={quotedByMap} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -446,6 +465,21 @@ const PostMobile = ({
|
||||
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, directories) : undefined;
|
||||
const linksCount = useCountLinksInReplies(post);
|
||||
const { replies, hasMore, loadMore } = useReplies({ comment: post, accountComments: { newerThan: Infinity } });
|
||||
const registerComments = usePostNumberStore((s) => s.registerComments);
|
||||
const numberToCid = usePostNumberStore((s) => s.numberToCid);
|
||||
const prevCidsRef = useRef<string>('');
|
||||
useEffect(() => {
|
||||
const all = post ? [post, ...(replies || [])] : replies || [];
|
||||
if (!all.length) return;
|
||||
const cidsKey = all
|
||||
.map((c) => c?.cid)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.join(',');
|
||||
if (cidsKey === prevCidsRef.current) return;
|
||||
prevCidsRef.current = cidsKey;
|
||||
registerComments(all);
|
||||
}, [post, replies, registerComments]);
|
||||
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
const { hidden, unhide } = useHide({ cid });
|
||||
@@ -455,6 +489,37 @@ const PostMobile = ({
|
||||
// Filter out deleted replies with no children for both virtuoso and non-virtuoso rendering
|
||||
const filteredReplies = useMemo(() => (replies || []).filter((reply) => !(reply.deleted && (reply.replyCount === 0 || !reply.replyCount))), [replies]);
|
||||
|
||||
// Compute quotedByMap: map each quoted CID to array of replies that quote it
|
||||
const quotedByMap = useMemo(() => {
|
||||
const map = new Map<string, Comment[]>();
|
||||
for (const reply of filteredReplies) {
|
||||
const cidSet = new Set<string>();
|
||||
|
||||
if (reply.quotedCids?.length) {
|
||||
for (const quotedCid of reply.quotedCids) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
|
||||
if (reply.content) {
|
||||
for (const match of reply.content.matchAll(QUOTE_NUMBER_REGEX)) {
|
||||
const postNumber = parseInt(match[1], 10);
|
||||
const quotedCid = numberToCid[postNumber];
|
||||
if (quotedCid) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const quotedCid of cidSet) {
|
||||
const arr = map.get(quotedCid);
|
||||
if (arr) arr.push(reply);
|
||||
else map.set(quotedCid, [reply]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [filteredReplies, numberToCid]);
|
||||
|
||||
// Virtuoso scroll position management for infinite replies
|
||||
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
|
||||
const virtuosoStateKey = `replies-mobile-${cid}`;
|
||||
@@ -570,7 +635,7 @@ const PostMobile = ({
|
||||
data={filteredReplies}
|
||||
itemContent={(index, reply) => (
|
||||
<div className={styles.replyContainer}>
|
||||
<Reply postReplyCount={replyCount} reply={reply} roles={roles} threadNumber={post?.number} />
|
||||
<Reply postReplyCount={replyCount} reply={reply} roles={roles} threadNumber={post?.number} quotedByMap={quotedByMap} />
|
||||
</div>
|
||||
)}
|
||||
useWindowScroll={true}
|
||||
@@ -589,7 +654,7 @@ const PostMobile = ({
|
||||
!hasMore &&
|
||||
filteredReplies.map((reply, index) => (
|
||||
<div key={index} className={styles.replyContainer}>
|
||||
<Reply postReplyCount={replyCount} reply={reply} roles={roles} threadNumber={post?.number} />
|
||||
<Reply postReplyCount={replyCount} reply={reply} roles={roles} threadNumber={post?.number} quotedByMap={quotedByMap} />
|
||||
</div>
|
||||
))}
|
||||
{/* Non-virtualized rendering for board view (last 5 replies) */}
|
||||
@@ -600,7 +665,7 @@ const PostMobile = ({
|
||||
showReplies &&
|
||||
filteredReplies.slice(-5).map((reply, index) => (
|
||||
<div key={index} className={styles.replyContainer}>
|
||||
<Reply postReplyCount={replyCount} reply={reply} roles={roles} threadNumber={post?.number} />
|
||||
<Reply postReplyCount={replyCount} reply={reply} roles={roles} threadNumber={post?.number} quotedByMap={quotedByMap} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isValidURL } from '../../lib/utils/url-utils';
|
||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import useSelectedTextStore from '../../stores/use-selected-text-store';
|
||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||
import usePublishReply from '../../hooks/use-publish-reply';
|
||||
import useIsMobile from '../../hooks/use-is-mobile';
|
||||
import styles from './reply-modal.module.css';
|
||||
@@ -43,7 +44,13 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
const [url, setUrl] = useState('');
|
||||
const textRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const urlRef = useRef<HTMLInputElement>(null);
|
||||
const lastSelectionStartRef = useRef(0);
|
||||
const lastSelectionEndRef = useRef(0);
|
||||
const lastProcessedQuoteInsertRequestIdRef = useRef(0);
|
||||
const { selectedText } = useSelectedTextStore();
|
||||
const quoteInsertRequestId = useReplyModalStore((state) => state.quoteInsertRequestId);
|
||||
const quoteInsertNumber = useReplyModalStore((state) => state.quoteInsertNumber);
|
||||
const quoteInsertSelectedText = useReplyModalStore((state) => state.quoteInsertSelectedText);
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [lengthError, setLengthError] = useState<string | null>(null);
|
||||
@@ -186,6 +193,9 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
if (showReplyModal && textRef.current) {
|
||||
textRef.current.spellcheck = false;
|
||||
textRef.current.value = contentPrefix + (selectedText || '');
|
||||
const len = textRef.current.value.length;
|
||||
lastSelectionStartRef.current = len;
|
||||
lastSelectionEndRef.current = len;
|
||||
|
||||
setTimeout(() => {
|
||||
if (textRef.current) {
|
||||
@@ -200,6 +210,8 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
if (!value.startsWith(contentPrefix)) {
|
||||
e.target.value = contentPrefix + value.slice(contentPrefix.length);
|
||||
}
|
||||
lastSelectionStartRef.current = e.target.selectionStart ?? e.target.value.length;
|
||||
lastSelectionEndRef.current = e.target.selectionEnd ?? lastSelectionStartRef.current;
|
||||
};
|
||||
|
||||
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
@@ -211,6 +223,52 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const canInsertQuote = showReplyModal && quoteInsertRequestId !== 0 && !!textRef.current;
|
||||
|
||||
const textarea = textRef.current;
|
||||
if (!canInsertQuote || !textarea) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Guard: skip if we already processed this exact request id.
|
||||
// setPublishReplyOptions identity changes after each call (store update -> new content -> new useCallback),
|
||||
// which re-triggers this effect. Without this guard, that creates an infinite update loop.
|
||||
if (quoteInsertRequestId === lastProcessedQuoteInsertRequestIdRef.current) {
|
||||
return;
|
||||
}
|
||||
lastProcessedQuoteInsertRequestIdRef.current = quoteInsertRequestId;
|
||||
|
||||
const quote = `>>${quoteInsertNumber ?? '?'}`;
|
||||
const selectedQuote = quoteInsertSelectedText?.trimEnd() || '';
|
||||
const isFocused = document.activeElement === textarea;
|
||||
const rawStart = isFocused ? (textarea.selectionStart ?? textarea.value.length) : lastSelectionStartRef.current;
|
||||
const selectionEnd = isFocused ? (textarea.selectionEnd ?? rawStart) : lastSelectionEndRef.current;
|
||||
const minStart = contentPrefix.length;
|
||||
const start = Math.max(rawStart, minStart);
|
||||
const end = Math.max(selectionEnd, minStart);
|
||||
const before = textarea.value.slice(0, start);
|
||||
const after = textarea.value.slice(end);
|
||||
const needsLeadingNewline = before.length > minStart && !before.endsWith('\n');
|
||||
let insertion = `${needsLeadingNewline ? '\n' : ''}${quote}\n`;
|
||||
if (selectedQuote) {
|
||||
insertion += `${selectedQuote}\n`;
|
||||
}
|
||||
const nextValue = `${before}${insertion}${after}`;
|
||||
|
||||
textarea.value = nextValue;
|
||||
const nextCursor = before.length + insertion.length;
|
||||
textarea.focus();
|
||||
textarea.setSelectionRange(nextCursor, nextCursor);
|
||||
lastSelectionStartRef.current = nextCursor;
|
||||
lastSelectionEndRef.current = nextCursor;
|
||||
|
||||
const contentWithoutPrefix = nextValue.slice(contentPrefix.length);
|
||||
const formattedContent = formatMarkdown(contentWithoutPrefix);
|
||||
setPublishReplyOptions({ content: formattedContent });
|
||||
checkContentLength(formattedContent, t);
|
||||
}, [showReplyModal, quoteInsertRequestId, quoteInsertNumber, quoteInsertSelectedText, contentPrefix, setPublishReplyOptions, checkContentLength, t]);
|
||||
|
||||
// on android, auto upload file to image hosting sites with open api
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [uploadedFileName, setUploadedFileName] = useState<string | null>(null);
|
||||
@@ -294,7 +352,23 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<textarea cols={48} rows={4} wrap='soft' ref={textRef} spellCheck={true} onInput={handleContentInput} onChange={handleContentChange} />
|
||||
<textarea
|
||||
cols={48}
|
||||
rows={4}
|
||||
wrap='soft'
|
||||
ref={textRef}
|
||||
spellCheck={true}
|
||||
onInput={handleContentInput}
|
||||
onChange={handleContentChange}
|
||||
onSelect={(e) => {
|
||||
lastSelectionStartRef.current = e.currentTarget.selectionStart ?? e.currentTarget.value.length;
|
||||
lastSelectionEndRef.current = e.currentTarget.selectionEnd ?? lastSelectionStartRef.current;
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
lastSelectionStartRef.current = e.currentTarget.selectionStart ?? e.currentTarget.value.length;
|
||||
lastSelectionEndRef.current = e.currentTarget.selectionEnd ?? lastSelectionStartRef.current;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
{url && !isAndroid && (
|
||||
|
||||
@@ -14,10 +14,12 @@ interface ReplyQuotePreviewProps {
|
||||
backlinkReply?: Comment;
|
||||
isQuotelinkReply?: boolean;
|
||||
quotelinkReply?: Comment;
|
||||
isOP?: boolean;
|
||||
}
|
||||
|
||||
const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
||||
const targetElements = document.querySelectorAll(`[data-cid="${cid}"]`);
|
||||
const isOpElement = (element: HTMLElement) => element.getAttribute('data-post-cid') === cid;
|
||||
|
||||
const isInViewport = (element: HTMLElement) => {
|
||||
const bounding = element.getBoundingClientRect();
|
||||
@@ -34,6 +36,13 @@ const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
||||
targetElements.forEach((element) => {
|
||||
const htmlElement = element as HTMLElement;
|
||||
if (isInViewport(htmlElement)) {
|
||||
// Never apply quote-hover highlight styles to OP cards.
|
||||
if (isOpElement(htmlElement)) {
|
||||
htmlElement.classList.remove('highlight', 'double-highlight');
|
||||
anyInView = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const hasHighlight = Array.from(htmlElement.classList).some((className) => className.includes('highlight') && !className.includes('double-highlight'));
|
||||
if (hasHighlight) {
|
||||
htmlElement.classList.remove('highlight');
|
||||
@@ -54,7 +63,7 @@ const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
||||
}
|
||||
};
|
||||
|
||||
const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply }: ReplyQuotePreviewProps) => {
|
||||
const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply, isOP }: ReplyQuotePreviewProps) => {
|
||||
const [hoveredCid, setHoveredCid] = useState<string | null>(null);
|
||||
const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null);
|
||||
const placementRef = useRef<Placement>('right');
|
||||
@@ -104,10 +113,6 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
|
||||
if (cid && subplebbitAddress) {
|
||||
const boardPath = getBoardPath(subplebbitAddress, directories);
|
||||
navigate(`/${boardPath}/thread/${cid}`);
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector(`[data-cid="${cid}"]`);
|
||||
element?.scrollIntoView();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -173,6 +178,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
|
||||
onClick={(e) => handleClick(e, quotelinkReply?.cid, quotelinkReply?.subplebbitAddress)}
|
||||
>
|
||||
{`>>${quotelinkReply?.number ?? '?'}`}
|
||||
{isOP && ' (OP)'}
|
||||
{quotelinkReply?.author?.address === account?.author?.address && ' (You)'}
|
||||
</Link>
|
||||
<br />
|
||||
@@ -190,7 +196,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
|
||||
return isBacklinkReply ? replyBacklink : isQuotelinkReply && replyQuotelink;
|
||||
};
|
||||
|
||||
const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply }: ReplyQuotePreviewProps) => {
|
||||
const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply, isOP }: ReplyQuotePreviewProps) => {
|
||||
const [hoveredCid, setHoveredCid] = useState<string | null>(null);
|
||||
const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null);
|
||||
const directories = useDirectories();
|
||||
@@ -217,10 +223,6 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
|
||||
if (cid && subplebbitAddress) {
|
||||
const boardPath = getBoardPath(subplebbitAddress, directories);
|
||||
navigate(`/${boardPath}/thread/${cid}`);
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector(`[data-cid="${cid}"]`);
|
||||
element?.scrollIntoView();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -286,6 +288,7 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
|
||||
onMouseLeave={() => handleMouseLeave(quotelinkReply?.cid)}
|
||||
>
|
||||
{`>>${quotelinkReply?.number ?? '?'}`}
|
||||
{isOP && ' (OP)'}
|
||||
{quotelinkReply?.author?.address === account?.author?.address && ' (You)'}
|
||||
</span>
|
||||
{quotelinkReply?.number &&
|
||||
@@ -317,13 +320,19 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
|
||||
return isBacklinkReply ? replyBacklink : isQuotelinkReply && replyQuotelink;
|
||||
};
|
||||
|
||||
const ReplyQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply }: ReplyQuotePreviewProps) => {
|
||||
const ReplyQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply, isOP }: ReplyQuotePreviewProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return isMobile ? (
|
||||
<MobileQuotePreview backlinkReply={backlinkReply} quotelinkReply={quotelinkReply} isBacklinkReply={isBacklinkReply} isQuotelinkReply={isQuotelinkReply} />
|
||||
<MobileQuotePreview backlinkReply={backlinkReply} quotelinkReply={quotelinkReply} isBacklinkReply={isBacklinkReply} isQuotelinkReply={isQuotelinkReply} isOP={isOP} />
|
||||
) : (
|
||||
<DesktopQuotePreview backlinkReply={backlinkReply} quotelinkReply={quotelinkReply} isBacklinkReply={isBacklinkReply} isQuotelinkReply={isQuotelinkReply} />
|
||||
<DesktopQuotePreview
|
||||
backlinkReply={backlinkReply}
|
||||
quotelinkReply={quotelinkReply}
|
||||
isBacklinkReply={isBacklinkReply}
|
||||
isQuotelinkReply={isQuotelinkReply}
|
||||
isOP={isOP}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ import { useLocation, useParams } from 'react-router-dom';
|
||||
import useThemeStore from '../stores/use-theme-store';
|
||||
import { useDirectories } from './use-directories';
|
||||
import { isAllView, isHomeView, isNotFoundView, isPendingPostView, isSubscriptionsView, isModView } from '../lib/utils/view-utils';
|
||||
import { getSubplebbitAddress } from '../lib/utils/route-utils';
|
||||
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
const useInitialTheme = (pendingPostSubplebbitAddress?: string) => {
|
||||
const location = useLocation();
|
||||
const { communityAddress: paramsSubplebbitAddress, accountCommentIndex } = useParams<{ communityAddress: string; accountCommentIndex?: string }>();
|
||||
const { boardIdentifier, accountCommentIndex } = useParams<{ boardIdentifier: string; accountCommentIndex?: string }>();
|
||||
const commentIndex = accountCommentIndex ? parseInt(accountCommentIndex) : undefined;
|
||||
const pendingPost = useAccountComment({ commentIndex });
|
||||
// Subscribe to the actual themes data, not just functions
|
||||
@@ -21,13 +22,15 @@ const useInitialTheme = (pendingPostSubplebbitAddress?: string) => {
|
||||
const isInModView = isModView(location.pathname);
|
||||
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
||||
|
||||
const paramsSubplebbitAddress = boardIdentifier ? getSubplebbitAddress(boardIdentifier, directories) : undefined;
|
||||
|
||||
const initialTheme = useMemo(() => {
|
||||
let theme = 'yotsuba';
|
||||
|
||||
if (isInPendingPostView) {
|
||||
const communityAddress = pendingPostSubplebbitAddress || pendingPost?.communityAddress;
|
||||
if (communityAddress) {
|
||||
const community = directories.find((s) => s.address === communityAddress);
|
||||
const subplebbitAddress = pendingPostSubplebbitAddress || pendingPost?.subplebbitAddress;
|
||||
if (subplebbitAddress) {
|
||||
const community = directories.find((s) => s.address === subplebbitAddress);
|
||||
if (community?.nsfw) {
|
||||
theme = themes.nsfw || 'yotsuba';
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
isNotFoundView,
|
||||
ParamsType,
|
||||
} from './view-utils';
|
||||
import { formatUserIDForDisplay } from './string-utils';
|
||||
|
||||
export {
|
||||
isAllView,
|
||||
@@ -25,5 +26,6 @@ export {
|
||||
isSettingsView,
|
||||
isSubscriptionsView,
|
||||
isNotFoundView,
|
||||
formatUserIDForDisplay,
|
||||
};
|
||||
export type { ParamsType };
|
||||
|
||||
@@ -106,7 +106,7 @@ export const isFeedRoute = (pathname: string): boolean => {
|
||||
|
||||
if (normalizedPath.includes('/thread/')) return false;
|
||||
if (normalizedPath.startsWith('/pending/')) return false;
|
||||
if (normalizedPath.includes('/queue')) return false;
|
||||
if (normalizedPath.includes('/modqueue')) return false;
|
||||
|
||||
const pathWithoutSettings = normalizedPath.replace(/\/settings$/, '');
|
||||
|
||||
@@ -140,7 +140,7 @@ export const isPendingPostRoute = (pathname: string): boolean => {
|
||||
|
||||
export const isModQueueRoute = (pathname: string): boolean => {
|
||||
const normalizedPath = pathname.replace(/\/settings$/, '');
|
||||
return normalizedPath.includes('/queue');
|
||||
return normalizedPath.includes('/modqueue');
|
||||
};
|
||||
|
||||
export const getFeedCacheKey = (pathname: string): string | null => {
|
||||
@@ -156,7 +156,7 @@ export const getFeedCacheKey = (pathname: string): string | null => {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (normalizedPath.includes('/queue')) {
|
||||
if (normalizedPath.includes('/modqueue')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Format a user ID for display.
|
||||
* - If ID contains a dot (domain), show full ID but truncate with "..." if exceeds maxLength
|
||||
* - Otherwise, shorten to 8 characters
|
||||
* @param userID - The user ID to format
|
||||
* @param maxDomainLength - Maximum length for domain IDs before truncating with "..."
|
||||
* @returns Formatted user ID
|
||||
*/
|
||||
export function formatUserIDForDisplay(userID: string | undefined, maxDomainLength: number = 40): string {
|
||||
if (!userID) return '';
|
||||
|
||||
// If ID contains a dot, it's a domain - show fully but truncate if too long
|
||||
if (userID.includes('.')) {
|
||||
if (userID.length > maxDomainLength) {
|
||||
return `${userID.slice(0, maxDomainLength - 3)}...`;
|
||||
}
|
||||
return userID;
|
||||
}
|
||||
|
||||
// Otherwise, shorten to 8 characters
|
||||
return userID.slice(0, 8);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { copyToClipboard } from './clipboard-utils';
|
||||
|
||||
export const QUOTE_NUMBER_REGEX = /(?<![>/\w])>>(\d+)/g;
|
||||
|
||||
export const getHostname = (url: string) => {
|
||||
try {
|
||||
return new URL(url).hostname.replace(/^www\./, '');
|
||||
@@ -180,14 +182,21 @@ export const isValidCrossboardPattern = (pattern: string): boolean => {
|
||||
return isValidDomain(pathPart) || isValidIPNSKey(pathPart);
|
||||
};
|
||||
|
||||
// Transform >>{number} post number patterns to markdown links with special anchor
|
||||
const preprocessPostNumberPatterns = (content: string): string => {
|
||||
// Match >> followed by digits, avoid overlap with greentext (>>>), cross-board (>>>/), URLs, CID-like patterns
|
||||
return content.replace(QUOTE_NUMBER_REGEX, (_, num) => `[>>${num}](#q-${num})`);
|
||||
};
|
||||
|
||||
// Preprocess content to convert plain text 5chan cross-board patterns to markdown links
|
||||
export const preprocess5chanPatterns = (content: string): string => {
|
||||
const withPostNumbers = preprocessPostNumberPatterns(content);
|
||||
// Pattern to match ">>>/something" or ">>>/something/cid"
|
||||
// Negative lookbehind prevents matching patterns that are already part of URLs
|
||||
// Matches: >>>/directory/, >>>/directory/cid (46 chars), >>>/address, >>>/address/cid (46 chars)
|
||||
const pattern = /(?<!https?:\/\/[^\s]*)>>>\/([a-zA-Z0-9]{1,10}\/(?:[a-zA-Z0-9]{46})?|[a-zA-Z0-9\-.]+(?:\/[a-zA-Z0-9]{46})?)[.,:;!?]*/g;
|
||||
|
||||
return content.replace(pattern, (match, capturedPath) => {
|
||||
return withPostNumbers.replace(pattern, (match, capturedPath) => {
|
||||
// Remove any trailing punctuation from the captured path
|
||||
const cleanPath = capturedPath.replace(/[.,:;!?]+$/, '');
|
||||
const fullPattern = `>>>/${cleanPath}`;
|
||||
|
||||
@@ -60,7 +60,7 @@ export const isModView = (pathname: string): boolean => {
|
||||
};
|
||||
|
||||
export const isModQueueView = (pathname: string): boolean => {
|
||||
return pathname.includes('/queue');
|
||||
return pathname.includes('/modqueue');
|
||||
};
|
||||
|
||||
export const isPendingPostView = (pathname: string, params: ParamsType): boolean => {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { create } from 'zustand';
|
||||
import type { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
interface PostNumberState {
|
||||
numberToCid: Record<number, string>;
|
||||
cidToNumber: Record<string, number>;
|
||||
registerComments: (comments: Comment[]) => void;
|
||||
}
|
||||
|
||||
const usePostNumberStore = create<PostNumberState>((set) => ({
|
||||
numberToCid: {},
|
||||
cidToNumber: {},
|
||||
registerComments: (comments: Comment[]) => {
|
||||
if (!comments?.length) return;
|
||||
|
||||
set((state) => {
|
||||
const nextNumberToCid = { ...state.numberToCid };
|
||||
const nextCidToNumber = { ...state.cidToNumber };
|
||||
|
||||
for (const c of comments) {
|
||||
const num = c?.number;
|
||||
const cid = c?.cid;
|
||||
if (typeof num === 'number' && cid) {
|
||||
nextNumberToCid[num] = cid;
|
||||
nextCidToNumber[cid] = num;
|
||||
}
|
||||
}
|
||||
|
||||
return { numberToCid: nextNumberToCid, cidToNumber: nextCidToNumber };
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
export default usePostNumberStore;
|
||||
@@ -9,10 +9,28 @@ interface ReplyModalState {
|
||||
threadCid: string | null;
|
||||
subplebbitAddress: string | null;
|
||||
scrollY: number;
|
||||
quoteInsertRequestId: number;
|
||||
quoteInsertNumber: number | null;
|
||||
quoteInsertSelectedText: string | null;
|
||||
closeModal: () => void;
|
||||
openReplyModal: (parentCid: string, parentNumber: number | undefined, postCid: string, threadNumber: number | undefined, subplebbitAddress: string) => void;
|
||||
}
|
||||
|
||||
const getQuotedSelection = () => {
|
||||
const text = document.getSelection()?.toString();
|
||||
if (!text) return '';
|
||||
|
||||
// Keep each selected line as 5chan greentext and normalize newlines.
|
||||
const normalizedText = text.replace(/\r\n/g, '\n').replace(/\n+$/g, '');
|
||||
|
||||
if (!normalizedText) return '';
|
||||
|
||||
return normalizedText
|
||||
.split('\n')
|
||||
.map((line) => `>${line}`)
|
||||
.join('\n');
|
||||
};
|
||||
|
||||
const useReplyModalStore = create<ReplyModalState>((set, get) => ({
|
||||
showReplyModal: false,
|
||||
activeCid: null,
|
||||
@@ -21,6 +39,9 @@ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
|
||||
threadCid: null,
|
||||
subplebbitAddress: null,
|
||||
scrollY: 0,
|
||||
quoteInsertRequestId: 0,
|
||||
quoteInsertNumber: null,
|
||||
quoteInsertSelectedText: null,
|
||||
|
||||
closeModal: () => {
|
||||
// Reset selected text if you're using that store
|
||||
@@ -30,20 +51,26 @@ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
|
||||
activeCid: null,
|
||||
parentNumber: null,
|
||||
threadNumber: null,
|
||||
quoteInsertNumber: null,
|
||||
quoteInsertSelectedText: null,
|
||||
});
|
||||
},
|
||||
|
||||
openReplyModal: (parentCid, parentNumber, postCid, threadNumber, subplebbitAddress) => {
|
||||
// Don't update if already open with different parent
|
||||
if (get().activeCid && get().activeCid !== parentCid) {
|
||||
window.alert('Multiple quotes are not possible on 5chan for the time being, because of a protocol limitation. Please reply to one post at a time.');
|
||||
const quotedSelection = getQuotedSelection();
|
||||
|
||||
// If the reply modal is already open, insert this quote in the current textarea at caret.
|
||||
if (get().showReplyModal) {
|
||||
set((state) => ({
|
||||
quoteInsertRequestId: state.quoteInsertRequestId + 1,
|
||||
quoteInsertNumber: parentNumber ?? null,
|
||||
quoteInsertSelectedText: quotedSelection || null,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get selected text
|
||||
const text = document.getSelection()?.toString();
|
||||
if (text) {
|
||||
useSelectedTextStore.getState().setSelectedText(`>${text}\n`);
|
||||
if (quotedSelection) {
|
||||
useSelectedTextStore.getState().setSelectedText(`${quotedSelection}\n`);
|
||||
}
|
||||
|
||||
// Handle mobile scrollY
|
||||
|
||||
@@ -547,7 +547,7 @@ const ModQueueButtonContent = ({ feed, alertThresholdSeconds, boardIdentifier, i
|
||||
}, [statusMap]);
|
||||
|
||||
const totalCount = normalCount + urgentCount;
|
||||
const to = boardIdentifier ? `/${boardIdentifier}/queue` : '/mod/queue';
|
||||
const to = boardIdentifier ? `/${boardIdentifier}/modqueue` : '/mod/modqueue';
|
||||
|
||||
const buttonContent = (
|
||||
<button className='button'>
|
||||
@@ -593,7 +593,7 @@ export const ModQueueButton = ({ boardIdentifier, isMobile }: ModQueueButtonProp
|
||||
(state) => {
|
||||
const activeAccountId = state.activeAccountId;
|
||||
const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined;
|
||||
const accountSubplebbits = activeAccount?.communities || {};
|
||||
const accountSubplebbits = activeAccount?.subplebbits || {};
|
||||
return Object.keys(accountSubplebbits);
|
||||
},
|
||||
(prev, next) => {
|
||||
@@ -651,7 +651,7 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV
|
||||
(state) => {
|
||||
const activeAccountId = state.activeAccountId;
|
||||
const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined;
|
||||
const accountSubplebbits = activeAccount?.communities || {};
|
||||
const accountSubplebbits = activeAccount?.subplebbits || {};
|
||||
return Object.keys(accountSubplebbits);
|
||||
},
|
||||
(prev, next) => {
|
||||
@@ -678,7 +678,7 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV
|
||||
return [resolvedAddress];
|
||||
}
|
||||
|
||||
// Always require a board filter when viewing /mod/queue (no boardIdentifier)
|
||||
// Always require a board filter when viewing /mod/modqueue (no boardIdentifier)
|
||||
if (selectedBoardFilter) {
|
||||
return [selectedBoardFilter];
|
||||
}
|
||||
@@ -708,7 +708,7 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV
|
||||
setResetFunction(reset);
|
||||
}, [reset, setResetFunction]);
|
||||
|
||||
// Auto-select first board if viewing /mod/queue without a boardIdentifier and no filter is set
|
||||
// Auto-select first board if viewing /mod/modqueue without a boardIdentifier and no filter is set
|
||||
useEffect(() => {
|
||||
if (!resolvedAddress && !selectedBoardFilter && accountSubplebbitAddresses.length > 0) {
|
||||
const { setSelectedBoardFilter } = useModQueueStore.getState();
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface PostProps {
|
||||
isPublishing?: boolean;
|
||||
onApprove?: () => void;
|
||||
onReject?: () => void;
|
||||
quotedByMap?: Map<string, Comment[]>;
|
||||
}
|
||||
|
||||
export const Post = ({
|
||||
|
||||
@@ -1227,27 +1227,29 @@
|
||||
resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3"
|
||||
integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==
|
||||
|
||||
"@electron-forge/cli@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/cli/-/cli-7.6.0.tgz#4c88ac4525a333b20ab6c4083a8b79fbc072782c"
|
||||
integrity sha512-5G7rBbvTb4HJDiCuhncBzNaRj1e1dEmrk6jExpziqv4Y8p9b+nxfdOjsjWu0hvAl4k2V65Rnm1uEkAA7MmlZOQ==
|
||||
"@electron-forge/cli@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/cli/-/cli-7.8.0.tgz#871e6f5e491cf7c41b11847f3176369176c2e073"
|
||||
integrity sha512-XZ+Hg7pxeE9pgrahqcpMlND+VH0l0UTZLyO5wkI+YfanNyBQksB2mw24XeEtCA6x8F2IaEYdIGgijmPF6qpjzA==
|
||||
dependencies:
|
||||
"@electron-forge/core" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/core" "7.8.0"
|
||||
"@electron-forge/core-utils" "7.8.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
"@electron/get" "^3.0.0"
|
||||
chalk "^4.0.0"
|
||||
commander "^4.1.1"
|
||||
commander "^11.1.0"
|
||||
debug "^4.3.1"
|
||||
fs-extra "^10.0.0"
|
||||
listr2 "^7.0.2"
|
||||
log-symbols "^4.0.0"
|
||||
semver "^7.2.1"
|
||||
|
||||
"@electron-forge/core-utils@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/core-utils/-/core-utils-7.6.0.tgz#cca5fbd64d111fde13eae1440eac475dae9051a6"
|
||||
integrity sha512-7XVKHPI87p558kVen280yB1UC2cVGHvrMfnPFv4zm3TQHEVaKWKW+5y+UZsKUnGAukNlahHWuHF/1S8dRCJNEg==
|
||||
"@electron-forge/core-utils@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/core-utils/-/core-utils-7.8.0.tgz#e38682c892ac260d9f60ac11c3f9f7c150e56fde"
|
||||
integrity sha512-ZioRzqkXVOGuwkfvXN/FPZxcssJ9AkOZx6RvxomQn90F77G2KfEbw4ZwAxVTQ+jWNUzydTic5qavWle++Y5IeA==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
"@electron/rebuild" "^3.7.0"
|
||||
"@malept/cross-spawn-promise" "^2.0.0"
|
||||
chalk "^4.0.0"
|
||||
@@ -1256,24 +1258,23 @@
|
||||
fs-extra "^10.0.0"
|
||||
log-symbols "^4.0.0"
|
||||
semver "^7.2.1"
|
||||
yarn-or-npm "^3.0.1"
|
||||
|
||||
"@electron-forge/core@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/core/-/core-7.6.0.tgz#2c662b844ec412c19d58820dfad6e2680ec26bd8"
|
||||
integrity sha512-DgkjpoK+SPExNTLZL1v81zl0RswQWvMXkMnMqZYf0/S/KHKTXWsoE9KTzr8fDGpiG3nUJXWMqHyny9zLoUdKXQ==
|
||||
"@electron-forge/core@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/core/-/core-7.8.0.tgz#45991916312c6ee76cb627653c565182a3dd189b"
|
||||
integrity sha512-7byf660ECZND+irOhGxvpmRXjk1bMrsTWh5J2AZMEvaXI8tub9OrZY9VSbi5fcDt0lpHPKmgVk7NRf/ZjJ+beQ==
|
||||
dependencies:
|
||||
"@electron-forge/core-utils" "7.6.0"
|
||||
"@electron-forge/maker-base" "7.6.0"
|
||||
"@electron-forge/plugin-base" "7.6.0"
|
||||
"@electron-forge/publisher-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/template-base" "7.6.0"
|
||||
"@electron-forge/template-vite" "7.6.0"
|
||||
"@electron-forge/template-vite-typescript" "7.6.0"
|
||||
"@electron-forge/template-webpack" "7.6.0"
|
||||
"@electron-forge/template-webpack-typescript" "7.6.0"
|
||||
"@electron-forge/tracer" "7.6.0"
|
||||
"@electron-forge/core-utils" "7.8.0"
|
||||
"@electron-forge/maker-base" "7.8.0"
|
||||
"@electron-forge/plugin-base" "7.8.0"
|
||||
"@electron-forge/publisher-base" "7.8.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
"@electron-forge/template-base" "7.8.0"
|
||||
"@electron-forge/template-vite" "7.8.0"
|
||||
"@electron-forge/template-vite-typescript" "7.8.0"
|
||||
"@electron-forge/template-webpack" "7.8.0"
|
||||
"@electron-forge/template-webpack-typescript" "7.8.0"
|
||||
"@electron-forge/tracer" "7.8.0"
|
||||
"@electron/get" "^3.0.0"
|
||||
"@electron/packager" "^18.3.5"
|
||||
"@electron/rebuild" "^3.7.0"
|
||||
@@ -1284,27 +1285,25 @@
|
||||
filenamify "^4.1.0"
|
||||
find-up "^5.0.0"
|
||||
fs-extra "^10.0.0"
|
||||
global-dirs "^3.0.0"
|
||||
got "^11.8.5"
|
||||
interpret "^3.1.1"
|
||||
listr2 "^7.0.2"
|
||||
lodash "^4.17.20"
|
||||
log-symbols "^4.0.0"
|
||||
node-fetch "^2.6.7"
|
||||
progress "^2.0.3"
|
||||
rechoir "^0.8.0"
|
||||
resolve-package "^1.0.1"
|
||||
semver "^7.2.1"
|
||||
source-map-support "^0.5.13"
|
||||
sudo-prompt "^9.1.1"
|
||||
username "^5.1.0"
|
||||
yarn-or-npm "^3.0.1"
|
||||
|
||||
"@electron-forge/maker-base@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/maker-base/-/maker-base-7.6.0.tgz#b95564390a444b44d037b53e3b796e083e935d2f"
|
||||
integrity sha512-GrVYhiA/g0NXrI13LcXrT+JKLlq8kkYyO6w0jQORqDFeRSLRoLhrru5w0msg0wINGugBe+/NwyAyFZ2KaQ6o4g==
|
||||
"@electron-forge/maker-base@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/maker-base/-/maker-base-7.8.0.tgz#4790a4cd477bd5952aba783e9ff433d15a56217c"
|
||||
integrity sha512-yGRvz70w+NnKO7PhzNFRgYM+x6kxYFgpbChJIQBs3WChd9bGjL+MZLrwYqmxOFLpWNwRAJ6PEi4E/8U5GgV6AQ==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
fs-extra "^10.0.0"
|
||||
which "^2.0.2"
|
||||
|
||||
@@ -1317,60 +1316,60 @@
|
||||
fs-extra "^10.0.0"
|
||||
which "^2.0.2"
|
||||
|
||||
"@electron-forge/maker-dmg@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/maker-dmg/-/maker-dmg-7.6.0.tgz#facd0f5a4aef75d036a7709f841da9150a13c80b"
|
||||
integrity sha512-Wa4XG9r4RldF5uy7vef5/BR8o/P+BmltZIbZulnFwhOYEu4Quj+SDF1bet7y3tM2i1am/sEocFcBHXC7CRk8xg==
|
||||
"@electron-forge/maker-dmg@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/maker-dmg/-/maker-dmg-7.8.0.tgz#1286ebffd5c7a8f4ac91d042b0578c8b6ccb6750"
|
||||
integrity sha512-ml6GpHvUyhOapIF1ALEM4zCqXiAf2+t+3FqKnjNtiVbH5fnV2CW//SWWozrvAGTrYGi/6V4s9TL/rIek0BHOPA==
|
||||
dependencies:
|
||||
"@electron-forge/maker-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/maker-base" "7.8.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
fs-extra "^10.0.0"
|
||||
optionalDependencies:
|
||||
electron-installer-dmg "^5.0.1"
|
||||
|
||||
"@electron-forge/maker-squirrel@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/maker-squirrel/-/maker-squirrel-7.6.0.tgz#98475a3c400db6dcdaf876f25bc4ef893de8ab41"
|
||||
integrity sha512-8tqsJBRAe37YZSKv1fPc1tijQljkSlUQCaeun37ZOM/viurSeydt5nu2M+UDmJHAfD/PRZMjnYvCCWH+08wGVg==
|
||||
"@electron-forge/maker-squirrel@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/maker-squirrel/-/maker-squirrel-7.8.0.tgz#56cccbaed4d47b3a405b22c6fa760f57455b7632"
|
||||
integrity sha512-On8WIyjNtNlWf8NJRRVToighGCCU+wcxytFM0F8Zx/pLszgc01bt7wIarOiAIzuIT9Z8vshAYA0iG1U099jfeA==
|
||||
dependencies:
|
||||
"@electron-forge/maker-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/maker-base" "7.8.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
fs-extra "^10.0.0"
|
||||
optionalDependencies:
|
||||
electron-winstaller "^5.3.0"
|
||||
|
||||
"@electron-forge/maker-zip@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/maker-zip/-/maker-zip-7.6.0.tgz#4d590b1d2ea3553374e2a634ab1e517cd4279d14"
|
||||
integrity sha512-sDPQoEs6CnkxsydvnfZByBGf+RREky2xqiusWCvaPnUoLRpq96SFaBb1BRCS6tQKQHKkaEUXEC5pBdrYGLHPVg==
|
||||
"@electron-forge/maker-zip@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/maker-zip/-/maker-zip-7.8.0.tgz#8d594f8a4cb3e66f0775e766e2f3c30eadf035f8"
|
||||
integrity sha512-7MLD7GkZdlGecC9GvgBu0sWYt48p3smYvr+YCwlpdH1CTeLmWhvCqeH33a2AB0XI5CY8U8jnkG2jgdTkzr/EQw==
|
||||
dependencies:
|
||||
"@electron-forge/maker-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/maker-base" "7.8.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
cross-zip "^4.0.0"
|
||||
fs-extra "^10.0.0"
|
||||
got "^11.8.5"
|
||||
|
||||
"@electron-forge/plugin-auto-unpack-natives@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/plugin-auto-unpack-natives/-/plugin-auto-unpack-natives-7.6.0.tgz#222ea2d265a00f96a3a65f37f547c90e28c0d740"
|
||||
integrity sha512-rSWRLJinRIxtlkLke0uJzOLksRnXszu3hZrzlgOWChDuMFM298yb6gxWAjYh94VoNxXrUHl9Cd4ia/5+wgPwwg==
|
||||
"@electron-forge/plugin-auto-unpack-natives@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/plugin-auto-unpack-natives/-/plugin-auto-unpack-natives-7.8.0.tgz#5167410e0e74d3bd8949c6acbe79249b7cffd1ce"
|
||||
integrity sha512-JGal5ltZmbTQ5rNq67OgGC4MJ2zjjFW0fqykHy8X9J8cgaH7SRdKkT4yYZ8jH01IAF1J57FD2zIob1MvcBqjcg==
|
||||
dependencies:
|
||||
"@electron-forge/plugin-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/plugin-base" "7.8.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
|
||||
"@electron-forge/plugin-base@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/plugin-base/-/plugin-base-7.6.0.tgz#3925651b6e33a989209f7272fc54724f1364dd43"
|
||||
integrity sha512-9llu4algWZJAJFVVZtd/Xa71c0QVxRmoMrpHX2SB+XJ+ZlFVdXrlnhn2hc/CnM0by9cBElyAL3cx3533OKS7lA==
|
||||
"@electron-forge/plugin-base@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/plugin-base/-/plugin-base-7.8.0.tgz#bb617781c28b99d2b65d6f2e66679b245d388e78"
|
||||
integrity sha512-rDeeChRWIp5rQVo3Uc1q0ncUvA+kWWURW7tMuQjPvy2qVSgX+jIf5krk+T1Dp06+D4YZzEIrkibRaamAaIcR1w==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
|
||||
"@electron-forge/publisher-base@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/publisher-base/-/publisher-base-7.6.0.tgz#869748f2988994371e4ef842077970dcec9f5367"
|
||||
integrity sha512-IL9bbIb/4J4I1bfW53RAmE/Al835XJsOwFXTLUnxnaGtbWg5jz7eiyw9Vl8XvvfHN1Dpoa9f94to8keU2MXgDg==
|
||||
"@electron-forge/publisher-base@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/publisher-base/-/publisher-base-7.8.0.tgz#5b86f7dec28aecf19448a54b7f65371e18bcc9c7"
|
||||
integrity sha512-wrZyptJ0Uqvlh2wYzDZfIu2HgCQ+kdGiBlcucmLY4W+GUqf043O8cbYso3D9NXQxOow55QC/1saCQkgLphprPA==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
|
||||
"@electron-forge/shared-types@7.11.1":
|
||||
version "7.11.1"
|
||||
@@ -1382,61 +1381,62 @@
|
||||
"@electron/rebuild" "^3.7.0"
|
||||
listr2 "^7.0.2"
|
||||
|
||||
"@electron-forge/shared-types@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/shared-types/-/shared-types-7.6.0.tgz#5e073e2a9949e2af1b9119a75a2a0a7fec690efc"
|
||||
integrity sha512-qpJRaPo/tx/+t3iFdUWnK4Tk/elo+Izk3yS+BhzfaF0XOK8wS+NNYW4vycK6eVMxN3Yu7/924MQFtPlCKlWHvA==
|
||||
"@electron-forge/shared-types@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/shared-types/-/shared-types-7.8.0.tgz#dbc9b20ecd547d0758d7abc90587983c7f2fd3e8"
|
||||
integrity sha512-Ul+7HPvAZiAirqpZm0vc9YvlkAE+2bcrI10p3t50mEtuxn5VO/mB72NXiEKfWzHm8F31JySIe9bUV6s1MHQcCw==
|
||||
dependencies:
|
||||
"@electron-forge/tracer" "7.6.0"
|
||||
"@electron-forge/tracer" "7.8.0"
|
||||
"@electron/packager" "^18.3.5"
|
||||
"@electron/rebuild" "^3.7.0"
|
||||
listr2 "^7.0.2"
|
||||
|
||||
"@electron-forge/template-base@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-base/-/template-base-7.6.0.tgz#b057590b4e8a1d8bce460ed8520973f95d3699dd"
|
||||
integrity sha512-lhvab8a/knuGnpzep8BMOEkgnkHGr11QELGBzslEnA6rwZi9DDyEgmMCk6VWOVQNHMeuEqh5XlgjVqJmjW6nIQ==
|
||||
"@electron-forge/template-base@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-base/-/template-base-7.8.0.tgz#9721d6c42120db094c83436404a458a2cb6e5412"
|
||||
integrity sha512-hc8NwoDqEEmZFH/p0p3MK/7xygMmI+cm8Gavoj2Mr2xS7VUUu4r3b5PwIGKvkLfPG34uwsiVwtid2t1rWGF4UA==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/core-utils" "7.8.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
"@malept/cross-spawn-promise" "^2.0.0"
|
||||
debug "^4.3.1"
|
||||
fs-extra "^10.0.0"
|
||||
username "^5.1.0"
|
||||
|
||||
"@electron-forge/template-vite-typescript@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.6.0.tgz#f00ccb5cba2de02ce2ebde8f65058d708a2ef79f"
|
||||
integrity sha512-i2Bt5Hehoq2CNNrUQjl8DQX7VatBMQ6mv+CCa+m+EV92nUYxXsoFva62/5ITpc3gFAGd1upw/S7dTbHV6GOwsA==
|
||||
"@electron-forge/template-vite-typescript@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.8.0.tgz#106558f6e3af263629e730fd2a60e9eaffefb7cf"
|
||||
integrity sha512-kW3CaVxKHUYuVfY+rT3iepeZ69frBRGh3YZOngLY2buCvGIqNEx+VCgrFBRDDbOKGmwQtwO1E9wp2rtC8q6Ztg==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/template-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
"@electron-forge/template-base" "7.8.0"
|
||||
fs-extra "^10.0.0"
|
||||
|
||||
"@electron-forge/template-vite@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-vite/-/template-vite-7.6.0.tgz#a1bed7d52c93883efb0108ceb9d41f76e1a0daf3"
|
||||
integrity sha512-C0V0dGDO1hLXnAM9lGnZU0esNOTbxwcgILWJXv0mYErBkmputAIi3HM1Is3h3JdSijXgVbRWcIQxFxJlOCpB/A==
|
||||
"@electron-forge/template-vite@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-vite/-/template-vite-7.8.0.tgz#8906aa82cb4c0efc3501da0141a9dd382715dd86"
|
||||
integrity sha512-bf/jd8WzD0gU7Jet+WSi0Lm0SQmseb08WY27ZfJYEs2EVNMiwDfPicgQnOaqP++2yTrXhj1OY/rolZCP9CUyVw==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/template-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
"@electron-forge/template-base" "7.8.0"
|
||||
fs-extra "^10.0.0"
|
||||
|
||||
"@electron-forge/template-webpack-typescript@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.6.0.tgz#2a38e50181d94fb1257350180eaac3f47d115280"
|
||||
integrity sha512-fDj4DkGxJJjGL8lpowFnkX7PvV9koLHKJuyusK8p8ayVMGoHpHrIcVCrV06tKYOvhFrL/ahW+CKKvjlxF8niEg==
|
||||
"@electron-forge/template-webpack-typescript@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.8.0.tgz#26731829d8fa18196ae65bf3db46fb6659b1eac7"
|
||||
integrity sha512-Pl8l+gv3HzqCfFIMLxlEsoAkNd0VEWeZZ675SYyqs0/kBQUifn0bKNhVE4gUZwKGgQCcG1Gvb23KdVGD3H3XmA==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/template-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
"@electron-forge/template-base" "7.8.0"
|
||||
fs-extra "^10.0.0"
|
||||
|
||||
"@electron-forge/template-webpack@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-webpack/-/template-webpack-7.6.0.tgz#b8f6f108dd0fedb9b459db5bb7180cb839656775"
|
||||
integrity sha512-+HEf0ryUfLpHvl27TXSdP2Ob69+ktNtr5EnmroZGGIxhSAtEs4HloPtDF9PSfBzm38pZhQBZn78kY9LbITTGjg==
|
||||
"@electron-forge/template-webpack@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/template-webpack/-/template-webpack-7.8.0.tgz#b85b2827193142c6c836ceec8a1776471fdd28c4"
|
||||
integrity sha512-AdLGC6NVgrd7Q0SaaeiwJKmSBjN6C2EHxZgLMy1yxNSpazU9m3DtYQilDjXqmCWfxkeNzdke0NaeDvLgdJSw5A==
|
||||
dependencies:
|
||||
"@electron-forge/shared-types" "7.6.0"
|
||||
"@electron-forge/template-base" "7.6.0"
|
||||
"@electron-forge/shared-types" "7.8.0"
|
||||
"@electron-forge/template-base" "7.8.0"
|
||||
fs-extra "^10.0.0"
|
||||
|
||||
"@electron-forge/tracer@7.11.1":
|
||||
@@ -1446,10 +1446,10 @@
|
||||
dependencies:
|
||||
chrome-trace-event "^1.0.3"
|
||||
|
||||
"@electron-forge/tracer@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/tracer/-/tracer-7.6.0.tgz#f31fc5bbe75dec70bb79add4d4c72adb18f742b3"
|
||||
integrity sha512-Rn76RHqNhLyZDnu+xY/X73+bv+Q09XKaZBL/WvlYBbvrrHe26NOHJ3IHXxkWRokSWd4B7lOGLGKm3j1Il8dVbQ==
|
||||
"@electron-forge/tracer@7.8.0":
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron-forge/tracer/-/tracer-7.8.0.tgz#19a9a8164efd2b7a9fa144d424d5e714f115cf77"
|
||||
integrity sha512-t4fIATZEX6/7PJNfyh6tLzKEsNMpO01Nz/rgHWBxeRvjCw5UNul9OOxoM7b43vfFAO9Jv++34oI3VJ09LeVQ2Q==
|
||||
dependencies:
|
||||
chrome-trace-event "^1.0.3"
|
||||
|
||||
@@ -2911,13 +2911,6 @@
|
||||
wrap-ansi "^8.1.0"
|
||||
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
|
||||
|
||||
"@isaacs/fs-minipass@^4.0.0":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
|
||||
integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==
|
||||
dependencies:
|
||||
minipass "^7.0.4"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.13"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
|
||||
@@ -6457,10 +6450,10 @@ chownr@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
|
||||
chownr@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4"
|
||||
integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==
|
||||
chownr@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
|
||||
|
||||
chrome-trace-event@^1.0.3:
|
||||
version "1.0.4"
|
||||
@@ -6620,6 +6613,11 @@ comma-separated-tokens@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
|
||||
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
|
||||
|
||||
commander@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906"
|
||||
integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==
|
||||
|
||||
commander@^12.0.0, commander@^12.1.0:
|
||||
version "12.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
|
||||
@@ -6635,11 +6633,6 @@ commander@^2.20.0, commander@^2.20.3, commander@^2.8.1:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
||||
|
||||
commander@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
@@ -6986,7 +6979,7 @@ cross-fetch@3.1.6:
|
||||
dependencies:
|
||||
node-fetch "^2.6.11"
|
||||
|
||||
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
||||
cross-spawn@^6.0.0:
|
||||
version "6.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57"
|
||||
integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==
|
||||
@@ -8292,7 +8285,7 @@ find-up@^3.0.0:
|
||||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
find-up@^4.0.0, find-up@^4.1.0:
|
||||
find-up@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
|
||||
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
|
||||
@@ -8460,6 +8453,13 @@ fs-extra@^8.1.0:
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-minipass@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
||||
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
|
||||
fs-minipass@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54"
|
||||
@@ -8583,13 +8583,6 @@ get-caller-file@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-installed-path@^2.0.3:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/get-installed-path/-/get-installed-path-2.1.1.tgz#a1f33dc6b8af542c9331084e8edbe37fe2634152"
|
||||
integrity sha512-Qkn9eq6tW5/q9BDVdMpB8tOHljX9OSP0jRC5TRNVA4qRc839t4g8KQaR8t0Uv0EFVL0MlyG7m/ofjEgAROtYsA==
|
||||
dependencies:
|
||||
global-modules "1.0.0"
|
||||
|
||||
get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
|
||||
@@ -8747,7 +8740,14 @@ global-directory@^4.0.1:
|
||||
dependencies:
|
||||
ini "4.1.1"
|
||||
|
||||
global-modules@1.0.0, global-modules@^1.0.0:
|
||||
global-dirs@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485"
|
||||
integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==
|
||||
dependencies:
|
||||
ini "2.0.0"
|
||||
|
||||
global-modules@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
|
||||
integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
|
||||
@@ -9321,6 +9321,11 @@ inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ini@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
|
||||
integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
|
||||
|
||||
ini@4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1"
|
||||
@@ -11748,12 +11753,17 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3.
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2:
|
||||
minipass@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
|
||||
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
|
||||
|
||||
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3, minipass@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
||||
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
||||
|
||||
minizlib@^2.0.0, minizlib@^2.1.2:
|
||||
minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
|
||||
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
|
||||
@@ -11761,13 +11771,6 @@ minizlib@^2.0.0, minizlib@^2.1.2:
|
||||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
|
||||
minizlib@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c"
|
||||
integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==
|
||||
dependencies:
|
||||
minipass "^7.1.2"
|
||||
|
||||
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
@@ -11780,6 +11783,11 @@ mkdirp@^0.5.1:
|
||||
dependencies:
|
||||
minimist "^1.2.6"
|
||||
|
||||
mkdirp@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
modify-filename@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1"
|
||||
@@ -12812,13 +12820,6 @@ pkg-conf@^3.1.0:
|
||||
find-up "^3.0.0"
|
||||
load-json-file "^5.2.0"
|
||||
|
||||
pkg-dir@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
|
||||
integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
|
||||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
pkg-dir@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760"
|
||||
@@ -13588,13 +13589,6 @@ resolve-from@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
|
||||
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
|
||||
|
||||
resolve-package@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-package/-/resolve-package-1.0.1.tgz#686f70b188bd7d675f5bbc4282ccda060abb9d27"
|
||||
integrity sha512-rzB7NnQpOkPHBWFPP3prUMqOP6yg3HkRGgcvR+lDyvyHoY3fZLFLYDkPXh78SPVBAE6VTCk/V+j8we4djg6o4g==
|
||||
dependencies:
|
||||
get-installed-path "^2.0.3"
|
||||
|
||||
resolve-pkg-maps@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
|
||||
@@ -14736,16 +14730,17 @@ tar-stream@^2.1.4:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar@7.5.4, tar@^6.0.5, tar@^6.1.11, tar@^6.1.2, tar@^6.2.1, tar@^7.4.3:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.4.tgz#18b53b44f939a7e03ed874f1fafe17d29e306c81"
|
||||
integrity sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==
|
||||
tar@6.2.1, tar@^6.0.5, tar@^6.1.11, tar@^6.1.2, tar@^6.2.1, tar@^7.4.3:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
|
||||
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
|
||||
dependencies:
|
||||
"@isaacs/fs-minipass" "^4.0.0"
|
||||
chownr "^3.0.0"
|
||||
minipass "^7.1.2"
|
||||
minizlib "^3.1.0"
|
||||
yallist "^5.0.0"
|
||||
chownr "^2.0.0"
|
||||
fs-minipass "^2.0.0"
|
||||
minipass "^5.0.0"
|
||||
minizlib "^2.1.1"
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
tcp-port-used@1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -16179,11 +16174,6 @@ yallist@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yallist@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533"
|
||||
integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
|
||||
|
||||
yaml@^1.10.0, yaml@^1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||
@@ -16212,14 +16202,6 @@ yargs@^17.0.1, yargs@^17.7.1:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
yarn-or-npm@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/yarn-or-npm/-/yarn-or-npm-3.0.1.tgz#6336eea4dff7e23e226acc98c1a8ada17a1b8666"
|
||||
integrity sha512-fTiQP6WbDAh5QZAVdbMQkecZoahnbOjClTQhzv74WX5h2Uaidj1isf9FDes11TKtsZ0/ZVfZsqZ+O3x6aLERHQ==
|
||||
dependencies:
|
||||
cross-spawn "^6.0.5"
|
||||
pkg-dir "^4.2.0"
|
||||
|
||||
yauzl@^2.10.0, yauzl@^2.4.2:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
|
||||
Reference in New Issue
Block a user