Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,15 @@ jobs:
allowUpdates: true

mac:
runs-on: macOS-13
strategy:
fail-fast: false
matrix:
include:
- runner: macOS-13 # Intel x64
arch: x64
- runner: macOS-14 # Apple Silicon arm64
arch: arm64
runs-on: ${{ matrix.runner }}
permissions:
contents: write
steps:
Expand Down
4 changes: 3 additions & 1 deletion electron/before-pack.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const ipfsClientLinuxPath = path.join(ipfsClientsPath, 'linux');
// official kubo download links https://docs.ipfs.tech/install/command-line/#install-official-binary-distributions
const ipfsClientVersion = '0.32.1';
const ipfsClientWindowsUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_windows-amd64.zip`;
const ipfsClientMacUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_darwin-amd64.tar.gz`;
// Choose proper mac binary by builder architecture to avoid Rosetta on Apple Silicon
const macArch = process.arch === 'arm64' ? 'arm64' : 'amd64';
const ipfsClientMacUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_darwin-${macArch}.tar.gz`;
const ipfsClientLinuxUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_linux-amd64.tar.gz`;

const downloadWithProgress = (url) =>
Expand Down
63 changes: 42 additions & 21 deletions electron/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import util from 'util';
import fs from 'fs-extra';
import path from 'path';
import EnvPaths from 'env-paths';
import isDev from 'electron-is-dev';
const envPaths = EnvPaths('plebbit', { suffix: false });

// previous version created a file instead of folder
Expand All @@ -26,32 +27,52 @@ const writeLog = (...args) => {
};

const consoleLog = console.log;
console.log = (...args) => {
writeLog(...args);
consoleLog(...args);
};
const consoleError = console.error;
console.error = (...args) => {
writeLog(...args);
consoleError(...args);
};
const consoleWarn = console.warn;
console.warn = (...args) => {
writeLog(...args);
consoleWarn(...args);
};
const consoleDebug = console.debug;
console.debug = (...args) => {
// don't add date for debug because it's usually already included
for (const arg of args) {
logFile.write(util.format(arg) + ' ');
}
logFile.write('\r\n');
consoleDebug(...args);
};

// In production, avoid writing verbose logs (log/debug) to disk to prevent I/O thrash.
if (!isDev) {
console.log = (...args) => {
// keep stdout behavior but don't write to file
consoleLog(...args);
};
console.debug = (...args) => {
consoleDebug(...args);
};
console.warn = (...args) => {
writeLog(...args);
consoleWarn(...args);
};
console.error = (...args) => {
writeLog(...args);
consoleError(...args);
};
} else {
// In dev, mirror everything to file for easier debugging
console.log = (...args) => {
writeLog(...args);
consoleLog(...args);
};
console.warn = (...args) => {
writeLog(...args);
consoleWarn(...args);
};
console.error = (...args) => {
writeLog(...args);
consoleError(...args);
};
console.debug = (...args) => {
for (const arg of args) {
logFile.write(util.format(arg) + ' ');
}
logFile.write('\r\n');
consoleDebug(...args);
};
}

// errors aren't console logged
process.on('uncaughtException', console.error);
process.on('unhandledRejection', console.error);

