refactor(core): remove legacy plebbit terminology

This commit is contained in:
Tommaso Casaburi
2026-04-17 10:48:27 +07:00
parent e366dac25c
commit ee7a5b5778
158 changed files with 626 additions and 836 deletions
+1 -1
View File
@@ -164,6 +164,6 @@ Routes profiled: /route1, /route2, ...
- If `__getReactScanReport` returns null, note "react-scan report unavailable" and rely on commit counts
- If a route has no content or fails to load, note it in Info and move on
- **Always stop tracing and close the browser when done, even on errors** — wrap your workflow in a try/finally mindset: if any step fails, still run `tracing-stop` and `close`
- Board codes (`biz`, `pol`, `g`, etc.) map to subplebbit addresses via the app's directory
- Board codes (`biz`, `pol`, `g`, etc.) map to community addresses via the app's directory
- High commit counts without long tasks = frequent cheap rerenders — still worth fixing for efficiency
- React-scan report pinpoints exact components — prioritize these in recommendations
+2 -2
View File
@@ -55,13 +55,13 @@ AI adds try/catch blocks and null guards everywhere, even on trusted codepaths.
```typescript
// ❌ Slop — bitsocial-react-hooks already handles errors internally
try {
const { feed } = useFeed({ subplebbitAddresses });
const { feed } = useFeed({ communities });
} catch (error) {
console.error('Failed to fetch feed:', error);
}
// ✅ Clean — just use the hook directly
const { feed } = useFeed({ subplebbitAddresses });
const { feed } = useFeed({ communities });
```
### `as any` casts
+1 -1
View File
@@ -160,5 +160,5 @@ playwright-cli -s=prof-3 close 2>/dev/null
- **Per-route collection**: Data resets on each `goto` — the profiler collects before navigating away.
- **addInitScript persistence**: Instrumentation re-injects automatically in each new document.
- **Tracing**: Each subagent produces a `trace.zip` viewable in [Trace Viewer](https://trace.playwright.dev).
- **Board codes**: `biz`, `pol`, `g`, `a`, `v`, etc. map to subplebbit addresses via the directory.
- **Board codes**: `biz`, `pol`, `g`, `a`, `v`, etc. map to community addresses via the directory.
- **Without react-scan**: If `__getReactScanReport` returns null, the profiler falls back to commit counts + render bursts (still useful, just no component names).
@@ -134,7 +134,7 @@ if (typeof window !== 'undefined') {
| useEffect pattern | Replace with |
|-------------------|-------------|
| Fetch data | `useComment`, `useFeed`, `useSubplebbit`, etc. from bitsocial-react-hooks |
| Fetch data | `useComment`, `useFeed`, `useCommunity`, etc. from bitsocial-react-hooks |
| Sync shared state | Zustand store in `src/stores/` |
| Derive values from state | Calculate during render |
| Boolean loading/error flags | `state` field from bitsocial-react-hooks, or state machine in Zustand |
+2 -2
View File
@@ -55,13 +55,13 @@ AI adds try/catch blocks and null guards everywhere, even on trusted codepaths.
```typescript
// ❌ Slop — bitsocial-react-hooks already handles errors internally
try {
const { feed } = useFeed({ subplebbitAddresses });
const { feed } = useFeed({ communities });
} catch (error) {
console.error('Failed to fetch feed:', error);
}
// ✅ Clean — just use the hook directly
const { feed } = useFeed({ subplebbitAddresses });
const { feed } = useFeed({ communities });
```
### `as any` casts
+1 -1
View File
@@ -158,5 +158,5 @@ playwright-cli -s=prof-3 close 2>/dev/null
- **Per-route collection**: Data resets on each `goto` — the profiler collects before navigating away.
- **addInitScript persistence**: Instrumentation re-injects automatically in each new document.
- **Tracing**: Each subagent produces a `trace.zip` viewable in [Trace Viewer](https://trace.playwright.dev).
- **Board codes**: `biz`, `pol`, `g`, `a`, `v`, etc. map to subplebbit addresses via the directory.
- **Board codes**: `biz`, `pol`, `g`, `a`, `v`, etc. map to community addresses via the directory.
- **Without react-scan**: If `__getReactScanReport` returns null, the profiler falls back to commit counts + render bursts (still useful, just no component names).
@@ -134,7 +134,7 @@ if (typeof window !== 'undefined') {
| useEffect pattern | Replace with |
|-------------------|-------------|
| Fetch data | `useComment`, `useFeed`, `useSubplebbit`, etc. from bitsocial-react-hooks |
| Fetch data | `useComment`, `useFeed`, `useCommunity`, etc. from bitsocial-react-hooks |
| Sync shared state | Zustand store in `src/stores/` |
| Derive values from state | Calculate during render |
| Boolean loading/error flags | `state` field from bitsocial-react-hooks, or state machine in Zustand |
+1 -1
View File
@@ -163,6 +163,6 @@ Routes profiled: /route1, /route2, ...
- If `__getReactScanReport` returns null, note "react-scan report unavailable" and rely on commit counts
- If a route has no content or fails to load, note it in Info and move on
- **Always stop tracing and close the browser when done, even on errors** — wrap your workflow in a try/finally mindset: if any step fails, still run `tracing-stop` and `close`
- Board codes (`biz`, `pol`, `g`, etc.) map to subplebbit addresses via the app's directory
- Board codes (`biz`, `pol`, `g`, etc.) map to community addresses via the app's directory
- High commit counts without long tasks = frequent cheap rerenders — still worth fixing for efficiency
- React-scan report pinpoints exact components — prioritize these in recommendations
+2 -2
View File
@@ -55,13 +55,13 @@ AI adds try/catch blocks and null guards everywhere, even on trusted codepaths.
```typescript
// ❌ Slop — bitsocial-react-hooks already handles errors internally
try {
const { feed } = useFeed({ subplebbitAddresses });
const { feed } = useFeed({ communities });
} catch (error) {
console.error('Failed to fetch feed:', error);
}
// ✅ Clean — just use the hook directly
const { feed } = useFeed({ subplebbitAddresses });
const { feed } = useFeed({ communities });
```
### `as any` casts
+1 -1
View File
@@ -160,5 +160,5 @@ playwright-cli -s=prof-3 close 2>/dev/null
- **Per-route collection**: Data resets on each `goto` — the profiler collects before navigating away.
- **addInitScript persistence**: Instrumentation re-injects automatically in each new document.
- **Tracing**: Each subagent produces a `trace.zip` viewable in [Trace Viewer](https://trace.playwright.dev).
- **Board codes**: `biz`, `pol`, `g`, `a`, `v`, etc. map to subplebbit addresses via the directory.
- **Board codes**: `biz`, `pol`, `g`, `a`, `v`, etc. map to community addresses via the directory.
- **Without react-scan**: If `__getReactScanReport` returns null, the profiler falls back to commit counts + render bursts (still useful, just no component names).
@@ -134,7 +134,7 @@ if (typeof window !== 'undefined') {
| useEffect pattern | Replace with |
|-------------------|-------------|
| Fetch data | `useComment`, `useFeed`, `useSubplebbit`, etc. from bitsocial-react-hooks |
| Fetch data | `useComment`, `useFeed`, `useCommunity`, etc. from bitsocial-react-hooks |
| Sync shared state | Zustand store in `src/stores/` |
| Derive values from state | Calculate during render |
| Boolean loading/error flags | `state` field from bitsocial-react-hooks, or state machine in Zustand |
+1 -1
View File
@@ -263,7 +263,7 @@ jobs:
- name: Optimize APK
run: cd android/app/build/outputs/apk/release && zipalign 4 app-release-unsigned.apk app-release-unsigned-zip.apk
- name: Sign APK
run: cd android/app/build/outputs/apk/release && apksigner sign --ks ../../../../../plebbit.keystore --ks-pass pass:${{ secrets.PLEBBIT_REACT_KEYSTORE_PASSWORD }} --ks-key-alias release --out app-release-signed.apk app-release-unsigned-zip.apk
run: cd android/app/build/outputs/apk/release && apksigner sign --ks ../../../../../bitsocial.keystore --ks-pass pass:${{ secrets.BITSOCIAL_KEYSTORE_PASSWORD }} --ks-key-alias release --out app-release-signed.apk app-release-unsigned-zip.apk
- name: Stage release artifacts
run: |
mkdir -p release-assets
+2 -2
View File
@@ -1,5 +1,5 @@
# plebbit temp files
.plebbit
# PKC temp files
.pkc
bin
.env
.deploy-env
+6 -6
View File
@@ -48,14 +48,14 @@ If uncertain, ask the developer before adding an entry.
- **Mitigation:** Keep `portless` in `optionalDependencies` and make `yarn start` fall back to direct `vite` startup when `portless` is unavailable.
- **Status:** confirmed
### Do not add plebbit-js directly for Electron RPC
### Electron RPC uses direct pkc-js imports
- **Date:** 2026-03-07
- **Observed by:** Codex
- **Context:** Adding `knip` exposed `electron/start-plebbit-rpc.js` importing `@plebbit/plebbit-js/rpc` as an unlisted dependency.
- **What was surprising:** Even though that file imports `@plebbit/plebbit-js` directly, repository policy is to depend only on `@bitsocialnet/bitsocial-react-hooks` and use its transitive copy of `plebbit-js`.
- **Impact:** Agents may “fix” the unlisted import by adding `@plebbit/plebbit-js` to `package.json`, which violates project policy.
- **Mitigation:** Do not add `@plebbit/plebbit-js` to `package.json` for this repo. If `knip` flags `electron/start-plebbit-rpc.js`, handle it with a targeted `ignoreIssues` entry instead.
- **Context:** The desktop bootstrap now imports `@pkcprotocol/pkc-js/rpc` directly from `electron/start-pkc-rpc.js`.
- **What was surprising:** Most app data access still goes through `@bitsocialnet/bitsocial-react-hooks`, but the Electron-local RPC bootstrap is intentionally a direct `pkc-js` integration.
- **Impact:** Agents may try to route Electron RPC back through hooks, or reintroduce the legacy protocol package name while fixing dependency/tooling warnings.
- **Mitigation:** Keep Electron RPC on direct `@pkcprotocol/pkc-js` imports. If `knip` flags `electron/start-pkc-rpc.js`, audit the actual dependency graph before adding ignores or legacy packages.
- **Status:** confirmed
### Electron packaging can ship a broken `better-sqlite3` binary
@@ -63,7 +63,7 @@ If uncertain, ask the developer before adding an entry.
- **Date:** 2026-03-17
- **Observed by:** Codex
- **Context:** Investigating the `v0.7.1` macOS arm64 DMG after the app showed a live IPFS node but never loaded boards or comments.
- **What was surprising:** The packaged app can start IPFS successfully while `electron/start-plebbit-rpc.js` loops forever because `/Applications/5chan.app/.../better_sqlite3.node` was built for plain Node 22 (`NODE_MODULE_VERSION 127`) instead of Electron 36 (`NODE_MODULE_VERSION 135`).
- **What was surprising:** The packaged app can start IPFS successfully while `electron/start-pkc-rpc.js` loops forever because `/Applications/5chan.app/.../better_sqlite3.node` was built for plain Node 22 (`NODE_MODULE_VERSION 127`) instead of Electron 36 (`NODE_MODULE_VERSION 135`).
- **Impact:** The local RPC server on `ws://localhost:9138` never starts, so the desktop app cannot load boards, posts, or comments even though node stats look healthy.
- **Mitigation:** Before any Electron package/build job, run `yarn electron:prepare-package` so `better-sqlite3` is rebuilt for Electron and immediately verified via `ELECTRON_RUN_AS_NODE=1 electron`.
- **Status:** confirmed
-6
View File
@@ -12,12 +12,6 @@ const ipfsClientMacPath = path.join(ipfsClientsPath, 'mac');
const ipfsClientLinuxPath = path.join(ipfsClientsPath, 'linux');
const kuboReleaseBaseUrl = 'https://github.com/ipfs/kubo/releases/download';
// plebbit kubo download links https://github.com/plebbit/kubo/releases
// const ipfsClientVersion = '0.20.0'
// const ipfsClientWindowsUrl = `https://github.com/plebbit/kubo/releases/download/v${ipfsClientVersion}/ipfs-windows-amd64`
// const ipfsClientMacUrl = `https://github.com/plebbit/kubo/releases/download/v${ipfsClientVersion}/ipfs-darwin-amd64`
// const ipfsClientLinuxUrl = `https://github.com/plebbit/kubo/releases/download/v${ipfsClientVersion}/ipfs-linux-amd64`
const ipfsClientVersion = '0.39.0';
// Resolve desired build arch: allow overriding via env (so cross-arch builds pick correct binary)
+6 -6
View File
@@ -3,18 +3,18 @@
import util from 'util';
import fs from 'fs-extra';
import path from 'path';
import EnvPaths from 'env-paths';
const envPaths = EnvPaths('plebbit', { suffix: false });
import { getPkcLogPath } from './pkc-paths.js';
const logRootPath = getPkcLogPath();
// previous version created a file instead of folder
// we should remove this at some point
try {
if (fs.lstatSync(envPaths.log).isFile()) {
fs.removeSync(envPaths.log);
if (fs.lstatSync(logRootPath).isFile()) {
fs.removeSync(logRootPath);
}
} catch (e) {}
const logFilePath = path.join(envPaths.log, new Date().toISOString().substring(0, 7));
const logFilePath = path.join(logRootPath, new Date().toISOString().substring(0, 7));
fs.ensureFileSync(logFilePath);
const logFile = fs.createWriteStream(logFilePath, { flags: 'a' });
const writeLog = (...args) => {
@@ -54,4 +54,4 @@ console.debug = (...args) => {
process.on('uncaughtException', console.error);
process.on('unhandledRejection', console.error);
console.log(envPaths);
console.log({ logRootPath });
+6 -6
View File
@@ -5,11 +5,11 @@ import { downloadAndInstallUpdate } from './app-updater.js';
import isDev from 'electron-is-dev';
import fs from 'fs';
import path from 'path';
import EnvPaths from 'env-paths';
import startIpfs from './start-ipfs.js';
import './start-plebbit-rpc.js';
import './start-pkc-rpc.js';
import { URL, fileURLToPath } from 'node:url';
import contextMenu from 'electron-context-menu';
import { getPkcDataPath } from './pkc-paths.js';
// Determine __filename and dirname for ESM
const __filename = fileURLToPath(import.meta.url);
@@ -39,10 +39,10 @@ startIpfs.onError = (error) => {
}
};
// send plebbit rpc auth key to renderer
const plebbitDataPath = !isDev ? EnvPaths('plebbit', { suffix: false }).data : path.join(dirname, '..', '.plebbit');
const plebbitRpcAuthKey = fs.readFileSync(path.join(plebbitDataPath, 'auth-key'), 'utf8');
ipcMain.on('get-plebbit-rpc-auth-key', (event) => event.reply('plebbit-rpc-auth-key', plebbitRpcAuthKey));
// Send the local PKC RPC auth key to the isolated renderer bridge.
const pkcDataPath = getPkcDataPath({ isDev, projectRoot: path.join(dirname, '..') });
const pkcRpcAuthKey = fs.readFileSync(path.join(pkcDataPath, 'auth-key'), 'utf8');
ipcMain.on('get-pkc-rpc-auth-key', (event) => event.reply('pkc-rpc-auth-key', pkcRpcAuthKey));
// use common user agent instead of electron so img, video, audio, iframe elements don't get blocked
// https://www.whatismybrowser.com/guides/the-latest-version/chrome
+13
View File
@@ -0,0 +1,13 @@
import path from 'path';
import EnvPaths from 'env-paths';
const PKC_APP_NAME = 'pkc';
const PKC_DEV_DATA_DIR = '.pkc';
export const getDevPkcDataPath = (projectRoot) => path.join(projectRoot, PKC_DEV_DATA_DIR);
const getProductionPkcDataPath = () => EnvPaths(PKC_APP_NAME, { suffix: false }).data;
export const getPkcLogPath = () => EnvPaths(PKC_APP_NAME, { suffix: false }).log;
export const getPkcDataPath = ({ isDev, projectRoot }) => (isDev ? getDevPkcDataPath(projectRoot) : getProductionPkcDataPath());
+6 -6
View File
@@ -3,18 +3,18 @@ import { contextBridge, ipcRenderer, webUtils } from 'electron';
// dev uses http://localhost, prod uses file://...index.html
const isDev = window.location.protocol === 'http:';
const defaultPlebbitOptions = {
plebbitRpcClientsOptions: ['ws://localhost:9138'],
const defaultPkcOptions = {
pkcRpcClientsOptions: ['ws://localhost:9138'],
httpRoutersOptions: ['https://peers.pleb.bot', 'https://routing.lol', 'https://peers.forumindex.com', 'https://peers.plebpubsub.xyz'],
};
contextBridge.exposeInMainWorld('isElectron', true);
contextBridge.exposeInMainWorld('defaultPlebbitOptions', defaultPlebbitOptions);
contextBridge.exposeInMainWorld('defaultPkcOptions', defaultPkcOptions);
contextBridge.exposeInMainWorld('defaultMediaIpfsGatewayUrl', 'http://localhost:6473');
// receive plebbit rpc auth key from main
ipcRenderer.on('plebbit-rpc-auth-key', (event, plebbitRpcAuthKey) => contextBridge.exposeInMainWorld('plebbitRpcAuthKey', plebbitRpcAuthKey));
ipcRenderer.send('get-plebbit-rpc-auth-key');
// receive PKC RPC auth key from main
ipcRenderer.on('pkc-rpc-auth-key', (event, pkcRpcAuthKey) => contextBridge.exposeInMainWorld('pkcRpcAuthKey', pkcRpcAuthKey));
ipcRenderer.send('get-pkc-rpc-auth-key');
contextBridge.exposeInMainWorld('electronApi', {
isElectron: true,
+3 -3
View File
@@ -5,10 +5,10 @@ import fs from 'fs-extra';
import ps from 'node:process';
import proxyServer from './proxy-server.js';
import tcpPortUsed from 'tcp-port-used';
import EnvPaths from 'env-paths';
import { fileURLToPath, pathToFileURL } from 'url';
import { getPkcDataPath } from './pkc-paths.js';
const dirname = path.join(path.dirname(fileURLToPath(import.meta.url)));
const envPaths = EnvPaths('plebbit', { suffix: false });
const projectRoot = path.join(dirname, '..');
// Get platform-specific binary name
const getIpfsBinaryName = () => (process.platform === 'win32' ? 'ipfs.exe' : 'ipfs');
@@ -112,7 +112,7 @@ const spawnAsync = (...args) =>
const startIpfs = async () => {
const ipfsPath = await getKuboPath();
const ipfsDataPath = isDev ? path.join(dirname, '..', '.plebbit', 'ipfs') : path.join(envPaths.data, 'ipfs');
const ipfsDataPath = path.join(getPkcDataPath({ isDev, projectRoot }), 'ipfs');
if (!fs.existsSync(ipfsPath)) {
throw Error(`ipfs binary '${ipfsPath}' doesn't exist`);
+67
View File
@@ -0,0 +1,67 @@
import tcpPortUsed from 'tcp-port-used';
import { randomBytes } from 'crypto';
import fs from 'fs-extra';
import PKCRpc from '@pkcprotocol/pkc-js/rpc';
import path from 'path';
import { fileURLToPath } from 'url';
import isDev from 'electron-is-dev';
import { getPkcDataPath } from './pkc-paths.js';
const dirname = path.join(path.dirname(fileURLToPath(import.meta.url)));
const projectRoot = path.join(dirname, '..');
// Always run the local PKC RPC server on this port so all desktop clients can reuse it.
const port = 9138;
const defaultPkcOptions = {
// find the user's OS data path
dataPath: getPkcDataPath({ isDev, projectRoot }),
kuboRpcClientsOptions: [{ url: 'http://localhost:50019/api/v0' }],
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
};
// Generate the local PKC RPC auth key if it does not exist yet.
const pkcRpcAuthKeyPath = path.join(defaultPkcOptions.dataPath, 'auth-key');
let pkcRpcAuthKey;
try {
pkcRpcAuthKey = fs.readFileSync(pkcRpcAuthKeyPath, 'utf8');
} catch (e) {
pkcRpcAuthKey = randomBytes(32).toString('base64').replace(/[/+=]/g, '').substring(0, 40);
fs.ensureFileSync(pkcRpcAuthKeyPath);
fs.writeFileSync(pkcRpcAuthKeyPath, pkcRpcAuthKey);
}
const startPkcRpcAutoRestart = async () => {
let pendingStart = false;
const start = async () => {
if (pendingStart) {
return;
}
pendingStart = true;
try {
const started = await tcpPortUsed.check(port, '127.0.0.1');
if (!started) {
const pkcWebSocketServer = await PKCRpc.PKCWsServer({ port, pkc: defaultPkcOptions, authKey: pkcRpcAuthKey });
pkcWebSocketServer.on('error', (e) => console.log('pkc rpc error', e));
console.log(`pkc rpc: listening on ws://localhost:${port} (local connections only)`);
console.log(`pkc rpc: listening on ws://localhost:${port}/${pkcRpcAuthKey} (secret auth key for remote connections)`);
pkcWebSocketServer.ws.on('connection', (socket, request) => {
console.log('pkc rpc: new connection');
// debug raw JSON RPC messages in console
if (isDev) {
socket.on('message', (message) => console.log(`pkc rpc: ${message.toString()}`));
}
});
}
} catch (e) {
console.log('failed starting pkc rpc server', e);
}
pendingStart = false;
};
// Retry every second in case another client briefly owned the shared local server.
start();
setInterval(() => {
start();
}, 1000);
};
startPkcRpcAutoRestart();
-68
View File
@@ -1,68 +0,0 @@
import tcpPortUsed from 'tcp-port-used';
import EnvPaths from 'env-paths';
import { randomBytes } from 'crypto';
import fs from 'fs-extra';
import PlebbitRpc from '@pkcprotocol/pkc-js/rpc';
import path from 'path';
import { fileURLToPath } from 'url';
import isDev from 'electron-is-dev';
const dirname = path.join(path.dirname(fileURLToPath(import.meta.url)));
const envPaths = EnvPaths('plebbit', { suffix: false });
// PLEB, always run plebbit rpc on this port so all clients can use it
const port = 9138;
const defaultPlebbitOptions = {
// find the user's OS data path
dataPath: !isDev ? envPaths.data : path.join(dirname, '..', '.plebbit'),
kuboRpcClientsOptions: [{ url: 'http://localhost:50019/api/v0' }],
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
};
// generate plebbit rpc auth key if doesn't exist
const plebbitRpcAuthKeyPath = path.join(defaultPlebbitOptions.dataPath, 'auth-key');
let plebbitRpcAuthKey;
try {
plebbitRpcAuthKey = fs.readFileSync(plebbitRpcAuthKeyPath, 'utf8');
} catch (e) {
plebbitRpcAuthKey = randomBytes(32).toString('base64').replace(/[/+=]/g, '').substring(0, 40);
fs.ensureFileSync(plebbitRpcAuthKeyPath);
fs.writeFileSync(plebbitRpcAuthKeyPath, plebbitRpcAuthKey);
}
const startPlebbitRpcAutoRestart = async () => {
let pendingStart = false;
const start = async () => {
if (pendingStart) {
return;
}
pendingStart = true;
try {
const started = await tcpPortUsed.check(port, '127.0.0.1');
if (!started) {
const plebbitWebSocketServer = await PlebbitRpc.PlebbitWsServer({ port, plebbitOptions: defaultPlebbitOptions, authKey: plebbitRpcAuthKey });
plebbitWebSocketServer.on('error', (e) => console.log('plebbit rpc error', e));
console.log(`plebbit rpc: listening on ws://localhost:${port} (local connections only)`);
console.log(`plebbit rpc: listening on ws://localhost:${port}/${plebbitRpcAuthKey} (secret auth key for remote connections)`);
plebbitWebSocketServer.ws.on('connection', (socket, request) => {
console.log('plebbit rpc: new connection');
// debug raw JSON RPC messages in console
if (isDev) {
socket.on('message', (message) => console.log(`plebbit rpc: ${message.toString()}`));
}
});
}
} catch (e) {
console.log('failed starting plebbit rpc server', e);
}
pendingStart = false;
};
// retry starting the plebbit rpc server every 1 second,
// in case it was started by another client that shut down and shut down the server with it
start();
setInterval(() => {
start();
}, 1000);
};
startPlebbitRpcAutoRestart();
+1 -1
View File
@@ -20,7 +20,7 @@ const config = {
/^\/\.github$/,
/^\/scripts$/,
/^\/\.git/,
/^\/\.plebbit$/,
/^\/\.pkc$/,
/^\/out$/,
/^\/dist$/,
/^\/squashfs-root$/,
+1 -1
View File
@@ -37,7 +37,7 @@
],
"ignoreIssues": {
// This import is intentionally satisfied transitively through bitsocial-react-hooks.
"electron/start-plebbit-rpc.js": ["unlisted"],
"electron/start-pkc-rpc.js": ["unlisted"],
// Knip falsely infers v8 coverage for Vitest config even though this repo uses Istanbul.
"vitest.config.ts": ["unlisted"]
}
+2 -2
View File
@@ -79,11 +79,11 @@
"electron:build:mac:x64": "corepack yarn build && corepack yarn build:preload && corepack yarn electron:prepare-package && electron-forge make --platform=darwin --arch=x64",
"electron:build:mac:arm64": "corepack yarn build && corepack yarn build:preload && corepack yarn electron:prepare-package && electron-forge make --platform=darwin --arch=arm64",
"electron:before": "corepack yarn electron:before:delete-data",
"electron:before:delete-data": "node -e \"fs.rmSync('.plebbit', { recursive: true, force: true })\"",
"electron:before:delete-data": "node scripts/clear-local-pkc-data.mjs",
"test:update:e2e:electron": "node scripts/run-electron-app-update-e2e.mjs",
"test:update:e2e:android": "node scripts/run-android-app-update-e2e.mjs",
"test:thread-auto-update:e2e": "node scripts/run-thread-auto-update-e2e.mjs --start-dev",
"android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'",
"android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/bitsocial-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'",
"knip": "knip --production --include dependencies,unlisted,binaries --no-progress",
"knip:full": "knip --no-progress --no-exit-code",
"lint": "oxlint src/**/*.{js,ts,tsx}",
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "ترتيب الدفع",
"creation_date": "تاريخ الإنشاء",
"sort_by": "ترتيب حسب",
"subplebbit_offline_info": "قد يكون هذا المنتدى غير متصل وقد تفشل عملية النشر",
"posts_last_synced_info": "تمت مزامنة المشاركات آخر مرة {{time}}، قد يكون هذا المنتدى غير متصل وقد تفشل عملية النشر.",
"no_threads": "لا توجد مواضيع",
"hidden": "مخفي",
@@ -324,5 +323,6 @@
"download": "تحميل",
"checking_for_updates": "جاري التحقق من التحديثات...",
"new_version_found": "تم العثور على إصدار جديد",
"site_legal_meta_license_text": "5chan هو برنامج مجاني ومفتوح المصدر بموجب GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan هو برنامج مجاني ومفتوح المصدر بموجب GPL-3.0-or-later.",
"community_offline_info": "قد يكون هذا المنتدى غير متصل وقد تفشل عملية النشر"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "বাম অর্ডার",
"creation_date": "তৈরি তারিখ",
"sort_by": "দ্বারা বাছাই করুন",
"subplebbit_offline_info": "এই বোর্ডটি অফলাইনে থাকতে পারে এবং প্রকাশনা ব্যর্থ হতে পারে",
"posts_last_synced_info": "পোস্টগুলি শেষবার {{time}} সিঙ্ক করা হয়েছে, এই বোর্ডটি অফলাইনে থাকতে পারে এবং প্রকাশনা ব্যর্থ হতে পারে।",
"no_threads": "কোন থ্রেড নেই",
"hidden": "লুকানো",
@@ -324,5 +323,6 @@
"download": "ডাউনলোড",
"checking_for_updates": "আপডেট পরীক্ষা করা হচ্ছে...",
"new_version_found": "নতুন সংস্করণ পাওয়া গেছে",
"site_legal_meta_license_text": "5chan হলো GPL-3.0-or-later-এর অধীনে মুক্ত এবং ওপেন-সোর্স সফটওয়্যার।"
"site_legal_meta_license_text": "5chan হলো GPL-3.0-or-later-এর অধীনে মুক্ত এবং ওপেন-সোর্স সফটওয়্যার।",
"community_offline_info": "এই বোর্ডটি অফলাইনে থাকতে পারে এবং প্রকাশনা ব্যর্থ হতে পারে"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Pořadí bump",
"creation_date": "Datum vytvoření",
"sort_by": "Řadit podle",
"subplebbit_offline_info": "Toto fórum může být offline a publikování může selhat",
"posts_last_synced_info": "Příspěvky naposledy synchronizovány {{time}}, toto fórum může být offline a publikování může selhat.",
"no_threads": "Žádná témata",
"hidden": "skrytý",
@@ -324,5 +323,6 @@
"download": "stáhnout",
"checking_for_updates": "Kontroluji aktualizace...",
"new_version_found": "nalezena nová verze",
"site_legal_meta_license_text": "5chan je svobodný software s otevřeným zdrojovým kódem pod licencí GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan je svobodný software s otevřeným zdrojovým kódem pod licencí GPL-3.0-or-later.",
"community_offline_info": "Toto fórum může být offline a publikování může selhat"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump rækkefølge",
"creation_date": "Oprettelsesdato",
"sort_by": "Sorter efter",
"subplebbit_offline_info": "Dette board kan være offline og publicering kan mislykkes",
"posts_last_synced_info": "Indlæg sidst synkroniseret {{time}}, dette board kan være offline og publicering kan mislykkes.",
"no_threads": "Ingen tråde",
"hidden": "skjult",
@@ -324,5 +323,6 @@
"download": "hent",
"checking_for_updates": "Tjekker for opdateringer...",
"new_version_found": "ny version fundet",
"site_legal_meta_license_text": "5chan er fri software med åben kildekode under GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan er fri software med åben kildekode under GPL-3.0-or-later.",
"community_offline_info": "Dette board kan være offline og publicering kan mislykkes"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump-Reihenfolge",
"creation_date": "Erstellungsdatum",
"sort_by": "Sortieren nach",
"subplebbit_offline_info": "Dieses Board könnte offline sein und die Veröffentlichung könnte fehlschlagen",
"posts_last_synced_info": "Beiträge zuletzt synchronisiert {{time}}, dieses Board könnte offline sein und die Veröffentlichung könnte fehlschlagen.",
"no_threads": "Keine Threads",
"hidden": "versteckt",
@@ -324,5 +323,6 @@
"download": "herunterladen",
"checking_for_updates": "Suche nach Updates...",
"new_version_found": "neue Version gefunden",
"site_legal_meta_license_text": "5chan ist freie Open-Source-Software unter GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan ist freie Open-Source-Software unter GPL-3.0-or-later.",
"community_offline_info": "Dieses Board könnte offline sein und die Veröffentlichung könnte fehlschlagen"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Τάξη Bump",
"creation_date": "Ημερομηνία δημιουργίας",
"sort_by": "Ταξινόμηση κατά",
"subplebbit_offline_info": "Αυτό το φόρουμ μπορεί να είναι εκτός σύνδεσης και η δημοσίευση μπορεί να αποτύχει",
"posts_last_synced_info": "Οι δημοσιεύσεις συγχρονίστηκαν τελευταία {{time}}, αυτό το φόρουμ μπορεί να είναι εκτός σύνδεσης και η δημοσίευση μπορεί να αποτύχει.",
"no_threads": "Δεν υπάρχουν θέματα",
"hidden": "κρυφός",
@@ -324,5 +323,6 @@
"download": "λήψη",
"checking_for_updates": "Έλεγχος για ενημερώσεις...",
"new_version_found": "βρέθηκε νέα έκδοση",
"site_legal_meta_license_text": "Το 5chan είναι ελεύθερο λογισμικό ανοιχτού κώδικα υπό την GPL-3.0-or-later."
"site_legal_meta_license_text": "Το 5chan είναι ελεύθερο λογισμικό ανοιχτού κώδικα υπό την GPL-3.0-or-later.",
"community_offline_info": "Αυτό το φόρουμ μπορεί να είναι εκτός σύνδεσης και η δημοσίευση μπορεί να αποτύχει"
}
-1
View File
@@ -138,7 +138,6 @@
"bump_order": "Bump order",
"creation_date": "Creation date",
"sort_by": "Sort by",
"subplebbit_offline_info": "This board might be offline and publishing might fail",
"posts_last_synced_info": "Posts last synced {{time}}, this board might be offline and publishing might fail.",
"no_threads": "No threads",
"hidden": "hidden",
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Orden de Bump",
"creation_date": "Fecha de creación",
"sort_by": "Ordenar por",
"subplebbit_offline_info": "Este tablero podría estar offline y la publicación podría fallar",
"posts_last_synced_info": "Publicaciones sincronizadas por última vez {{time}}, este tablero podría estar offline y la publicación podría fallar.",
"no_threads": "No hay hilos",
"hidden": "oculto",
@@ -324,5 +323,6 @@
"download": "descargar",
"checking_for_updates": "Buscando actualizaciones...",
"new_version_found": "nueva versión encontrada",
"site_legal_meta_license_text": "5chan es software libre y de código abierto bajo GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan es software libre y de código abierto bajo GPL-3.0-or-later.",
"community_offline_info": "Este tablero podría estar offline y la publicación podría fallar"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "سفارش بامپ",
"creation_date": "تاریخ ایجاد",
"sort_by": "مرتب‌سازی بر اساس",
"subplebbit_offline_info": "این انجمن ممکن است آفلاین باشد و انتشار ممکن است با شکست مواجه شود",
"posts_last_synced_info": "پست‌ها آخرین بار {{time}} همگام‌سازی شدند، این انجمن ممکن است آفلاین باشد و انتشار ممکن است با شکست مواجه شود.",
"no_threads": "هیچ موضوعی وجود ندارد",
"hidden": "مخفی",
@@ -324,5 +323,6 @@
"download": "بارگیری",
"checking_for_updates": "در حال بررسی به‌روزرسانی‌ها...",
"new_version_found": "نسخه جدید یافت شد",
"site_legal_meta_license_text": "5chan نرم‌افزار آزاد و متن‌باز تحت GPL-3.0-or-later است."
"site_legal_meta_license_text": "5chan نرم‌افزار آزاد و متن‌باز تحت GPL-3.0-or-later است.",
"community_offline_info": "این انجمن ممکن است آفلاین باشد و انتشار ممکن است با شکست مواجه شود"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump-järjestys",
"creation_date": "Luomispäivämäärä",
"sort_by": "Järjestä",
"subplebbit_offline_info": "Tämä taulu voi olla offline-tilassa ja julkaisu voi epäonnistua",
"posts_last_synced_info": "Viestit viimeksi synkronoitu {{time}}, tämä taulu voi olla offline-tilassa ja julkaisu voi epäonnistua.",
"no_threads": "Ei ketjuja",
"hidden": "piilotettu",
@@ -324,5 +323,6 @@
"download": "lataa",
"checking_for_updates": "Tarkistetaan päivityksiä...",
"new_version_found": "uusi versio löytyi",
"site_legal_meta_license_text": "5chan on vapaata ja avoimen lähdekoodin ohjelmistoa GPL-3.0-or-later -lisenssillä."
"site_legal_meta_license_text": "5chan on vapaata ja avoimen lähdekoodin ohjelmistoa GPL-3.0-or-later -lisenssillä.",
"community_offline_info": "Tämä taulu voi olla offline-tilassa ja julkaisu voi epäonnistua"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump order",
"creation_date": "Petsa ng Paglikha",
"sort_by": "Ayusin ayon sa",
"subplebbit_offline_info": "Maaaring offline ang board na ito at maaaring mabigo ang pag-publish",
"posts_last_synced_info": "Huling na-sync ang mga post {{time}}, maaaring offline ang board na ito at maaaring mabigo ang pag-publish.",
"no_threads": "Walang mga thread",
"hidden": "nakatago",
@@ -324,5 +323,6 @@
"download": "i-download",
"checking_for_updates": "Sinusuri ang mga update...",
"new_version_found": "may bagong bersyon na nahanap",
"site_legal_meta_license_text": "Ang 5chan ay libre at open-source na software sa ilalim ng GPL-3.0-or-later."
"site_legal_meta_license_text": "Ang 5chan ay libre at open-source na software sa ilalim ng GPL-3.0-or-later.",
"community_offline_info": "Maaaring offline ang board na ito at maaaring mabigo ang pag-publish"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Ordre de Bump",
"creation_date": "Date de création",
"sort_by": "Trier par",
"subplebbit_offline_info": "Ce forum pourrait être hors ligne et la publication pourrait échouer",
"posts_last_synced_info": "Publications synchronisées pour la dernière fois {{time}}, ce forum pourrait être hors ligne et la publication pourrait échouer.",
"no_threads": "Pas de fils",
"hidden": "caché",
@@ -324,5 +323,6 @@
"download": "télécharger",
"checking_for_updates": "Vérification des mises à jour...",
"new_version_found": "nouvelle version trouvée",
"site_legal_meta_license_text": "5chan est un logiciel libre et open source sous GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan est un logiciel libre et open source sous GPL-3.0-or-later.",
"community_offline_info": "Ce forum pourrait être hors ligne et la publication pourrait échouer"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "סדר הפעם",
"creation_date": "תאריך יצירה",
"sort_by": "מיין לפי",
"subplebbit_offline_info": "פורום זה עשוי להיות לא מקוון והפרסום עלול להיכשל",
"posts_last_synced_info": "פוסטים סונכרנו לאחרונה {{time}}, פורום זה עשוי להיות לא מקוון והפרסום עלול להיכשל.",
"no_threads": "אין נושאים",
"hidden": "מוסתר",
@@ -324,5 +323,6 @@
"download": "הורדה",
"checking_for_updates": "בודק עדכונים...",
"new_version_found": "נמצאה גרסה חדשה",
"site_legal_meta_license_text": "5chan היא תוכנה חופשית וקוד פתוח תחת GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan היא תוכנה חופשית וקוד פתוח תחת GPL-3.0-or-later.",
"community_offline_info": "פורום זה עשוי להיות לא מקוון והפרסום עלול להיכשל"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "बंप आदेश",
"creation_date": "निर्माण तिथि",
"sort_by": "क्रमबद्ध करें द्वारा",
"subplebbit_offline_info": "यह बोर्ड ऑफ़लाइन हो सकता है और प्रकाशन विफल हो सकता है",
"posts_last_synced_info": "पोस्ट अंतिम बार {{time}} सिंक की गईं, यह बोर्ड ऑफ़लाइन हो सकता है और प्रकाशन विफल हो सकता है।",
"no_threads": "कोई थ्रेड नहीं है",
"hidden": "छिपा हुआ",
@@ -324,5 +323,6 @@
"download": "डाउनलोड",
"checking_for_updates": "अपडेट जाँच रहा है...",
"new_version_found": "नया संस्करण मिला",
"site_legal_meta_license_text": "5chan GPL-3.0-or-later के तहत मुक्त और ओपन-सोर्स सॉफ़्टवेयर है।"
"site_legal_meta_license_text": "5chan GPL-3.0-or-later के तहत मुक्त और ओपन-सोर्स सॉफ़्टवेयर है।",
"community_offline_info": "यह बोर्ड ऑफ़लाइन हो सकता है और प्रकाशन विफल हो सकता है"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump sorrend",
"creation_date": "Létrehozás dátuma",
"sort_by": "Rendezés",
"subplebbit_offline_info": "Ez a fórum offline lehet és a közzététel sikertelen lehet",
"posts_last_synced_info": "Hozzászólások utoljára szinkronizálva {{time}}, ez a fórum offline lehet és a közzététel sikertelen lehet.",
"no_threads": "Nincsenek témák",
"hidden": "rejtett",
@@ -324,5 +323,6 @@
"download": "letöltés",
"checking_for_updates": "Frissítések keresése...",
"new_version_found": "új verzió található",
"site_legal_meta_license_text": "Az 5chan szabad és nyílt forráskódú szoftver a GPL-3.0-or-later alatt."
"site_legal_meta_license_text": "Az 5chan szabad és nyílt forráskódú szoftver a GPL-3.0-or-later alatt.",
"community_offline_info": "Ez a fórum offline lehet és a közzététel sikertelen lehet"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Urutan Bump",
"creation_date": "Tanggal pembuatan",
"sort_by": "Urutkan berdasarkan",
"subplebbit_offline_info": "Papan ini mungkin offline dan publikasi mungkin gagal",
"posts_last_synced_info": "Posting terakhir disinkronkan {{time}}, papan ini mungkin offline dan publikasi mungkin gagal.",
"no_threads": "Tidak ada thread",
"hidden": "tersembunyi",
@@ -324,5 +323,6 @@
"download": "unduh",
"checking_for_updates": "Memeriksa pembaruan...",
"new_version_found": "versi baru ditemukan",
"site_legal_meta_license_text": "5chan adalah perangkat lunak bebas dan open-source di bawah GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan adalah perangkat lunak bebas dan open-source di bawah GPL-3.0-or-later.",
"community_offline_info": "Papan ini mungkin offline dan publikasi mungkin gagal"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Ordine di Bump",
"creation_date": "Data di creazione",
"sort_by": "Ordina per",
"subplebbit_offline_info": "Questa bacheca potrebbe essere offline e la pubblicazione potrebbe fallire",
"posts_last_synced_info": "Post sincronizzati l'ultima volta {{time}}, questa bacheca potrebbe essere offline e la pubblicazione potrebbe fallire.",
"no_threads": "Nessun thread",
"hidden": "nascosto",
@@ -324,5 +323,6 @@
"download": "scarica",
"checking_for_updates": "Controllo aggiornamenti...",
"new_version_found": "nuova versione trovata",
"site_legal_meta_license_text": "5chan è software libero e open-source sotto GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan è software libero e open-source sotto GPL-3.0-or-later.",
"community_offline_info": "Questa bacheca potrebbe essere offline e la pubblicazione potrebbe fallire"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "バンプ順",
"creation_date": "作成日",
"sort_by": "並べ替え",
"subplebbit_offline_info": "このボードはオフラインの可能性があり、公開が失敗する可能性があります",
"posts_last_synced_info": "投稿は最後に {{time}} に同期されました。このボードはオフラインの可能性があり、公開が失敗する可能性があります。",
"no_threads": "スレッドはありません",
"hidden": "隠し",
@@ -324,5 +323,6 @@
"download": "ダウンロード",
"checking_for_updates": "更新を確認しています...",
"new_version_found": "新しいバージョンが見つかりました",
"site_legal_meta_license_text": "5chanはGPL-3.0-or-laterのもとで提供される自由かつオープンソースのソフトウェアです。"
"site_legal_meta_license_text": "5chanはGPL-3.0-or-laterのもとで提供される自由かつオープンソースのソフトウェアです。",
"community_offline_info": "このボードはオフラインの可能性があり、公開が失敗する可能性があります"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "버프 순서",
"creation_date": "작성 날짜",
"sort_by": "정렬 기준",
"subplebbit_offline_info": "이 게시판이 오프라인일 수 있으며 게시가 실패할 수 있습니다",
"posts_last_synced_info": "게시물이 마지막으로 {{time}}에 동기화되었습니다. 이 게시판이 오프라인일 수 있으며 게시가 실패할 수 있습니다.",
"no_threads": "스레드가 없습니다",
"hidden": "숨겨진",
@@ -324,5 +323,6 @@
"download": "다운로드",
"checking_for_updates": "업데이트 확인 중...",
"new_version_found": "새 버전을 찾았습니다",
"site_legal_meta_license_text": "5chan은 GPL-3.0-or-later에 따른 자유 오픈 소스 소프트웨어입니다."
"site_legal_meta_license_text": "5chan은 GPL-3.0-or-later에 따른 자유 오픈 소스 소프트웨어입니다.",
"community_offline_info": "이 게시판이 오프라인일 수 있으며 게시가 실패할 수 있습니다"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "बंप क्रम",
"creation_date": "निर्माण तारीख",
"sort_by": "क्रमवार करा",
"subplebbit_offline_info": "हे बोर्ड ऑफलाइन असू शकते आणि प्रकाशन अयशस्वी होऊ शकते",
"posts_last_synced_info": "पोस्ट शेवटी {{time}} सिंक केल्या गेल्या, हे बोर्ड ऑफलाइन असू शकते आणि प्रकाशन अयशस्वी होऊ शकते.",
"no_threads": "कोणतेही थ्रेड्स नाहीत",
"hidden": "लपवलेले",
@@ -324,5 +323,6 @@
"download": "डाउनलोड",
"checking_for_updates": "अपडेट तपासत आहे...",
"new_version_found": "नवीन आवृत्ती सापडली",
"site_legal_meta_license_text": "5chan हे GPL-3.0-or-later अंतर्गत मुक्त आणि ओपन-सोर्स सॉफ्टवेअर आहे."
"site_legal_meta_license_text": "5chan हे GPL-3.0-or-later अंतर्गत मुक्त आणि ओपन-सोर्स सॉफ्टवेअर आहे.",
"community_offline_info": "हे बोर्ड ऑफलाइन असू शकते आणि प्रकाशन अयशस्वी होऊ शकते"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump-volgorde",
"creation_date": "Creatiedatum",
"sort_by": "Sorteer op",
"subplebbit_offline_info": "Dit bord kan offline zijn en publiceren kan mislukken",
"posts_last_synced_info": "Berichten voor het laatst gesynchroniseerd {{time}}, dit bord kan offline zijn en publiceren kan mislukken.",
"no_threads": "Geen threads",
"hidden": "verborgen",
@@ -324,5 +323,6 @@
"download": "downloaden",
"checking_for_updates": "Controleren op updates...",
"new_version_found": "nieuwe versie gevonden",
"site_legal_meta_license_text": "5chan is vrije en open-source software onder GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan is vrije en open-source software onder GPL-3.0-or-later.",
"community_offline_info": "Dit bord kan offline zijn en publiceren kan mislukken"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump rekkefølge",
"creation_date": "Opprettelsesdato",
"sort_by": "Sorter etter",
"subplebbit_offline_info": "Dette brettet kan være offline og publisering kan mislykkes",
"posts_last_synced_info": "Innlegg sist synkronisert {{time}}, dette brettet kan være offline og publisering kan mislykkes.",
"no_threads": "Ingen tråder",
"hidden": "skjult",
@@ -324,5 +323,6 @@
"download": "last ned",
"checking_for_updates": "Sjekker etter oppdateringer...",
"new_version_found": "ny versjon funnet",
"site_legal_meta_license_text": "5chan er fri programvare med åpen kildekode under GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan er fri programvare med åpen kildekode under GPL-3.0-or-later.",
"community_offline_info": "Dette brettet kan være offline og publisering kan mislykkes"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Kolejność Bump",
"creation_date": "Data utworzenia",
"sort_by": "Sortuj według",
"subplebbit_offline_info": "To forum może być offline i publikacja może się nie powieść",
"posts_last_synced_info": "Posty ostatnio zsynchronizowane {{time}}, to forum może być offline i publikacja może się nie powieść.",
"no_threads": "Brak wątków",
"hidden": "ukryty",
@@ -324,5 +323,6 @@
"download": "pobierz",
"checking_for_updates": "Sprawdzanie aktualizacji...",
"new_version_found": "znaleziono nową wersję",
"site_legal_meta_license_text": "5chan to wolne i otwarte oprogramowanie na licencji GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan to wolne i otwarte oprogramowanie na licencji GPL-3.0-or-later.",
"community_offline_info": "To forum może być offline i publikacja może się nie powieść"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Ordem de Bump",
"creation_date": "Data de criação",
"sort_by": "Ordenar por",
"subplebbit_offline_info": "Este fórum pode estar offline e a publicação pode falhar",
"posts_last_synced_info": "Publicações sincronizadas pela última vez {{time}}, este fórum pode estar offline e a publicação pode falhar.",
"no_threads": "Sem tópicos",
"hidden": "escondido",
@@ -324,5 +323,6 @@
"download": "baixar",
"checking_for_updates": "Verificando atualizações...",
"new_version_found": "nova versão encontrada",
"site_legal_meta_license_text": "5chan é software livre e de código aberto sob GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan é software livre e de código aberto sob GPL-3.0-or-later.",
"community_offline_info": "Este fórum pode estar offline e a publicação pode falhar"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Ordinea Bump",
"creation_date": "Data de creare",
"sort_by": "Sortează după",
"subplebbit_offline_info": "Acest forum poate fi offline și publicarea poate eșua",
"posts_last_synced_info": "Postări sincronizate ultima dată {{time}}, acest forum poate fi offline și publicarea poate eșua.",
"no_threads": "Nu sunt subiecte",
"hidden": "ascuns",
@@ -324,5 +323,6 @@
"download": "descarcă",
"checking_for_updates": "Se verifică actualizările...",
"new_version_found": "versiune nouă găsită",
"site_legal_meta_license_text": "5chan este software liber și open-source sub GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan este software liber și open-source sub GPL-3.0-or-later.",
"community_offline_info": "Acest forum poate fi offline și publicarea poate eșua"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Порядок Bump",
"creation_date": "Дата создания",
"sort_by": "Сортировать по",
"subplebbit_offline_info": "Этот форум может быть офлайн, и публикация может не удаться",
"posts_last_synced_info": "Посты последний раз синхронизированы {{time}}, этот форум может быть офлайн, и публикация может не удаться.",
"no_threads": "Нет тем",
"hidden": "скрытый",
@@ -324,5 +323,6 @@
"download": "скачать",
"checking_for_updates": "Проверка обновлений...",
"new_version_found": "найдена новая версия",
"site_legal_meta_license_text": "5chan — свободное программное обеспечение с открытым исходным кодом под GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan — свободное программное обеспечение с открытым исходным кодом под GPL-3.0-or-later.",
"community_offline_info": "Этот форум может быть офлайн, и публикация может не удаться"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Renditja e Bump",
"creation_date": "Data e krijimit",
"sort_by": "Rendit sipas",
"subplebbit_offline_info": "Ky forum mund të jetë offline dhe publikimi mund të dështojë",
"posts_last_synced_info": "Postimet u sinkronizuan për herë të fundit {{time}}, ky forum mund të jetë offline dhe publikimi mund të dështojë.",
"no_threads": "Nuk ka tema",
"hidden": "i fshehur",
@@ -324,5 +323,6 @@
"download": "shkarko",
"checking_for_updates": "Duke kontrolluar për përditësime...",
"new_version_found": "u gjet version i ri",
"site_legal_meta_license_text": "5chan është softuer i lirë dhe me burim të hapur nën GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan është softuer i lirë dhe me burim të hapur nën GPL-3.0-or-later.",
"community_offline_info": "Ky forum mund të jetë offline dhe publikimi mund të dështojë"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump ordning",
"creation_date": "Skapelsedatum",
"sort_by": "Sortera efter",
"subplebbit_offline_info": "Detta forum kan vara offline och publicering kan misslyckas",
"posts_last_synced_info": "Inlägg senast synkroniserade {{time}}, detta forum kan vara offline och publicering kan misslyckas.",
"no_threads": "Inga trådar",
"hidden": "dold",
@@ -324,5 +323,6 @@
"download": "ladda ner",
"checking_for_updates": "Söker efter uppdateringar...",
"new_version_found": "ny version hittades",
"site_legal_meta_license_text": "5chan är fri programvara med öppen källkod under GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan är fri programvara med öppen källkod under GPL-3.0-or-later.",
"community_offline_info": "Detta forum kan vara offline och publicering kan misslyckas"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "బంప్ క్రమం",
"creation_date": "సృష్టి తేదీ",
"sort_by": "గట్టిగా",
"subplebbit_offline_info": "ఈ బోర్డ్ ఆఫ్‌లైన్‌లో ఉండవచ్చు మరియు ప్రచురణ విఫలమవచ్చు",
"posts_last_synced_info": "పోస్ట్‌లు చివరిగా {{time}} సమకాలీకరించబడ్డాయి, ఈ బోర్డ్ ఆఫ్‌లైన్‌లో ఉండవచ్చు మరియు ప్రచురణ విఫలమవచ్చు.",
"no_threads": "ఏ థ్రెడ్‌లు లేవు",
"hidden": "దాగిన",
@@ -324,5 +323,6 @@
"download": "డౌన్లోడ్",
"checking_for_updates": "నవీకరణలను తనిఖీ చేస్తోంది...",
"new_version_found": "కొత్త వెర్షన్ కనుగొనబడింది",
"site_legal_meta_license_text": "5chan GPL-3.0-or-later కింద ఉచిత మరియు ఓపెన్-సోర్స్ సాఫ్ట్‌వేర్."
"site_legal_meta_license_text": "5chan GPL-3.0-or-later కింద ఉచిత మరియు ఓపెన్-సోర్స్ సాఫ్ట్‌వేర్.",
"community_offline_info": "ఈ బోర్డ్ ఆఫ్‌లైన్‌లో ఉండవచ్చు మరియు ప్రచురణ విఫలమవచ్చు"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "ลำดับของ Bump",
"creation_date": "วันที่สร้าง",
"sort_by": "เรียงลำดับตาม",
"subplebbit_offline_info": "บอร์ดนี้อาจออฟไลน์และการเผยแพร่อาจล้มเหลว",
"posts_last_synced_info": "โพสต์ซิงค์ครั้งล่าสุด {{time}} บอร์ดนี้อาจออฟไลน์และการเผยแพร่อาจล้มเหลว",
"no_threads": "ไม่มีกระทู้",
"hidden": "ซ่อน",
@@ -324,5 +323,6 @@
"download": "ดาวน์โหลด",
"checking_for_updates": "กำลังตรวจสอบการอัปเดต...",
"new_version_found": "พบเวอร์ชันใหม่",
"site_legal_meta_license_text": "5chan เป็นซอฟต์แวร์เสรีและโอเพนซอร์สภายใต้ GPL-3.0-or-later"
"site_legal_meta_license_text": "5chan เป็นซอฟต์แวร์เสรีและโอเพนซอร์สภายใต้ GPL-3.0-or-later",
"community_offline_info": "บอร์ดนี้อาจออฟไลน์และการเผยแพร่อาจล้มเหลว"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Bump sırası",
"creation_date": "Oluşturulma tarihi",
"sort_by": "Sırala",
"subplebbit_offline_info": "Bu forum çevrimdışı olabilir ve yayınlama başarısız olabilir",
"posts_last_synced_info": "Gönderiler son olarak {{time}} senkronize edildi, bu forum çevrimdışı olabilir ve yayınlama başarısız olabilir.",
"no_threads": "Hiç gönderi yok",
"hidden": "gizli",
@@ -324,5 +323,6 @@
"download": "indir",
"checking_for_updates": "Güncellemeler kontrol ediliyor...",
"new_version_found": "yeni sürüm bulundu",
"site_legal_meta_license_text": "5chan, GPL-3.0-or-later kapsamında özgür ve açık kaynaklı yazılımdır."
"site_legal_meta_license_text": "5chan, GPL-3.0-or-later kapsamında özgür ve açık kaynaklı yazılımdır.",
"community_offline_info": "Bu forum çevrimdışı olabilir ve yayınlama başarısız olabilir"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Порядок Bump",
"creation_date": "Дата створення",
"sort_by": "Сортувати за",
"subplebbit_offline_info": "Цей форум може бути офлайн, і публікація може не вдатися",
"posts_last_synced_info": "Останній раз пости синхронізовано {{time}}, цей форум може бути офлайн, і публікація може не вдатися.",
"no_threads": "Немає тем",
"hidden": "прихований",
@@ -324,5 +323,6 @@
"download": "завантажити",
"checking_for_updates": "Перевірка оновлень...",
"new_version_found": "знайдено нову версію",
"site_legal_meta_license_text": "5chan — це вільне програмне забезпечення з відкритим кодом за GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan — це вільне програмне забезпечення з відкритим кодом за GPL-3.0-or-later.",
"community_offline_info": "Цей форум може бути офлайн, і публікація може не вдатися"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "بمپ آرڈر",
"creation_date": "تخلیق کی تاریخ",
"sort_by": "کے ذریعہ سارٹ کریں",
"subplebbit_offline_info": "یہ بورڈ آف لائن ہو سکتا ہے اور اشاعت ناکام ہو سکتی ہے",
"posts_last_synced_info": "پوسٹس آخری بار {{time}} پر مطابقت پذیر ہوئیں، یہ بورڈ آف لائن ہو سکتا ہے اور اشاعت ناکام ہو سکتی ہے۔",
"no_threads": "کوئی تھریڈز نہیں ہیں",
"hidden": "چھپا ہوا",
@@ -324,5 +323,6 @@
"download": "ڈاؤن لوڈ",
"checking_for_updates": "اپڈیٹس چیک کیے جا رہے ہیں...",
"new_version_found": "نیا ورژن ملا",
"site_legal_meta_license_text": "5chan GPL-3.0-or-later کے تحت آزاد اور اوپن سورس سافٹ ویئر ہے۔"
"site_legal_meta_license_text": "5chan GPL-3.0-or-later کے تحت آزاد اور اوپن سورس سافٹ ویئر ہے۔",
"community_offline_info": "یہ بورڈ آف لائن ہو سکتا ہے اور اشاعت ناکام ہو سکتی ہے"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "Thứ tự Bump",
"creation_date": "Ngày tạo",
"sort_by": "Sắp xếp theo",
"subplebbit_offline_info": "Bảng này có thể đang offline và việc xuất bản có thể thất bại",
"posts_last_synced_info": "Bài đăng được đồng bộ lần cuối {{time}}, bảng này có thể đang offline và việc xuất bản có thể thất bại.",
"no_threads": "Không có chủ đề",
"hidden": "ẩn",
@@ -324,5 +323,6 @@
"download": "tải xuống",
"checking_for_updates": "Đang kiểm tra cập nhật...",
"new_version_found": "đã tìm thấy phiên bản mới",
"site_legal_meta_license_text": "5chan là phần mềm miễn phí và mã nguồn mở theo GPL-3.0-or-later."
"site_legal_meta_license_text": "5chan là phần mềm miễn phí và mã nguồn mở theo GPL-3.0-or-later.",
"community_offline_info": "Bảng này có thể đang offline và việc xuất bản có thể thất bại"
}
+2 -2
View File
@@ -125,7 +125,6 @@
"bump_order": "顶贴顺序",
"creation_date": "创造日期",
"sort_by": "排序方式",
"subplebbit_offline_info": "此看板可能处于离线状态,发布可能会失败",
"posts_last_synced_info": "帖子最后同步于 {{time}},此看板可能处于离线状态,发布可能会失败。",
"no_threads": "没有帖子",
"hidden": "隐藏",
@@ -324,5 +323,6 @@
"download": "下载",
"checking_for_updates": "正在检查更新...",
"new_version_found": "发现新版本",
"site_legal_meta_license_text": "5chan 是采用 GPL-3.0-or-later 的自由开源软件。"
"site_legal_meta_license_text": "5chan 是采用 GPL-3.0-or-later 的自由开源软件。",
"community_offline_info": "此看板可能处于离线状态,发布可能会失败"
}
+11
View File
@@ -0,0 +1,11 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { getDevPkcDataPath } from '../electron/pkc-paths.js';
const dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.join(dirname, '..');
const dataPath = getDevPkcDataPath(projectRoot);
console.log(`Removing local PKC data path: ${dataPath}`);
fs.rmSync(dataPath, { recursive: true, force: true });
+3 -3
View File
@@ -71,9 +71,9 @@ const main = async () => {
const versionMetadataPath = path.join(installedAppPath, 'Contents', 'Resources', 'app', 'build', 'version.json');
const sandboxHome = path.join(workspace, 'home');
const updaterDebugLogPath = path.join(workspace, 'app-updater.log');
const plebbitDataPath = path.join(sandboxHome, 'Library', 'Application Support', 'plebbit');
await fs.promises.mkdir(plebbitDataPath, { recursive: true });
await fs.promises.writeFile(path.join(plebbitDataPath, 'auth-key'), 'e2e-auth-key', 'utf8');
const pkcDataPath = path.join(sandboxHome, 'Library', 'Application Support', 'pkc');
await fs.promises.mkdir(pkcDataPath, { recursive: true });
await fs.promises.writeFile(path.join(pkcDataPath, 'auth-key'), 'e2e-auth-key', 'utf8');
const electronApp = await electron.launch({
executablePath: path.join(installedAppPath, 'Contents', 'MacOS', '5chan'),
+22 -23
View File
@@ -12,15 +12,15 @@ type ReplyModalShape = {
parentNumber: number | null;
scrollY: number;
showReplyModal: boolean;
subplebbitAddress: string | null;
communityAddress: string | null;
threadCid: string | null;
threadNumber: number | null;
};
const testState = vi.hoisted(() => ({
account: { author: { address: '0x123' } } as unknown,
accountComments: {} as Record<number, { subplebbitAddress?: string }>,
accountSubplebbitAddresses: [] as string[],
accountComments: {} as Record<number, { communityAddress?: string }>,
accountCommunityAddresses: [] as string[],
closeCreateBoardModalMock: vi.fn(),
directories: [
{ address: 'music-posting.eth', title: '/mu/ - Music', nsfw: false },
@@ -36,12 +36,12 @@ const testState = vi.hoisted(() => ({
parentNumber: null,
scrollY: 0,
showReplyModal: false,
subplebbitAddress: null,
communityAddress: null,
threadCid: null,
threadNumber: null,
} as ReplyModalShape,
resolvedSubplebbitAddress: undefined as string | undefined,
subplebbits: {} as Record<string, unknown>,
resolvedCommunityAddress: undefined as string | undefined,
communities: {} as Record<string, unknown>,
useThemeMock: vi.fn(),
}));
@@ -50,16 +50,15 @@ vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
useAccountComment: ({ commentIndex }: { commentIndex?: number }) => (typeof commentIndex === 'number' ? testState.accountComments[commentIndex] : undefined),
useCommunity: (options?: { communityAddress?: string; community?: { name?: string; publicKey?: string } }) => {
const communityAddress = options?.communityAddress ?? options?.community?.name ?? options?.community?.publicKey;
return communityAddress ? testState.subplebbits[communityAddress] : undefined;
return communityAddress ? testState.communities[communityAddress] : undefined;
},
useAccountCommunities: () => ({
accountCommunities: Object.fromEntries(testState.accountSubplebbitAddresses.map((address) => [address, { address }])),
accountCommunities: Object.fromEntries(testState.accountCommunityAddresses.map((address) => [address, { address }])),
}),
useSubplebbit: ({ subplebbitAddress }: { subplebbitAddress?: string }) => (subplebbitAddress ? testState.subplebbits[subplebbitAddress] : undefined),
}));
vi.mock('../hooks/use-account-subplebbit-addresses', () => ({
useAccountSubplebbitAddresses: () => testState.accountSubplebbitAddresses,
vi.mock('../hooks/use-account-community-addresses', () => ({
useAccountCommunityAddresses: () => testState.accountCommunityAddresses,
}));
vi.mock('../hooks/use-directories', () => ({
@@ -72,8 +71,8 @@ vi.mock('../hooks/use-is-mobile', () => ({
default: () => testState.isMobile,
}));
vi.mock('../hooks/use-resolved-subplebbit-address', () => ({
useResolvedSubplebbitAddress: () => testState.resolvedSubplebbitAddress,
vi.mock('../hooks/use-resolved-community-address', () => ({
useResolvedCommunityAddress: () => testState.resolvedCommunityAddress,
}));
vi.mock('../hooks/use-theme', () => ({
@@ -255,7 +254,7 @@ describe('App', () => {
latestLocation = '';
testState.account = { author: { address: '0x123' } };
testState.accountComments = {};
testState.accountSubplebbitAddresses = [];
testState.accountCommunityAddresses = [];
testState.isMobile = false;
testState.isSpecialEnabled = false;
testState.replyModalState = {
@@ -264,12 +263,12 @@ describe('App', () => {
parentNumber: null,
scrollY: 0,
showReplyModal: false,
subplebbitAddress: null,
communityAddress: null,
threadCid: null,
threadNumber: null,
} as ReplyModalShape;
testState.resolvedSubplebbitAddress = undefined;
testState.subplebbits = {};
testState.resolvedCommunityAddress = undefined;
testState.communities = {};
testState.useThemeMock.mockReset();
testState.closeCreateBoardModalMock.mockReset();
testState.initSnowMock.mockReset();
@@ -292,7 +291,7 @@ describe('App', () => {
parentNumber: 12,
scrollY: 32,
showReplyModal: true,
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
threadCid: 'thread-cid',
threadNumber: 99,
} as ReplyModalShape;
@@ -333,7 +332,7 @@ describe('App', () => {
});
it('allows the global mod queue only when the account moderates at least one board', async () => {
testState.accountSubplebbitAddresses = ['music-posting.eth'];
testState.accountCommunityAddresses = ['music-posting.eth'];
await renderApp('/mod/queue');
expect(container.querySelector('[data-testid="mod-queue-view"]')).toBeTruthy();
@@ -341,7 +340,7 @@ describe('App', () => {
act(() => root.unmount());
root = createRoot(container);
testState.accountSubplebbitAddresses = [];
testState.accountCommunityAddresses = [];
await renderApp('/mod/queue');
expect(latestLocation).toBe('/not-allowed');
@@ -373,8 +372,8 @@ describe('App', () => {
});
it('enforces board-scoped mod queue access by account role', async () => {
testState.resolvedSubplebbitAddress = 'music-posting.eth';
testState.subplebbits = {
testState.resolvedCommunityAddress = 'music-posting.eth';
testState.communities = {
'music-posting.eth': {
state: 'succeeded',
roles: {
@@ -389,7 +388,7 @@ describe('App', () => {
act(() => root.unmount());
root = createRoot(container);
testState.subplebbits = {
testState.communities = {
'music-posting.eth': {
state: 'succeeded',
roles: {
+5 -4
View File
@@ -15,9 +15,10 @@ import { useDirectories } from './hooks/use-directories';
import { useCommunityIdentifier } from './hooks/use-community-identifiers';
import { useResolvedCommunityAddress } from './hooks/use-resolved-community-address';
import useSafeAccountComment from './hooks/use-safe-account-comment';
import { getCommentCommunityAddress } from './lib/utils/comment-utils';
import {
getBoardPath,
getSubplebbitAddress,
getCommunityAddress,
isBoardModRoute,
isDirectoryBoard,
isArchiveRoute,
@@ -72,9 +73,9 @@ const BoardLayout = () => {
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const isInModView = isModView(location.pathname);
const directories = useDirectories();
const communityAddress = boardIdentifier ? getSubplebbitAddress(boardIdentifier, directories) : undefined;
const communityAddress = boardIdentifier ? getCommunityAddress(boardIdentifier, directories) : undefined;
const pendingPost = useSafeAccountComment({ commentIndex: accountCommentIndex });
const pendingPostCommunityAddress = pendingPost?.communityAddress || pendingPost?.subplebbitAddress;
const pendingPostCommunityAddress = getCommentCommunityAddress(pendingPost);
const { closeCreateBoardModal } = useCreateBoardModalStore();
const isOnPostRoute = isPostRoute(location.pathname);
const isOnPendingPostRoute = isPendingPostRoute(location.pathname);
@@ -178,7 +179,7 @@ const BoardLayout = () => {
const GlobalLayout = () => {
useTheme();
const { activeCid, parentNumber, threadNumber, threadCid, subplebbitAddress: activeCommunityAddress, closeModal, showReplyModal, scrollY } = useReplyModalStore();
const { activeCid, parentNumber, threadNumber, threadCid, communityAddress: activeCommunityAddress, closeModal, showReplyModal, scrollY } = useReplyModalStore();
const location = useLocation();
const isInSettingsView = location.pathname.endsWith('/settings');
@@ -37,7 +37,6 @@ type TestComment = {
>;
};
state?: string;
subplebbitAddress?: string;
thumbnailUrl?: string;
timestamp?: number;
updatedAt?: number;
@@ -417,13 +416,13 @@ const makeLegacyThread = (): TestComment => ({
number: 2,
parentCid: 'post-1',
postCid: 'post-1',
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
},
],
},
},
},
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
timestamp: 1_710_000_000,
});
@@ -456,7 +455,7 @@ describe('post community address compatibility', () => {
container.remove();
});
it('renders desktop multiboard posts with only subplebbitAddress and still fetches replies', async () => {
it('renders desktop multiboard posts with only communityAddress and still fetches replies', async () => {
await renderWithRoute(createElement(PostDesktop, { post: makeLegacyThread() }));
const primaryRepliesComment = testState.replyComments.find((comment) => comment?.cid === 'post-1');
@@ -468,7 +467,7 @@ describe('post community address compatibility', () => {
expect(container.textContent).toContain('reply-1');
});
it('renders mobile multiboard posts with only subplebbitAddress and still fetches replies', async () => {
it('renders mobile multiboard posts with only communityAddress and still fetches replies', async () => {
await renderWithRoute(createElement(PostMobile, { post: makeLegacyThread() }));
const primaryRepliesComment = testState.replyComments.find((comment) => comment?.cid === 'post-1');
@@ -28,14 +28,14 @@ describe('BlotterMessage', () => {
container.remove();
});
it('normalizes legacy subplebbit wording without rewriting plain community text', async () => {
it('renders manual messages unchanged', async () => {
await act(async () => {
root.render(
createElement(BlotterMessage, {
entry: {
id: 'manual-1',
kind: 'manual',
message: 'Moved a subplebbit into a community spotlight',
message: 'Moved a board into a community spotlight',
timestamp: 1_710_000_000,
},
}),
@@ -43,18 +43,16 @@ describe('BlotterMessage', () => {
});
expect(container.textContent).toContain('Moved a board into a community spotlight');
expect(container.textContent).not.toContain('subplebbit');
expect(container.textContent).not.toContain('board spotlight');
});
it('normalizes release one-liners after the version prefix', async () => {
it('renders release one-liners after the version prefix', async () => {
await act(async () => {
root.render(
createElement(BlotterMessage, {
entry: {
id: 'release-1',
kind: 'release',
message: 'v0.7.0: Fix subplebbit loading in plebchan',
message: 'v0.7.0: Fix community loading in 5chan',
timestamp: 1_710_000_000,
version: '0.7.0',
},
@@ -63,6 +61,6 @@ describe('BlotterMessage', () => {
});
expect(container.querySelector('a')?.getAttribute('href')).toBe('https://github.com/bitsocialnet/5chan/releases/tag/v0.7.0');
expect(container.textContent).toContain('Fix board loading in 5chan');
expect(container.textContent).toContain('Fix community loading in 5chan');
});
});
@@ -3,10 +3,6 @@ import styles from './blotter-message.module.css';
const RELEASES_BASE = 'https://github.com/bitsocialnet/5chan/releases/tag/v';
function normalizeMessage(text: string): string {
return text.replace(/subplebbit/gi, 'board').replace(/plebchan/gi, '5chan');
}
const BlotterMessage = ({ entry }: { entry: BlotterEntry }) => {
if (entry.kind === 'release' && entry.version) {
const idx = entry.message.indexOf(': ');
@@ -16,11 +12,11 @@ const BlotterMessage = ({ entry }: { entry: BlotterEntry }) => {
<a href={`${RELEASES_BASE}${entry.version}`} className={styles.versionLink} target='_blank' rel='noopener noreferrer'>
v{entry.version}
</a>
: {normalizeMessage(oneLiner)}
: {oneLiner}
</>
);
}
return <>{normalizeMessage(entry.message)}</>;
return <>{entry.message}</>;
};
export default BlotterMessage;
@@ -66,7 +66,7 @@ export const ArchiveButton = ({ address, isInAllView, isInSubscriptionsView, isI
const directories = useDirectories();
const isInvalidArchiveContext = isInAllView || isInSubscriptionsView || isInModView;
const boardIdentifier = params.boardIdentifier || params.subplebbitAddress;
const boardIdentifier = params.boardIdentifier;
const archiveBoardIdentifier = address ? getBoardPath(address, directories) : boardIdentifier ? getBoardPath(boardIdentifier, directories) : '';
const archivePath = archiveBoardIdentifier ? `/${archiveBoardIdentifier}/archive` : '';
@@ -130,7 +130,7 @@ const VoteButton = () => {
const directories = useDirectories();
// Get the boardIdentifier from params (try boardIdentifier first, then communityAddress for backward compatibility)
const boardIdentifier = params.boardIdentifier || params.communityAddress;
const boardIdentifier = params.boardIdentifier;
// Only render the vote button if we're on a directory board route
if (!boardIdentifier || !isDirectoryBoard(boardIdentifier, directories)) {
@@ -400,7 +400,7 @@ export const MobileBoardButtons = () => {
// Check if we should show the vote button (only for directory boards)
const directories = useDirectories();
const boardIdentifier = params.boardIdentifier || params.communityAddress;
const boardIdentifier = params.boardIdentifier;
const showVoteButton = boardIdentifier && isDirectoryBoard(boardIdentifier, directories);
return (
@@ -507,7 +507,7 @@ export const PostPageStats = () => {
const isThreadView = isPostPageView(location.pathname, params);
const pageNumber = usePostPageNumber({
subplebbitAddress: communityAddress,
communityAddress,
postCid,
enabled: isThreadView,
});
@@ -555,7 +555,7 @@ export const DesktopBoardButtons = () => {
// Check if we should show the vote button (only for directory boards)
const directories = useDirectories();
const boardIdentifier = params.boardIdentifier || params.communityAddress;
const boardIdentifier = params.boardIdentifier;
const showVoteButton = boardIdentifier && isDirectoryBoard(boardIdentifier, directories);
return (
@@ -9,7 +9,7 @@ import BoardsBar from '../boards-bar';
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
const testState = vi.hoisted(() => ({
accountComment: undefined as { communityAddress?: string; subplebbitAddress?: string } | undefined,
accountComment: undefined as { communityAddress?: string } | undefined,
accountCommunityAddresses: ['music-posting.eth'] as string[],
directories: [
{ address: 'music-posting.eth', title: '/mu/ - Music' },
@@ -262,7 +262,7 @@ describe('BoardsBar', () => {
it('keeps the mobile board context for legacy account comments', async () => {
testState.resolvedCommunityAddress = undefined;
testState.accountComment = { subplebbitAddress: 'music-posting.eth' };
testState.accountComment = { communityAddress: 'music-posting.eth' };
await renderBoardsBar('/pending/7');
@@ -15,15 +15,12 @@ type FilterItem = {
hide: boolean;
communityCounts: Map<string, number>;
communityFilteredCids: Map<string, Set<string>>;
subplebbitCounts?: Map<string, number>;
subplebbitFilteredCids?: Map<string, Set<string>>;
text: string;
top: boolean;
};
const testState = vi.hoisted(() => ({
currentCommunityAddress: 'music-posting.eth' as string | null,
currentSubplebbitAddress: 'music-posting.eth' as string | null,
filterItems: [] as FilterItem[],
resetCountsMock: vi.fn(),
resetFeedMock: vi.fn(),
@@ -38,8 +35,6 @@ const createFilterItem = (overrides: Partial<FilterItem> = {}): FilterItem => ({
hide: true,
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: undefined,
subplebbitFilteredCids: undefined,
text: '',
top: false,
...overrides,
@@ -48,7 +43,6 @@ const createFilterItem = (overrides: Partial<FilterItem> = {}): FilterItem => ({
function getCatalogFiltersState() {
return {
currentCommunityAddress: testState.currentCommunityAddress,
currentSubplebbitAddress: testState.currentSubplebbitAddress,
filterItems: testState.filterItems,
saveAndApplyFilters: testState.saveAndApplyFiltersMock,
};
@@ -141,7 +135,6 @@ describe('CatalogFilters', () => {
vi.clearAllMocks();
vi.useRealTimers();
testState.currentCommunityAddress = 'music-posting.eth';
testState.currentSubplebbitAddress = 'music-posting.eth';
testState.filterItems = [
createFilterItem({
count: 2,
@@ -278,40 +271,4 @@ describe('CatalogFilters', () => {
expect(testState.resetFeedMock).toHaveBeenCalledTimes(1);
expect(container.querySelector('[title="close"]')).toBeNull();
});
it('shows filter hit counts when only the legacy currentSubplebbitAddress is populated', async () => {
testState.currentCommunityAddress = null;
renderCatalogFilters();
await openModal();
expect(container.textContent).toContain('x2');
expect(container.textContent).toContain('x4');
});
it('shows filter hit counts when only the legacy subplebbit count payload is populated', async () => {
testState.currentCommunityAddress = null;
testState.filterItems = [
createFilterItem({
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: new Map([['music-posting.eth', 2]]),
subplebbitFilteredCids: new Map([['music-posting.eth', new Set(['alpha-cid'])]]),
text: 'alpha',
}),
createFilterItem({
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: new Map([['music-posting.eth', 4]]),
subplebbitFilteredCids: new Map([['music-posting.eth', new Set(['beta-cid'])]]),
text: 'beta',
}),
];
renderCatalogFilters();
await openModal();
expect(container.textContent).toContain('x2');
expect(container.textContent).toContain('x4');
});
});
@@ -11,8 +11,6 @@ type CatalogFilterItemInput = {
enabled: boolean;
count: number;
filteredCids: Set<string>;
subplebbitCounts?: Map<string, number>;
subplebbitFilteredCids?: Map<string, Set<string>>;
communityCounts?: Map<string, number>;
communityFilteredCids?: Map<string, Set<string>>;
hide?: boolean;
@@ -28,32 +26,19 @@ type CatalogFilterItemStore = {
filteredCids: Set<string>;
communityCounts: Map<string, number>;
communityFilteredCids: Map<string, Set<string>>;
subplebbitCounts: Map<string, number>;
subplebbitFilteredCids: Map<string, Set<string>>;
hide: boolean;
top: boolean;
color: string;
id?: string;
};
const selectFilterMap = <K, V>(preferred?: Map<K, V>, legacy?: Map<K, V>) => {
if (preferred && preferred.size > 0) return preferred;
if (legacy && legacy.size > 0) return legacy;
return preferred || legacy || new Map<K, V>();
};
const toCatalogFilterItem = (item: CatalogFilterItemInput): CatalogFilterItemStore => {
const counts = selectFilterMap(item.communityCounts, item.subplebbitCounts);
const filteredByCommunity = selectFilterMap(item.communityFilteredCids, item.subplebbitFilteredCids);
return {
...item,
count: item.count || 0,
filteredCids: item.filteredCids || new Set<string>(),
communityCounts: counts,
communityFilteredCids: filteredByCommunity,
subplebbitCounts: counts,
subplebbitFilteredCids: filteredByCommunity,
communityCounts: item.communityCounts || new Map<string, number>(),
communityFilteredCids: item.communityFilteredCids || new Map<string, Set<string>>(),
hide: item.hide ?? true,
top: item.top ?? false,
color: item.color || '',
@@ -62,14 +47,11 @@ const toCatalogFilterItem = (item: CatalogFilterItemInput): CatalogFilterItemSto
const FiltersTable = ({ onSave }: { onSave: () => void }) => {
const { t } = useTranslation();
const { currentSubplebbitAddress, currentCommunityAddress, filterItems, saveAndApplyFilters } = useCatalogFiltersStore((state) => ({
currentSubplebbitAddress: state.currentSubplebbitAddress,
// legacy fallback kept for compatibility while worker B/store migration is in progress
currentCommunityAddress: (state as { currentCommunityAddress?: string | null }).currentCommunityAddress ?? null,
const { currentCommunityAddress, filterItems, saveAndApplyFilters } = useCatalogFiltersStore((state) => ({
currentCommunityAddress: state.currentCommunityAddress,
filterItems: state.filterItems as CatalogFilterItemInput[],
saveAndApplyFilters: state.saveAndApplyFilters,
}));
const currentCommunityAddressResolved = currentCommunityAddress ?? currentSubplebbitAddress;
const resetFeed = useFeedResetStore((state) => state.reset);
const [localFilterItems, setLocalFilterItems] = useState(() =>
@@ -97,8 +79,6 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
filteredCids: new Set<string>(),
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: new Map<string, number>(),
subplebbitFilteredCids: new Map<string, Set<string>>(),
hide: true,
top: false,
color: '',
@@ -112,12 +92,7 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
saveAndApplyFilters(nonEmptyFilters);
const filtersState = useCatalogFiltersStore.getState() as {
resetCountsForCurrentCommunity?: () => void;
resetCountsForCurrentSubplebbit?: () => void;
};
filtersState.resetCountsForCurrentCommunity?.();
filtersState.resetCountsForCurrentSubplebbit?.();
useCatalogFiltersStore.getState().resetCountsForCurrentCommunity();
if (resetFeed) {
resetFeed();
@@ -234,9 +209,7 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
</span>
</td>
<td className={styles.filterHits}>
{currentCommunityAddressResolved &&
item.communityFilteredCids?.has(currentCommunityAddressResolved) &&
`x${item.communityCounts?.get(currentCommunityAddressResolved) ?? 0}`}
{currentCommunityAddress && item.communityFilteredCids?.has(currentCommunityAddress) && `x${item.communityCounts?.get(currentCommunityAddress) ?? 0}`}
</td>
</tr>
))}
@@ -260,12 +233,7 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
const { t } = useTranslation();
const [showHelp, setShowHelp] = useState(false);
const { currentSubplebbitAddress, currentCommunityAddress } = useCatalogFiltersStore((state) => ({
currentSubplebbitAddress: state.currentSubplebbitAddress,
// legacy fallback kept for compatibility while worker B/store migration is in progress
currentCommunityAddress: (state as { currentCommunityAddress?: string | null }).currentCommunityAddress ?? null,
}));
const currentCommunityAddressResolved = currentCommunityAddress ?? currentSubplebbitAddress;
const currentCommunityAddress = useCatalogFiltersStore((state) => state.currentCommunityAddress);
const openHelp = () => setShowHelp(true);
const closeHelp = () => setShowHelp(false);
@@ -292,7 +260,11 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
showHelp ? closeHelp() : closeModal();
if (showHelp) {
closeHelp();
} else {
closeModal();
}
}
}}
onClick={showHelp ? closeHelp : closeModal}
@@ -329,7 +301,7 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
onClick={closeModal}
/>
</div>
{showHelp ? <FiltersProtip /> : <FiltersTable key={currentCommunityAddressResolved ?? 'none'} onSave={closeModal} />}
{showHelp ? <FiltersProtip /> : <FiltersTable key={currentCommunityAddress ?? 'none'} onSave={closeModal} />}
</div>
</>
);
@@ -37,7 +37,6 @@ type TestComment = {
};
spoiler?: boolean;
communityAddress?: string;
subplebbitAddress?: string;
thumbnailUrl?: string;
timestamp?: number;
title?: string;
@@ -275,7 +274,7 @@ describe('CatalogRow', () => {
},
content: 'Archived thread',
replyCount: 3,
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
title: 'Old thread',
};
@@ -422,14 +421,14 @@ describe('CatalogRow', () => {
{
author: { address: 'author-2', displayName: 'Bob' },
cid: 'reply-legacy',
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
timestamp: 200,
},
],
},
},
},
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
timestamp: 100,
title: 'Legacy title',
};
+3 -3
View File
@@ -23,7 +23,7 @@ import PostMenuDesktop from '../post-desktop/post-menu-desktop';
import styles from './catalog-row.module.css';
import capitalize from 'lodash/capitalize';
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
import { withResolvedCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { getCommentCommunityAddress, withResolvedCommentCommunityAddress } from '../../lib/utils/comment-utils';
interface CatalogPostMediaProps {
cid: string;
@@ -325,8 +325,8 @@ const CatalogPost = memo(
(prevProps, nextProps) => {
const prev = prevProps.post;
const next = nextProps.post;
const prevCommunityAddress = prev?.communityAddress ?? prev?.subplebbitAddress;
const nextCommunityAddress = next?.communityAddress ?? next?.subplebbitAddress;
const prevCommunityAddress = getCommentCommunityAddress(prev);
const nextCommunityAddress = getCommentCommunityAddress(next);
// Compare all fields that affect rendering to avoid stale displays
return (
prev?.cid === next?.cid &&
@@ -12,9 +12,6 @@ type TestComment = {
community?: {
banExpiresAt?: number;
};
subplebbit?: {
banExpiresAt?: number;
};
};
cid?: string;
commentModeration?: {
@@ -276,10 +273,10 @@ describe('CommentContent', () => {
expect(queryMarkdownText()[0]).toHaveLength(1105);
});
it('keeps the ban indicator for legacy author subplebbit data', async () => {
it('shows the ban indicator from the canonical author community data', async () => {
await renderContent({
author: {
subplebbit: {
community: {
banExpiresAt: 1700000000,
},
},
@@ -75,8 +75,7 @@ const CommentContent = ({ comment: post, prependContent }: { comment: Comment; p
const { cid, content, deleted, edit, original, parentCid, postCid, pendingApproval, quotedCids, reason, removed, state } = resolvedPost || {};
const communityAddress = getCommentCommunityAddress(resolvedPost);
const purged = resolvedPost?.commentModeration?.purged;
const banExpiresAt =
resolvedPost?.author?.community?.banExpiresAt ?? (resolvedPost?.author as { subplebbit?: { banExpiresAt?: number } } | undefined)?.subplebbit?.banExpiresAt;
const banExpiresAt = resolvedPost?.author?.community?.banExpiresAt;
const banned = !!banExpiresAt;
const [showFullComment, setShowFullComment] = useState(false);
@@ -12,13 +12,13 @@
}
.floatingEmbed,
.subplebbitAvatar {
.communityAvatar {
max-width: 250px;
max-height: 250px;
display: inline-flex;
}
.subplebbitAvatar {
.communityAvatar {
margin: 3px 20px 5px 20px;
}
@@ -29,7 +29,7 @@
}
@media (max-width: 640px) {
.subplebbitAvatar {
.communityAvatar {
max-width: 125px;
max-height: 125px;
margin: 3px 10px 5px 5px;
@@ -438,7 +438,7 @@ const CommentMedia = ({
const maxThumbnailSize = isMobile || isReply ? 125 : 250;
if (linkWidth && linkHeight) {
// use the dimensions from the plebbit-js api
// use the dimensions from the pkc-js API
let scale = Math.min(1, maxThumbnailSize / Math.max(linkWidth, linkHeight));
displayWidth = `${linkWidth * scale}px`;
displayHeight = `${linkHeight * scale}px`;
@@ -61,7 +61,7 @@ const CreateBoardModal = () => {
<p>
Directory assignments will use gasless pubsub votingcommunities vote, highest-voted board wins the slot. <strong>Voting pages = discovery:</strong> Each
directory&apos;s voting page lists all competing boards (even low-voted ones), giving visibility without winning or dev approval. See{' '}
<a href='https://github.com/plebbit/plebbit-js/issues/25' target='_blank' rel='noopener noreferrer'>
<a href='https://github.com/pkcprotocol/pkc-js/issues/25' target='_blank' rel='noopener noreferrer'>
design draft
</a>
.
@@ -70,7 +70,7 @@ const DirectoryModal = () => {
<p>
Directory assignments will use gasless pubsub votingcommunities vote, highest-voted board wins the slot. <strong>Voting pages = discovery:</strong> Each
directory&apos;s voting page lists all competing boards (even low-voted ones), giving visibility without winning or dev approval. See{' '}
<a href='https://github.com/plebbit/plebbit-js/issues/25' target='_blank' rel='noopener noreferrer'>
<a href='https://github.com/pkcprotocol/pkc-js/issues/25' target='_blank' rel='noopener noreferrer'>
design draft
</a>
.
@@ -264,7 +264,6 @@ describe('EditMenu', () => {
communityAddress: 'music-posting.eth',
postCid: 'post-1',
});
expect(testState.authorPrivilegesOptions).not.toHaveProperty('subplebbitAddress');
});
it('allows pseudonymous boards to attempt author-side deletion without a local author address match', async () => {
+1 -1
View File
@@ -217,7 +217,7 @@ export const ThreadFooterMobile = ({ postCid, threadNumber, communityAddress, is
const linkCount = useCountLinksInReplies(post);
const directoryEntry = useDirectoryByAddress(communityAddress);
const requirePostLinkIsMedia = directoryEntry?.features?.requirePostLinkIsMedia === true;
const pageNumber = usePostPageNumber({ subplebbitAddress: communityAddress, postCid, enabled: true });
const pageNumber = usePostPageNumber({ communityAddress: communityAddress, postCid, enabled: true });
const handlePostReplyClick = () => {
if (isThreadClosed) return;
@@ -163,13 +163,9 @@ type PostMenuDesktopProps = {
postMenu: PostMenuProps;
};
type PostMenuLegacyAddress = Pick<PostMenuProps, 'subplebbitAddress'> & { communityAddress?: string };
const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
const { t } = useTranslation();
const { authorAddress, cid, link, thumbnailUrl, linkWidth, linkHeight, postCid } = postMenu || {};
const postMenuLegacyAddress = (postMenu as PostMenuLegacyAddress) || {};
const resolvedCommunityAddress = postMenuLegacyAddress.communityAddress || postMenuLegacyAddress.subplebbitAddress;
const { authorAddress, cid, communityAddress, link, thumbnailUrl, linkWidth, linkHeight, postCid } = postMenu || {};
const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth ?? 0, linkHeight ?? 0);
const { thumbnail, type, url } = commentMediaInfo || {};
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
@@ -267,7 +263,7 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
{hidden ? (postCid === cid ? t('unhide_thread') : t('unhide_post')) : postCid === cid ? t('hide_thread') : t('hide_post')}
</div>
)}
{cid && resolvedCommunityAddress && <CopyLinkButton cid={cid} communityAddress={resolvedCommunityAddress} linkType='thread' onClose={handleClose} />}
{cid && communityAddress && <CopyLinkButton cid={cid} communityAddress={communityAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
@@ -300,17 +300,13 @@ type PostMenuMobileProps = {
editMenuPost: Comment;
};
type PostMenuLegacyAddress = Pick<PostMenuProps, 'subplebbitAddress'> & { communityAddress?: string };
const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
const { authorAddress, cid, deleted, link, linkHeight, linkWidth, parentCid, postCid, removed, thumbnailUrl } = postMenu || {};
const postMenuLegacyAddress = (postMenu as PostMenuLegacyAddress) || {};
const resolvedCommunityAddress = postMenuLegacyAddress.communityAddress || postMenuLegacyAddress.subplebbitAddress;
const { authorAddress, cid, communityAddress, deleted, link, linkHeight, linkWidth, parentCid, postCid, removed, thumbnailUrl } = postMenu || {};
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({
commentAuthorAddress: authorAddress || '',
subplebbitAddress: resolvedCommunityAddress || '',
communityAddress: communityAddress || '',
});
const pseudonymityMode = useBoardPseudonymityMode(resolvedCommunityAddress);
const pseudonymityMode = useBoardPseudonymityMode(communityAddress);
const canAttemptAuthorDelete = pseudonymityMode !== undefined && pseudonymityMode !== 'none';
const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth || 0, linkHeight || 0);
const { thumbnail, type, url } = commentMediaInfo || {};
@@ -365,9 +361,9 @@ const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
<ReportPostButton onClose={handleClose} />
{cid && resolvedCommunityAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
{cid && communityAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
{(isAccountCommentAuthor || canAttemptAuthorDelete) && cid && <DeletePostButton post={editMenuPost} onClose={handleClose} />}
{cid && resolvedCommunityAddress && <CopyLinkButton cid={cid} communityAddress={resolvedCommunityAddress} linkType='thread' onClose={handleClose} />}
{cid && communityAddress && <CopyLinkButton cid={cid} communityAddress={communityAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButtons url={url} onClose={handleClose} />}
@@ -23,7 +23,7 @@ const testState = vi.hoisted(() => ({
rpcSettings: {
pkcRpcSettings: {
pkcOptions: {
dataPath: '/tmp/plebbit-data',
dataPath: '/tmp/pkc-data',
},
},
state: 'disconnected',
@@ -100,7 +100,7 @@ describe('AdvancedSettings', () => {
testState.rpcSettings = {
pkcRpcSettings: {
pkcOptions: {
dataPath: '/tmp/plebbit-data',
dataPath: '/tmp/pkc-data',
},
},
state: 'disconnected',
@@ -149,7 +149,7 @@ describe('AdvancedSettings', () => {
await dispatchInput(textareas[3], ' https://eth.one.example \n');
await dispatchInput(textareas[4], ' https://sol.one.example \n');
await dispatchInput(textInputs[1], ' ws://127.0.0.1:9138/secret ');
await dispatchInput(textInputs[2], ' /tmp/next-plebbit ');
await dispatchInput(textInputs[2], ' /tmp/next-pkc ');
await clickButton('save_advanced_settings');
expect(testState.setAccountMock).toHaveBeenCalledWith({
@@ -159,7 +159,7 @@ describe('AdvancedSettings', () => {
sol: { chainId: 101, urls: ['https://sol.one.example'] },
},
pkcOptions: {
dataPath: '/tmp/next-plebbit',
dataPath: '/tmp/next-pkc',
httpRoutersOptions: ['https://router.one.example'],
ipfsGatewayUrls: ['https://ipfs.one.example', 'https://ipfs.two.example'],
pkcRpcClientsOptions: ['ws://127.0.0.1:9138/secret'],
@@ -20,7 +20,6 @@ type AccountProtocolOptions = {
httpRoutersOptions?: string[];
ipfsGatewayUrls?: string[];
pkcRpcClientsOptions?: string[];
plebbitRpcClientsOptions?: string[];
pubsubHttpClientsOptions?: string[];
pubsubKuboRpcClientsOptions?: string[];
};
@@ -29,24 +28,22 @@ type AccountShape = {
chainProviders?: AccountProtocolOptions['chainProviders'];
mediaIpfsGatewayUrl?: string;
pkcOptions?: AccountProtocolOptions;
plebbitOptions?: AccountProtocolOptions;
};
type RpcSettingsShape = {
pkcOptions?: { dataPath?: string };
plebbitOptions?: { dataPath?: string };
};
const getProtocolOptions = (account?: AccountShape) => account?.pkcOptions ?? account?.plebbitOptions;
const getProtocolOptions = (account?: AccountShape) => account?.pkcOptions;
const getChainProviders = (account?: AccountShape) => account?.chainProviders ?? getProtocolOptions(account)?.chainProviders;
const getNodeRpcClientsOptions = (protocolOptions?: AccountProtocolOptions) => protocolOptions?.pkcRpcClientsOptions ?? protocolOptions?.plebbitRpcClientsOptions;
const getNodeRpcClientsOptions = (protocolOptions?: AccountProtocolOptions) => protocolOptions?.pkcRpcClientsOptions;
const getPubsubRpcClientsOptions = (protocolOptions?: AccountProtocolOptions) =>
protocolOptions?.pubsubKuboRpcClientsOptions ?? protocolOptions?.pubsubHttpClientsOptions;
const getRpcSettingsDataPath = (rpcSettings?: RpcSettingsShape) => rpcSettings?.pkcOptions?.dataPath ?? rpcSettings?.plebbitOptions?.dataPath ?? '';
const getRpcSettingsDataPath = (rpcSettings?: RpcSettingsShape) => rpcSettings?.pkcOptions?.dataPath ?? '';
const IPFSGatewaysSettings = ({ ipfsGatewayUrlsRef, mediaIpfsGatewayUrlRef }: SettingsProps) => {
const account = useAccount() as AccountShape | undefined;
+17 -17
View File
@@ -2,8 +2,8 @@ import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useAccountSubplebbitAddresses } from '../use-account-subplebbit-addresses';
import { useAccountSubplebbitsWithMetadata } from '../use-account-subplebbits-with-metadata';
import { useAccountCommunityAddresses } from '../use-account-community-addresses';
import { useAccountCommunitiesWithMetadata } from '../use-account-communities-with-metadata';
import useAuthorPrivileges from '../use-author-privileges';
import { useBoardFeedPageSize } from '../use-board-feed-page-size';
import { useBoardPseudonymityMode } from '../use-board-pseudonymity-mode';
@@ -16,16 +16,16 @@ const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise
const testState = vi.hoisted(() => ({
account: undefined as unknown,
accountSubplebbits: {} as Record<string, unknown>,
accountCommunities: {} as Record<string, unknown>,
directories: [] as Array<{ address: string; nsfw?: boolean }>,
directoryLookup: {} as Record<string, unknown>,
flattenedReplies: [] as unknown[],
subplebbitSnapshot: undefined as unknown,
communitySnapshot: undefined as unknown,
}));
vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
useAccount: () => testState.account,
useAccountCommunities: () => ({ accountCommunities: testState.accountSubplebbits }),
useAccountCommunities: () => ({ accountCommunities: testState.accountCommunities }),
}));
vi.mock('@bitsocialnet/bitsocial-react-hooks/dist/lib/utils', () => ({
@@ -38,7 +38,7 @@ vi.mock('../use-directories', () => ({
}));
vi.mock('../use-stable-community', () => ({
useCommunityField: (_address: string | undefined, selector: (community: unknown) => unknown) => selector(testState.subplebbitSnapshot),
useCommunityField: (_address: string | undefined, selector: (community: unknown) => unknown) => selector(testState.communitySnapshot),
}));
let latestValue: unknown;
@@ -67,11 +67,11 @@ describe('selector hooks', () => {
localStorage.clear();
vi.clearAllMocks();
testState.account = undefined;
testState.accountSubplebbits = {};
testState.accountCommunities = {};
testState.directories = [];
testState.directoryLookup = {};
testState.flattenedReplies = [];
testState.subplebbitSnapshot = undefined;
testState.communitySnapshot = undefined;
useAllFeedFilterStore.getState().setFilter('all');
container = document.createElement('div');
@@ -84,14 +84,14 @@ describe('selector hooks', () => {
container.remove();
});
it('derives account board addresses and metadata from cached account subplebbits', () => {
testState.accountSubplebbits = {
it('derives account board addresses and metadata from cached account communities', () => {
testState.accountCommunities = {
'music.eth': { address: 'music.eth', title: '/mu/ - Music' },
'tech.eth': { address: 'tech.eth', title: '/g/ - Technology' },
};
expect(renderHookValue(() => useAccountSubplebbitAddresses())).toEqual(['music.eth', 'tech.eth']);
expect(renderHookValue(() => useAccountSubplebbitsWithMetadata())).toEqual([
expect(renderHookValue(() => useAccountCommunityAddresses())).toEqual(['music.eth', 'tech.eth']);
expect(renderHookValue(() => useAccountCommunitiesWithMetadata())).toEqual([
{ address: 'music.eth', title: '/mu/ - Music' },
{ address: 'tech.eth', title: '/g/ - Technology' },
]);
@@ -99,14 +99,14 @@ describe('selector hooks', () => {
it('computes moderator privileges and whether the current account authored the comment', () => {
testState.account = { author: { address: '0xme' } };
testState.subplebbitSnapshot = {
testState.communitySnapshot = {
roles: {
'0xauthor': { role: 'moderator' },
'0xme': { role: 'admin' },
},
};
expect(renderHookValue(() => useAuthorPrivileges({ commentAuthorAddress: '0xauthor', subplebbitAddress: 'music.eth' }))).toEqual({
expect(renderHookValue(() => useAuthorPrivileges({ commentAuthorAddress: '0xauthor', communityAddress: 'music.eth' }))).toEqual({
isCommentAuthorMod: true,
isAccountMod: true,
isAccountCommentAuthor: false,
@@ -114,7 +114,7 @@ describe('selector hooks', () => {
accountAuthorRole: 'admin',
});
expect(renderHookValue(() => useAuthorPrivileges({ commentAuthorAddress: '0xme', subplebbitAddress: 'music.eth' }))).toEqual({
expect(renderHookValue(() => useAuthorPrivileges({ commentAuthorAddress: '0xme', communityAddress: 'music.eth' }))).toEqual({
isCommentAuthorMod: true,
isAccountMod: true,
isAccountCommentAuthor: true,
@@ -146,13 +146,13 @@ describe('selector hooks', () => {
features: { pseudonymityMode: 'directory-mode' },
},
};
testState.subplebbitSnapshot = {
testState.communitySnapshot = {
features: { pseudonymityMode: 'live-mode' },
};
expect(renderHookValue(() => useBoardPseudonymityMode('music.eth'))).toBe('live-mode');
testState.subplebbitSnapshot = {
testState.communitySnapshot = {
features: {},
};
@@ -12,7 +12,7 @@ type TestComment = {
content?: string;
index?: number;
number?: number;
subplebbitAddress?: string;
communityAddress?: string;
};
const testState = vi.hoisted(() => ({
@@ -79,12 +79,12 @@ describe('useFreshReplies', () => {
content: 'stale reply',
index: 3,
number: undefined,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
{
cid: 'network-reply-cid',
content: 'network reply',
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
testState.accountComments = [
@@ -93,7 +93,7 @@ describe('useFreshReplies', () => {
content: 'fresh reply',
index: 3,
number: 27,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
@@ -121,12 +121,12 @@ describe('useFreshReplies', () => {
{
content: 'stale failed reply',
index: 0,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
{
content: 'duplicate stale failed reply',
index: 0,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
testState.accountComments = [
@@ -134,7 +134,7 @@ describe('useFreshReplies', () => {
content: 'retried pending reply',
index: 0,
number: 99,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
@@ -2,7 +2,7 @@ import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import useIsSubplebbitOffline from '../use-is-subplebbit-offline';
import useIsCommunityOffline from '../use-is-community-offline';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
@@ -12,7 +12,7 @@ const testState = vi.hoisted(() => ({
loadingTimestamps: [0] as number[],
requestedAddresses: undefined as string[] | undefined,
setOfflineStateMock: vi.fn(),
subplebbitOfflineState: {} as Record<string, { initialLoad: boolean; state?: string; updatedAt?: number; updatingState?: string }>,
communityOfflineState: {} as Record<string, { initialLoad: boolean; state?: string; updatedAt?: number; updatingState?: string }>,
}));
vi.mock('react-i18next', () => ({
@@ -30,7 +30,7 @@ vi.mock('../../stores/use-community-offline-store', () => ({
default: () => ({
initializeCommunityOfflineState: testState.initializeMock,
setCommunityOfflineState: testState.setOfflineStateMock,
communityOfflineState: testState.subplebbitOfflineState,
communityOfflineState: testState.communityOfflineState,
}),
}));
@@ -45,12 +45,12 @@ vi.mock('../../lib/utils/time-utils', () => ({
getFormattedTimeAgo: (timestamp: number) => `ago:${timestamp}`,
}));
let latestValue: ReturnType<typeof useIsSubplebbitOffline>;
let latestValue: ReturnType<typeof useIsCommunityOffline>;
let container: HTMLDivElement;
let root: Root;
const HookHarness = ({ subplebbit }: { subplebbit?: { address?: string; state?: string; updatedAt?: number; updatingState?: string } }) => {
latestValue = useIsSubplebbitOffline(subplebbit as never);
const HookHarness = ({ community }: { community?: { address?: string; state?: string; updatedAt?: number; updatingState?: string } }) => {
latestValue = useIsCommunityOffline(community as never);
return null;
};
@@ -62,14 +62,14 @@ const flushEffects = async (count = 3) => {
}
};
const renderHook = async (subplebbit?: { address?: string; state?: string; updatedAt?: number; updatingState?: string }) => {
const renderHook = async (community?: { address?: string; state?: string; updatedAt?: number; updatingState?: string }) => {
await act(async () => {
root.render(createElement(HookHarness, { subplebbit }));
root.render(createElement(HookHarness, { community }));
});
await flushEffects();
};
describe('useIsSubplebbitOffline', () => {
describe('useIsCommunityOffline', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
@@ -83,7 +83,7 @@ describe('useIsSubplebbitOffline', () => {
};
testState.loadingTimestamps = [1_704_067_200];
testState.requestedAddresses = undefined;
testState.subplebbitOfflineState = {};
testState.communityOfflineState = {};
container = document.createElement('div');
document.body.appendChild(container);
@@ -116,7 +116,7 @@ describe('useIsSubplebbitOffline', () => {
it('reports boards with stale updates as offline and includes the last synced time', async () => {
const staleUpdatedAt = 1_704_052_000;
testState.subplebbitOfflineState = {
testState.communityOfflineState = {
'music.eth': {
initialLoad: false,
updatedAt: staleUpdatedAt,
@@ -136,7 +136,7 @@ describe('useIsSubplebbitOffline', () => {
});
it('marks boards without an update timestamp as offline once the loading timeout has elapsed', async () => {
testState.subplebbitOfflineState = {
testState.communityOfflineState = {
'music.eth': {
initialLoad: false,
},
@@ -149,13 +149,13 @@ describe('useIsSubplebbitOffline', () => {
isOffline: true,
isOnlineStatusLoading: false,
offlineIconClass: 'redOfflineIcon',
offlineTitle: 'subplebbit_offline_info',
offlineTitle: 'community_offline_info',
});
});
it('treats recently updated boards as online', async () => {
const freshUpdatedAt = 1_704_067_205;
testState.subplebbitOfflineState = {
testState.communityOfflineState = {
'music.eth': {
initialLoad: false,
updatedAt: freshUpdatedAt,
+19 -19
View File
@@ -39,13 +39,13 @@ const createPost = (boardAddress: string, suffix: string, replyCount: number, ti
content: `${suffix} content`,
link: `https://cdn.example/${boardAddress}/${suffix}.jpg`,
replyCount,
subplebbitAddress: boardAddress,
communityAddress: boardAddress,
thumbnailUrl: `https://cdn.example/${boardAddress}/${suffix}.thumb.jpg`,
timestamp,
title: `${suffix} title`,
}) as never;
const createSubplebbit = (boardAddress: string, posts: Array<{ cid: string }>, updatedAt = 1_704_067_150) =>
const createCommunity = (boardAddress: string, posts: Array<{ cid: string }>, updatedAt = 1_704_067_150) =>
({
address: boardAddress,
updatedAt,
@@ -58,14 +58,14 @@ const createSubplebbit = (boardAddress: string, posts: Array<{ cid: string }>, u
},
}) as never;
const HookHarness = ({ addresses, subplebbits }: { addresses: string[]; subplebbits: Array<unknown> }) => {
latestValue = usePopularPosts(subplebbits as never, addresses);
const HookHarness = ({ addresses, communities }: { addresses: string[]; communities: Array<unknown> }) => {
latestValue = usePopularPosts(communities as never, addresses);
return null;
};
const renderHook = async (addresses: string[], subplebbits: Array<unknown>) => {
const renderHook = async (addresses: string[], communities: Array<unknown>) => {
await act(async () => {
root.render(createElement(HookHarness, { addresses, subplebbits }));
root.render(createElement(HookHarness, { addresses, communities }));
});
};
@@ -112,8 +112,8 @@ describe('usePopularPosts', () => {
testState.loadingTimestamps = addresses.map(() => 1_704_067_180);
await renderHook(addresses, [
createSubplebbit(addresses[0], [createPost(addresses[0], 'top', 20), createPost(addresses[0], 'backup', 10)]),
createSubplebbit(addresses[1], [createPost(addresses[1], 'top', 18), createPost(addresses[1], 'backup', 9)]),
createCommunity(addresses[0], [createPost(addresses[0], 'top', 20), createPost(addresses[0], 'backup', 10)]),
createCommunity(addresses[1], [createPost(addresses[1], 'top', 18), createPost(addresses[1], 'backup', 9)]),
undefined,
undefined,
undefined,
@@ -128,12 +128,12 @@ describe('usePopularPosts', () => {
await renderHook(
addresses,
addresses.map((address, index) => createSubplebbit(address, [createPost(address, 'top', 20 - index), createPost(address, 'backup', 5 - index)])),
addresses.map((address, index) => createCommunity(address, [createPost(address, 'top', 20 - index), createPost(address, 'backup', 5 - index)])),
);
expect(latestValue.isLoading).toBe(false);
expect(latestValue.popularPosts).toHaveLength(8);
expect(new Set(latestValue.popularPosts.map((post) => post.subplebbitAddress)).size).toBe(8);
expect(new Set(latestValue.popularPosts.map((post) => post.communityAddress)).size).toBe(8);
expect(latestValue.popularPosts.every((post) => post.cid.endsWith('-top'))).toBe(true);
});
@@ -143,7 +143,7 @@ describe('usePopularPosts', () => {
await renderHook(
addresses,
addresses.map((address, index) => createSubplebbit(address, [createPost(address, 'initial', 30 - index)])),
addresses.map((address, index) => createCommunity(address, [createPost(address, 'initial', 30 - index)])),
);
const initialCids = latestValue.popularPosts.map((post) => post.cid);
@@ -151,7 +151,7 @@ describe('usePopularPosts', () => {
await renderHook(
addresses,
addresses.map((address, index) => createSubplebbit(address, [createPost(address, 'replacement', 100 - index), createPost(address, 'initial', 30 - index)])),
addresses.map((address, index) => createCommunity(address, [createPost(address, 'replacement', 100 - index), createPost(address, 'initial', 30 - index)])),
);
expect(latestValue.isLoading).toBe(false);
@@ -162,13 +162,13 @@ describe('usePopularPosts', () => {
const addresses = ['board-0.eth', 'board-1.eth'];
testState.loadingTimestamps = [1_704_067_180, 1_704_067_180];
await renderHook(addresses, [createSubplebbit(addresses[0], [createPost(addresses[0], 'only', 12)]), undefined]);
await renderHook(addresses, [createCommunity(addresses[0], [createPost(addresses[0], 'only', 12)]), undefined]);
expect(latestValue.isLoading).toBe(true);
expect(latestValue.popularPosts).toEqual([]);
testState.currentTime = 1_704_067_211;
await renderHook(addresses, [createSubplebbit(addresses[0], [createPost(addresses[0], 'only', 12)]), undefined]);
await renderHook(addresses, [createCommunity(addresses[0], [createPost(addresses[0], 'only', 12)]), undefined]);
expect(latestValue.isLoading).toBe(false);
expect(latestValue.popularPosts.map((post) => post.cid)).toEqual([`${addresses[0]}-only`]);
@@ -187,23 +187,23 @@ describe('usePopularPosts', () => {
it('reshuffles the selected boards on each mount while keeping one top thread per board', async () => {
const addresses = Array.from({ length: 10 }, (_, index) => `board-${index}.eth`);
const subplebbits = addresses.map((address, index) => createSubplebbit(address, [createPost(address, 'top', 30 - index), createPost(address, 'backup', 10 - index)]));
const communities = addresses.map((address, index) => createCommunity(address, [createPost(address, 'top', 30 - index), createPost(address, 'backup', 10 - index)]));
const keepOrderRandom = mockRandomSequence(Array.from({ length: addresses.length - 1 }, () => 0.999_999));
await renderHook(addresses, subplebbits);
await renderHook(addresses, communities);
expect(latestValue.isLoading).toBe(false);
expect(latestValue.popularPosts.map((post) => post.subplebbitAddress)).toEqual(addresses.slice(0, 8));
expect(latestValue.popularPosts.map((post) => post.communityAddress)).toEqual(addresses.slice(0, 8));
expect(latestValue.popularPosts.every((post) => post.cid.endsWith('-top'))).toBe(true);
keepOrderRandom.mockRestore();
resetHookRoot();
const rotateOrderRandom = mockRandomSequence(Array.from({ length: addresses.length - 1 }, () => 0));
await renderHook(addresses, subplebbits);
await renderHook(addresses, communities);
expect(latestValue.isLoading).toBe(false);
expect(latestValue.popularPosts.map((post) => post.subplebbitAddress)).toEqual(addresses.slice(1, 9));
expect(latestValue.popularPosts.map((post) => post.communityAddress)).toEqual(addresses.slice(1, 9));
expect(latestValue.popularPosts.every((post) => post.cid.endsWith('-top'))).toBe(true);
rotateOrderRandom.mockRestore();
@@ -48,12 +48,12 @@ let latestValue: number | undefined;
let container: HTMLDivElement;
let root: Root;
const HookHarness = ({ enabled = true, postCid, subplebbitAddress }: { enabled?: boolean; postCid?: string; subplebbitAddress?: string }) => {
latestValue = usePostPageNumber({ enabled, postCid, subplebbitAddress });
const HookHarness = ({ enabled = true, postCid, communityAddress }: { enabled?: boolean; postCid?: string; communityAddress?: string }) => {
latestValue = usePostPageNumber({ enabled, postCid, communityAddress });
return null;
};
const renderHook = (props: { enabled?: boolean; postCid?: string; subplebbitAddress?: string }) => {
const renderHook = (props: { enabled?: boolean; postCid?: string; communityAddress?: string }) => {
act(() => {
root.render(createElement(HookHarness, props));
});
@@ -95,7 +95,7 @@ describe('usePostPageNumber', () => {
boardFeed: [{ cid: 'post-1' }, { cid: 'post-2' }, { cid: 'post-3' }],
};
expect(renderHook({ postCid: 'post-3', subplebbitAddress: 'music.eth' })).toBe(2);
expect(renderHook({ postCid: 'post-3', communityAddress: 'music.eth' })).toBe(2);
expect(testState.preloadOptions).toEqual({
communities: [{ name: 'music.eth' }],
postsPerPage: 20,
@@ -106,7 +106,7 @@ describe('usePostPageNumber', () => {
it('falls back to the preloaded feed when cached feeds do not contain the post yet', () => {
testState.preloadFeed = [{ cid: 'post-1' }, { cid: 'post-2' }, { cid: 'post-3' }, { cid: 'post-4' }];
expect(renderHook({ postCid: 'post-4', subplebbitAddress: 'music.eth' })).toBe(2);
expect(renderHook({ postCid: 'post-4', communityAddress: 'music.eth' })).toBe(2);
expect(testState.preloadOptions).toEqual({
communities: [{ name: 'music.eth' }],
postsPerPage: 20,
@@ -117,10 +117,10 @@ describe('usePostPageNumber', () => {
it('skips resolution entirely when the hook is disabled or required inputs are missing', () => {
testState.preloadFeed = [{ cid: 'post-1' }];
expect(renderHook({ enabled: false, postCid: 'post-1', subplebbitAddress: 'music.eth' })).toBeUndefined();
expect(renderHook({ enabled: false, postCid: 'post-1', communityAddress: 'music.eth' })).toBeUndefined();
expect(testState.preloadOptions).toBeUndefined();
expect(renderHook({ enabled: true, postCid: undefined, subplebbitAddress: 'music.eth' })).toBeUndefined();
expect(renderHook({ enabled: true, postCid: undefined, communityAddress: 'music.eth' })).toBeUndefined();
expect(testState.preloadOptions).toBeUndefined();
});
});
@@ -53,7 +53,7 @@ describe('useQuotedByMap', () => {
cid: 'reply-cid',
content: 'replying to >>1',
state: 'succeeded',
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
@@ -85,7 +85,7 @@ describe('useQuotedByMap', () => {
content: 'replying to >>1',
number: 42,
state: 'succeeded',
subplebbitAddress: 'music.bso',
communityAddress: 'music.bso',
},
];
@@ -2,7 +2,7 @@ import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useStableSubplebbit, useSubplebbitField } from '../use-stable-subplebbit';
import { useStableCommunity, useCommunityField } from '../use-stable-community';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
@@ -36,7 +36,7 @@ const renderHookValue = (useValue: () => unknown) => {
return latestValue;
};
describe('use-stable-subplebbit', () => {
describe('use-stable-community', () => {
beforeEach(() => {
latestValue = undefined;
renderCount = 0;
@@ -63,11 +63,11 @@ describe('use-stable-subplebbit', () => {
},
};
expect(renderHookValue(() => useSubplebbitField('international-sfw.eth', (subplebbit) => subplebbit?.roles))).toEqual({
expect(renderHookValue(() => useCommunityField('international-sfw.eth', (community) => community?.roles))).toEqual({
'plebeius.eth': { role: 'owner' },
});
expect(renderHookValue(() => useStableSubplebbit('international-sfw.eth'))).toMatchObject({
expect(renderHookValue(() => useStableCommunity('international-sfw.eth'))).toMatchObject({
address: 'international-sfw.bso',
title: '/int/ - International',
});
@@ -85,6 +85,6 @@ describe('use-stable-subplebbit', () => {
},
};
expect(renderHookValue(() => useSubplebbitField('business.eth', (subplebbit) => subplebbit?.title))).toBe('/biz/ - Exact');
expect(renderHookValue(() => useCommunityField('business.eth', (community) => community?.title))).toBe('/biz/ - Exact');
});
});
@@ -1 +0,0 @@
export { useAccountCommunityAddresses as useAccountSubplebbitAddresses } from './use-account-community-addresses';
@@ -1 +0,0 @@
export { useAccountCommunitiesWithMetadata as useAccountSubplebbitsWithMetadata } from './use-account-communities-with-metadata';
+2 -4
View File
@@ -4,17 +4,15 @@ import { useCommunityField } from './use-stable-community';
interface AuthorPrivilegesProps {
commentAuthorAddress: string;
subplebbitAddress?: string;
communityAddress?: string;
postCid?: string;
}
const useAuthorPrivileges = ({ commentAuthorAddress, subplebbitAddress, communityAddress }: AuthorPrivilegesProps) => {
const useAuthorPrivileges = ({ commentAuthorAddress, communityAddress }: AuthorPrivilegesProps) => {
const account = useAccount();
const targetAddress = communityAddress ?? subplebbitAddress;
const accountAuthorAddress = account?.author?.address;
// Only subscribe to roles field to avoid rerenders from updatingState changes
const roles = useCommunityField(targetAddress, (community) => community?.roles);
const roles = useCommunityField(communityAddress, (community) => community?.roles);
const { isCommentAuthorMod, isAccountMod, isAccountCommentAuthor, commentAuthorRole, accountAuthorRole } = useMemo(() => {
const commentAuthorRole = roles?.[commentAuthorAddress]?.role;
const isCommentAuthorMod = commentAuthorRole === 'admin' || commentAuthorRole === 'owner' || commentAuthorRole === 'moderator';
-6
View File
@@ -29,9 +29,3 @@ export const CommunityStatsCollector = ({ communityAddress }: { communityAddress
return null;
};
/**
* Back-compat exports for old naming.
*/
export const useSubplebbitsStatsStore = useCommunitiesStatsStore;
export const SubplebbitStatsCollector = CommunityStatsCollector;
+4 -1
View File
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useRef, useState } from 'react';
import { ChallengeVerification, Comment, PublishCommentOptions, deleteComment, usePublishComment } from '@bitsocialnet/bitsocial-react-hooks';
import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils';
import useChallengesStore from '../stores/use-challenges-store';
import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
const retryExcludedFields = new Set([
'accountId',
@@ -57,9 +58,11 @@ export const getFailedPostRetryPublishOptions = (post?: FailedPost): PublishComm
retryOptions.author = author;
}
if (!retryOptions.communityAddress && !retryOptions.subplebbitAddress) {
const communityAddress = retryOptions.communityAddress ?? getCommentCommunityAddress(post);
if (!communityAddress) {
return undefined;
}
retryOptions.communityAddress = communityAddress;
return retryOptions;
};
+1 -3
View File
@@ -35,11 +35,9 @@ const useIsCommunityOffline = (community?: Community | undefined) => {
? 'downloading board...'
: updatedAt
? isOffline && t('posts_last_synced_info', { time: getFormattedTimeAgo(updatedAt), interpolation: { escapeValue: false } })
: t('subplebbit_offline_info');
: t('community_offline_info');
return { isOffline: !isOnline && isOffline, isOnlineStatusLoading: !isOnline && isLoading, offlineIconClass, offlineTitle };
};
export const useIsSubplebbitOffline = useIsCommunityOffline;
export default useIsCommunityOffline;
-4
View File
@@ -1,4 +0,0 @@
import useIsCommunityOffline from './use-is-community-offline';
export { useIsCommunityOffline as useIsSubplebbitOffline };
export default useIsCommunityOffline;
+1 -10
View File
@@ -7,10 +7,7 @@ import { useCommunityIdentifier } from './use-community-identifiers';
import { findPostPageInFeed, findPostPageInLoadedBoardFeeds, type FeedsOptionsLike, type LoadedFeedsLike } from '../lib/utils/post-page-resolution';
interface UsePostPageNumberOptions {
/** Canonical name. Kept for backward compatibility with older call sites. */
communityAddress?: string;
/** Legacy name kept for backwards compatibility. */
subplebbitAddress?: string;
postCid: string | undefined;
/** When false, page segment is excluded (e.g. pending-post view). When true, resolve and show page. */
enabled?: boolean;
@@ -23,13 +20,7 @@ interface UsePostPageNumberOptions {
*
* @returns 1-based page number, or undefined when unresolved (render as "?")
*/
export function usePostPageNumber({
communityAddress: requestedCommunityAddress,
subplebbitAddress: legacyCommunityAddress,
postCid,
enabled = true,
}: UsePostPageNumberOptions): number | undefined {
const communityAddress = requestedCommunityAddress ?? legacyCommunityAddress;
export function usePostPageNumber({ communityAddress, postCid, enabled = true }: UsePostPageNumberOptions): number | undefined {
const communityIdentifier = useCommunityIdentifier(communityAddress);
const community = useDirectoryByAddress(communityAddress);

Some files were not shown because too many files have changed in this diff Show More