console.log(envPaths);
if (isDev) console.log(envPaths);
2 changes: 1 addition & 1 deletion electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ const createMainWindow = () => {
webSecurity: true, // must be true or iframe embeds like youtube can do remote code execution
nodeIntegration: false,
contextIsolation: true,
devTools: true, // TODO: change to isDev when no bugs left
devTools: isDev,
preload: path.join(dirname, '../build/electron/preload.cjs'),
},
});
Expand Down
36 changes: 21 additions & 15 deletions electron/start-ipfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@ const envPaths = EnvPaths('plebbit', { suffix: false });
// also spawnSync might have been causing crash on start on windows
const spawnAsync = (...args) =>
new Promise((resolve, reject) => {
const spawedProcess = spawn(...args);
spawedProcess.on('exit', (exitCode, signal) => {
const spawnedProcess = spawn(...args);
spawnedProcess.on('exit', (exitCode, signal) => {
if (exitCode === 0) resolve();
else reject(Error(`spawnAsync process '${spawedProcess.pid}' exited with code '${exitCode}' signal '${signal}'`));
else reject(Error(`spawnAsync process '${spawnedProcess.pid}' exited with code '${exitCode}' signal '${signal}'`));
});
spawedProcess.stderr.on('data', (data) => console.error(data.toString()));
spawedProcess.stdin.on('data', (data) => console.log(data.toString()));
spawedProcess.stdout.on('data', (data) => console.log(data.toString()));
spawedProcess.on('error', (data) => console.error(data.toString()));
// Always surface errors from short-lived commands
spawnedProcess.stderr.on('data', (data) => console.error(data.toString()));
// Short-lived command stdout can be useful in dev, but is noisy in prod
if (isDev) {
spawnedProcess.stdout.on('data', (data) => console.log(data.toString()));
} else {
// Drain to avoid backpressure without logging
spawnedProcess.stdout.on('data', () => {});
}
spawnedProcess.on('error', (data) => console.error(data.toString?.() || String(data)));
});

const startIpfs = async () => {
Expand Down Expand Up @@ -51,7 +57,8 @@ const startIpfs = async () => {
console.log({ ipfsPath, ipfsDataPath });

fs.ensureDirSync(ipfsDataPath);
const env = { IPFS_PATH: ipfsDataPath };
// Reduce IPFS daemon log verbosity in production to avoid UI lag from excessive logging
const env = { ...process.env, IPFS_PATH: ipfsDataPath, ...(isDev ? {} : { GOLOG_LOG_LEVEL: 'error' }) };
// init ipfs client on first launch
try {
await spawnAsync(ipfsPath, ['init'], { env, hideWindows: true });
Expand Down Expand Up @@ -83,17 +90,16 @@ const startIpfs = async () => {
let lastError;
ipfsProcess.stderr.on('data', (data) => {
lastError = data.toString();
console.error(data.toString());
if (isDev) console.error(lastError);
});
ipfsProcess.stdin.on('data', (data) => console.log(data.toString()));
ipfsProcess.stdout.on('data', (data) => {
data = data.toString();
console.log(data);
if (data.includes('Daemon is ready')) {
ipfsProcess.stdout.on('data', (chunk) => {
const text = chunk.toString();
if (isDev) console.log(text);
if (text.includes('Daemon is ready')) {
resolve();
}
});
ipfsProcess.on('error', (data) => console.error(data.toString()));
ipfsProcess.on('error', (err) => console.error(err?.toString?.() || String(err)));
ipfsProcess.on('exit', () => {
console.error(`ipfs process with pid ${ipfsProcess.pid} exited`);
reject(Error(lastError));
Expand Down
2 changes: 1 addition & 1 deletion scripts/release-body.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let releaseChangelog =
// format
releaseChangelog = releaseChangelog.trim().replace(/\n\n+/g, '\n\n')

const releaseBody = `This version fixes a bug that marked all the user's comment edits as "pending" and "failed". It also fixes an issue that prevented exporting accounts from the settings in the android app.
const releaseBody = `This version fixes a bug that caused extreme slowness in some desktop versions of the app.

- Web app: https://seedit.app
- Decentralized web app: https://seedit.eth (only works on [Brave Browser](https://brave.com/) or via [IPFS Companion](https://docs.ipfs.tech/install/ipfs-companion/#prerequisites))
Expand Down
11 changes: 8 additions & 3 deletions src/components/post/post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,12 @@ const Post = ({ index, post = {} }: PostProps) => {
const postTitle =
(title?.length > 300 ? title?.slice(0, 300) + '...' : title) ||
(content?.length > 300 ? content?.slice(0, 300) + '...' : content)?.replace('&nbsp;', ' ')?.replace('>', '')?.replace('<', '')?.trim();
const displayedTitle = searchQuery ? highlightMatchedText(postTitle || '', searchQuery) : postTitle;

// Ensure we have a meaningful title - if it's only whitespace/newlines, treat as empty
const cleanedTitle = postTitle?.trim();
const finalTitle = cleanedTitle || '-';

const displayedTitle = searchQuery ? highlightMatchedText(finalTitle, searchQuery) : finalTitle;

const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const hostname = getHostname(link);
Expand Down Expand Up @@ -237,11 +242,11 @@ const Post = ({ index, post = {} }: PostProps) => {
<p className={styles.title}>
{isInPostPageView && link ? (
<a href={link} className={linkClass} target='_blank' rel='noopener noreferrer' onClick={handlePostClick}>
{displayedTitle ?? '-'}
{displayedTitle}
</a>
) : (
<Link className={linkClass} to={cid ? `/p/${subplebbitAddress}/c/${cid}` : `/profile/${post?.index}`} onClick={handlePostClick}>
{displayedTitle ?? '-'}
{displayedTitle}
</Link>
)}
{flair && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,10 @@
resize: vertical;
white-space: pre;
overflow-wrap: normal;
}

.error {
color: var(--red);
font-size: 12px;
margin: 20px 10px 0 10px;
}
33 changes: 31 additions & 2 deletions src/views/settings/account-data-editor/account-data-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import stringify from 'json-stringify-pretty-compact';
import styles from './account-data-editor.module.css';
import useIsMobile from '../../../hooks/use-is-mobile';
import LoadingEllipsis from '../../../components/loading-ellipsis';
import ErrorDisplay from '../../../components/error-display';

class EditorErrorBoundary extends Component<{ children: React.ReactNode; fallback: React.ReactNode }> {
constructor(props: { children: React.ReactNode; fallback: React.ReactNode }) {
Expand Down Expand Up @@ -57,6 +58,7 @@ const AccountDataEditor = () => {
const theme = useTheme((state) => state.theme);
const [text, setText] = useState('');
const [showEditor, setShowEditor] = useState(false);
const [currentError, setCurrentError] = useState<Error | undefined>(undefined);

const accountJson = useMemo(() => stringify({ account: { ...account, plebbit: undefined, karma: undefined, unreadNotificationCount: undefined } }), [account]);

Expand All @@ -66,16 +68,20 @@ const AccountDataEditor = () => {

const saveAccount = async () => {
try {
setCurrentError(undefined);
const editorAccount = JSON.parse(text).account;
await setAccount(editorAccount);
alert(`Saved ${editorAccount.name}`);
navigate('/settings');
window.location.reload();
} catch (error) {
if (error instanceof Error) {
setCurrentError(error);
alert(error.message);
console.log(error);
} else {
const unknownError = new Error('An unknown error occurred');
setCurrentError(unknownError);
console.error('An unknown error occurred:', error);
}
}
Expand All @@ -99,7 +105,20 @@ const AccountDataEditor = () => {

return (
<div className={styles.content}>
<EditorErrorBoundary fallback={<FallbackEditor value={text} onChange={setText} height={isMobile ? 'calc(80vh - 95px)' : 'calc(90vh - 77px)'} />}>
<EditorErrorBoundary
fallback={
<FallbackEditor
value={text}
onChange={(value) => {
setText(value);
if (currentError) {
setCurrentError(undefined);
}
}}
height={isMobile ? 'calc(80vh - 95px)' : 'calc(90vh - 77px)'}
/>
}
>
<Suspense
fallback={
<div className={styles.loading}>
Expand All @@ -111,7 +130,12 @@ const AccountDataEditor = () => {
mode='json'
theme={theme === 'dark' ? 'tomorrow_night' : 'github'}
value={text}
onChange={setText}
onChange={(value) => {
setText(value);
if (currentError) {
setCurrentError(undefined);
}
}}
name='ACCOUNT_DATA_EDITOR'
editorProps={{ $blockScrolling: true }}
className={styles.editor}
Expand All @@ -132,6 +156,11 @@ const AccountDataEditor = () => {
/>
</Suspense>
</EditorErrorBoundary>
{currentError && (
<div className={styles.error}>
<ErrorDisplay error={currentError} />
</div>
)}
<div className={styles.buttons}>
<Trans
i18nKey='save_reset_changes'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ const ChallengeSettings = ({ challenge, challengesSettings, index, isReadOnly, s
<input
type='number'
min={0}
value={exclude?.postScore || undefined}
value={exclude?.postScore ?? ''}
onChange={(e) => {
const value = e.target.value === '' ? undefined : Number(e.target.value);
handleExcludeChange(excludeIndex, 'postScore', value);
Expand All @@ -253,7 +253,7 @@ const ChallengeSettings = ({ challenge, challengesSettings, index, isReadOnly, s
<input
min={0}
type='number'
value={exclude?.replyScore || undefined}
value={exclude?.replyScore ?? ''}
onChange={(e) => {
const value = e.target.value === '' ? undefined : Number(e.target.value);
handleExcludeChange(excludeIndex, 'replyScore', value);
Expand All @@ -274,7 +274,7 @@ const ChallengeSettings = ({ challenge, challengesSettings, index, isReadOnly, s
<input
type='number'
min={0}
value={exclude?.firstCommentTimestamp || undefined}
value={exclude?.firstCommentTimestamp ?? ''}
onChange={(e) => {
const value = e.target.value === '' ? undefined : Number(e.target.value);
handleExcludeChange(excludeIndex, 'firstCommentTimestamp', value);
Expand Down Expand Up @@ -362,7 +362,7 @@ const ChallengeSettings = ({ challenge, challengesSettings, index, isReadOnly, s
<input
type='number'
min={0}
value={exclude?.rateLimit || undefined}
value={exclude?.rateLimit ?? ''}
onChange={(e) => {
const value = e.target.value === '' ? undefined : Number(e.target.value);
handleExcludeChange(excludeIndex, 'rateLimit', value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,11 @@ const SubplebbitDataEditor = () => {
/>
</Suspense>
</EditorErrorBoundary>
{currentError && <div className={styles.error}>error: {currentError.message || 'unknown error'}</div>}
{currentError && (
<div className={styles.error}>
<ErrorDisplay error={currentError} />
</div>
)}
{showSaving ? (
<div className={styles.loading}>
<LoadingEllipsis string={t('saving')} />
Expand Down