mirror of
https://github.com/plebbit/seedit.git
synced 2026-04-18 22:28:21 -04:00
50
.github/workflows/release.yml
vendored
50
.github/workflows/release.yml
vendored
@@ -20,22 +20,19 @@ jobs:
|
||||
with:
|
||||
# needed for git commit history changelog
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js v20
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install dependencies (with Node v20)
|
||||
run: yarn install --frozen-lockfile --ignore-engines
|
||||
# make sure the ipfs executable is executable
|
||||
- name: Download IPFS and set permissions (with Node v20)
|
||||
run: node electron/download-ipfs && sudo chmod +x bin/linux/ipfs
|
||||
|
||||
- name: Setup Node.js v22 for Electron build
|
||||
- name: Setup Node.js v22
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Verify Node.js version consistency
|
||||
run: yarn verify-node
|
||||
- name: Install dependencies (with Node v22)
|
||||
run: yarn install --frozen-lockfile --ignore-engines
|
||||
# make sure the ipfs executable is executable
|
||||
- name: Download IPFS and set permissions (with Node v22)
|
||||
run: node electron/download-ipfs && sudo chmod +x bin/linux/ipfs
|
||||
- name: Build React app (with Node v22)
|
||||
run: CI='' yarn build
|
||||
run: CI='' NODE_ENV=production yarn build
|
||||
- name: Build Electron app for Linux (with Node v22)
|
||||
run: yarn electron:build:linux
|
||||
- name: List dist directory
|
||||
@@ -62,27 +59,24 @@ jobs:
|
||||
with:
|
||||
# needed for git commit history changelog
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js v20
|
||||
- name: Setup Node.js v22
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
|
||||
# install missing dep for sqlite
|
||||
- run: python3 -m ensurepip
|
||||
- run: pip install setuptools
|
||||
|
||||
- name: Install dependencies (with Node v20)
|
||||
- name: Verify Node.js version consistency
|
||||
run: yarn verify-node
|
||||
- name: Install dependencies (with Node v22)
|
||||
run: yarn install --frozen-lockfile --ignore-engines
|
||||
# make sure the ipfs executable is executable
|
||||
- name: Download IPFS and set permissions (with Node v20)
|
||||
- name: Download IPFS and set permissions (with Node v22)
|
||||
run: node electron/download-ipfs && sudo chmod +x bin/mac/ipfs
|
||||
|
||||
- name: Setup Node.js v22 for Electron build
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Build React app (with Node v22)
|
||||
run: CI='' yarn build
|
||||
run: CI='' NODE_ENV=production yarn build
|
||||
- name: Build Electron app for Mac (with Node v22)
|
||||
run: yarn electron:build:mac
|
||||
- name: List dist directory
|
||||
@@ -113,10 +107,12 @@ jobs:
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Verify Node.js version consistency
|
||||
run: yarn verify-node
|
||||
- name: Install dependencies (with Node v22) # --network-timeout and --network-concurrency are yarn v1 flags.
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000 --network-concurrency 1
|
||||
- name: Build React app (with Node v22)
|
||||
run: yarn build
|
||||
run: npx cross-env NODE_ENV=production yarn build
|
||||
- name: Build Electron app for Windows (with Node v22)
|
||||
run: yarn electron:build:windows
|
||||
- name: List dist directory
|
||||
@@ -151,14 +147,16 @@ jobs:
|
||||
gradle-version: 8.9
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
- name: Verify Node.js version consistency
|
||||
run: yarn verify-node
|
||||
- run: sudo apt install -y apksigner zipalign
|
||||
|
||||
# install all dependencies (including devDependencies needed for React build)
|
||||
- name: Install dependencies (with Node v20)
|
||||
- name: Install dependencies (with Node v22)
|
||||
run: yarn install --frozen-lockfile --ignore-engines
|
||||
# build react app
|
||||
- run: CI='' yarn build
|
||||
- run: CI='' NODE_ENV=production yarn build
|
||||
# set android versionCode and versionName
|
||||
- run: sed -i "s/versionCode 1/versionCode $(git tag | wc -l)/" ./android/app/build.gradle
|
||||
- run: sed -i "s/versionName \"1.0\"/versionName \"$(node -e "console.log(require('./package.json').version)")\"/" ./android/app/build.gradle
|
||||
|
||||
@@ -59,12 +59,12 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=false vite build",
|
||||
"build:preload": "vite build --config electron/vite.preload.config.js",
|
||||
"build-netlify": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" PUBLIC_URL=./ GENERATE_SOURCEMAP=true VITE_COMMIT_REF=$COMMIT_REF CI='' vite build",
|
||||
"build": "cross-env NODE_ENV=production PUBLIC_URL=./ GENERATE_SOURCEMAP=false vite build",
|
||||
"build:preload": "cross-env NODE_ENV=production vite build --config electron/vite.preload.config.js",
|
||||
"build-netlify": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" NODE_ENV=production PUBLIC_URL=./ GENERATE_SOURCEMAP=true VITE_COMMIT_REF=$COMMIT_REF CI='' vite build",
|
||||
"test": "vitest",
|
||||
"preview": "vite preview",
|
||||
"analyze-bundle": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=true vite build && npx source-map-explorer 'build/assets/*.js'",
|
||||
"analyze-bundle": "cross-env NODE_ENV=production PUBLIC_URL=./ GENERATE_SOURCEMAP=true vite build && npx source-map-explorer 'build/assets/*.js'",
|
||||
"electron": "yarn build:preload && cross-env ELECTRON_IS_DEV=1 yarn electron:before && cross-env ELECTRON_IS_DEV=1 electron .",
|
||||
"electron:no-delete-data": "yarn electron:before:download-ipfs && electron .",
|
||||
"electron:start": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron\"",
|
||||
|
||||
1
src/components/info-tooltip/index.ts
Normal file
1
src/components/info-tooltip/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './info-tooltip';
|
||||
78
src/components/info-tooltip/info-tooltip.module.css
Normal file
78
src/components/info-tooltip/info-tooltip.module.css
Normal file
@@ -0,0 +1,78 @@
|
||||
.tooltipIcon {
|
||||
cursor: help;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
/* Just handles positioning from floating-ui */
|
||||
z-index: 100;
|
||||
/* Default triangle position as fallback */
|
||||
--triangle-left-border: 8px;
|
||||
--triangle-left-fill: 9px;
|
||||
}
|
||||
|
||||
.tooltipInner {
|
||||
/* Enhanced animation with both opacity and transform */
|
||||
animation: tooltipFadeIn 0.1s ease-out forwards;
|
||||
/* Move all visual styles here so they animate together */
|
||||
background: var(--background);
|
||||
color: var(--info-tooltip-text);
|
||||
border: 1px solid var(--info-tooltip-border-color);
|
||||
padding: 3px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.tooltip.exiting .tooltipInner {
|
||||
animation: tooltipFadeOut 0.1s ease-in forwards;
|
||||
}
|
||||
|
||||
/* Fade in animation: slides down from above while fading in */
|
||||
@keyframes tooltipFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fade out animation: slides up while fading out */
|
||||
@keyframes tooltipFadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
white-space: pre-wrap;
|
||||
font: 10px verdana, arial, helvetica, sans-serif;
|
||||
color: var(--info-tooltip-text);
|
||||
margin: .5em;
|
||||
}
|
||||
|
||||
.tooltipInner::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: var(--triangle-left-border);
|
||||
border: 10px solid transparent;
|
||||
border-bottom-color: var(--info-tooltip-border-color);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tooltipInner::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
left: var(--triangle-left-fill);
|
||||
border: 9px solid transparent;
|
||||
border-bottom-color: var(--background);
|
||||
z-index: 2;
|
||||
}
|
||||
117
src/components/info-tooltip/info-tooltip.tsx
Normal file
117
src/components/info-tooltip/info-tooltip.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { useState, useLayoutEffect, useRef } from 'react';
|
||||
import { useFloating, autoUpdate, offset, shift, size, useHover, useFocus, useDismiss, useRole, useInteractions, FloatingPortal, safePolygon } from '@floating-ui/react';
|
||||
import styles from './info-tooltip.module.css';
|
||||
|
||||
interface InfoTooltipProps {
|
||||
content: string;
|
||||
showTooltip?: boolean;
|
||||
}
|
||||
|
||||
const InfoTooltip = ({ content, showTooltip = true }: InfoTooltipProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isExiting, setIsExiting] = useState(false);
|
||||
const exitTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
// Handle opening and closing with animations
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (open) {
|
||||
// Opening: show immediately and set open state
|
||||
setIsVisible(true);
|
||||
setIsOpen(true);
|
||||
setIsExiting(false);
|
||||
// Clear any pending exit timeout
|
||||
if (exitTimeoutRef.current) {
|
||||
clearTimeout(exitTimeoutRef.current);
|
||||
exitTimeoutRef.current = undefined;
|
||||
}
|
||||
} else {
|
||||
// Closing: start exit animation, then hide after animation completes
|
||||
setIsOpen(false);
|
||||
setIsExiting(true);
|
||||
|
||||
// Remove from DOM after exit animation completes (200ms)
|
||||
exitTimeoutRef.current = setTimeout(() => {
|
||||
setIsVisible(false);
|
||||
setIsExiting(false);
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
open: isOpen,
|
||||
onOpenChange: handleOpenChange,
|
||||
placement: 'bottom-start',
|
||||
whileElementsMounted: autoUpdate,
|
||||
middleware: [
|
||||
offset({ mainAxis: 12 }), // 5px lower (7+5=12), 10px to the left (-10)
|
||||
shift(),
|
||||
size({
|
||||
apply({ availableWidth, elements }) {
|
||||
Object.assign(elements.floating.style, {
|
||||
maxWidth: `${Math.min(availableWidth, 460)}px`, // 35em ≈ 560px with 20px right padding
|
||||
});
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Calculate triangle position dynamically
|
||||
useLayoutEffect(() => {
|
||||
if (isOpen && refs.reference.current && refs.floating.current) {
|
||||
const referenceRect = refs.reference.current.getBoundingClientRect();
|
||||
const floatingRect = refs.floating.current.getBoundingClientRect();
|
||||
|
||||
// Calculate the horizontal center of the reference element relative to the floating element
|
||||
const referenceCenterX = referenceRect.left + referenceRect.width / 2;
|
||||
const floatingLeftX = floatingRect.left;
|
||||
const triangleLeft = referenceCenterX - floatingLeftX;
|
||||
|
||||
// Set CSS custom properties for triangle positioning
|
||||
// Keep the 1px offset between border and fill triangles for proper border effect
|
||||
refs.floating.current.style.setProperty('--triangle-left-border', `${Math.max(0, triangleLeft - 2)}px`);
|
||||
refs.floating.current.style.setProperty('--triangle-left-fill', `${Math.max(1, triangleLeft - 1)}px`);
|
||||
}
|
||||
}, [isOpen, refs.reference, refs.floating, floatingStyles]);
|
||||
|
||||
const hover = useHover(context, {
|
||||
move: false,
|
||||
delay: { open: 200, close: 1000 },
|
||||
handleClose: safePolygon(),
|
||||
});
|
||||
const focus = useFocus(context);
|
||||
const dismiss = useDismiss(context);
|
||||
const role = useRole(context, { role: 'tooltip' });
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus, dismiss, role]);
|
||||
|
||||
// Clean up timeout on unmount
|
||||
useLayoutEffect(() => {
|
||||
return () => {
|
||||
if (exitTimeoutRef.current) {
|
||||
clearTimeout(exitTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<sup className={styles.tooltipIcon} ref={refs.setReference} {...getReferenceProps()}>
|
||||
[?]
|
||||
</sup>
|
||||
{showTooltip && (
|
||||
<FloatingPortal>
|
||||
{isVisible && (
|
||||
<div className={`${styles.tooltip} ${isExiting ? styles.exiting : ''}`} ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>
|
||||
<div className={styles.tooltipInner}>
|
||||
<p className={styles.tooltipContent}>{content}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</FloatingPortal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoTooltip;
|
||||
@@ -54,7 +54,9 @@ const ModeratorsList = ({ roles }: { roles: Record<string, Role> }) => {
|
||||
<div className={styles.listTitle}>{t('moderators')}</div>
|
||||
<ul className={`${styles.listContent} ${styles.modsList}`}>
|
||||
{rolesList.map(({ address }, index) => (
|
||||
<li key={index}>u/{Plebbit.getShortAddress(address)}</li>
|
||||
<li key={index} onClick={() => window.alert('Direct profile links are not supported yet.')}>
|
||||
u/{Plebbit.getShortAddress(address)}
|
||||
</li>
|
||||
))}
|
||||
{/* TODO: https://github.com/plebbit/seedit/issues/274
|
||||
<li className={styles.listMore}>{t('about_moderation')} »</li> */}
|
||||
|
||||
@@ -16,11 +16,19 @@ if (window.location.hostname.startsWith('p2p.')) {
|
||||
};
|
||||
}
|
||||
|
||||
const reloadSW = registerSW({
|
||||
registerSW({
|
||||
immediate: true,
|
||||
onNeedRefresh() {
|
||||
// Reload the page to load the new version
|
||||
reloadSW(true);
|
||||
// Use window.location.reload() as it's more reliable than reloadSW(true)
|
||||
if (!sessionStorage.getItem('sw-update-reload')) {
|
||||
sessionStorage.setItem('sw-update-reload', 'true');
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
onOfflineReady() {
|
||||
// Clear the reload flag when offline-ready (prevents loops)
|
||||
sessionStorage.removeItem('sw-update-reload');
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@ export const isUserOwner = (roles: Roles | undefined, userAddress: string | unde
|
||||
return roles[userAddress]?.role === 'owner';
|
||||
};
|
||||
|
||||
export const isUserOwnerOrAdmin = (roles: Roles | undefined, userAddress: string | undefined): boolean => {
|
||||
if (!roles || !userAddress) return false;
|
||||
const userRole = roles[userAddress]?.role;
|
||||
return userRole === 'owner' || userRole === 'admin';
|
||||
};
|
||||
|
||||
export const findSubplebbitCreator = (roles: Roles | undefined): string => {
|
||||
if (!roles) {
|
||||
return 'anonymous';
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
--green: #228822;
|
||||
--green-bright: rgb(76, 207, 92);
|
||||
--icon: #c6c6c6;
|
||||
--info-tooltip-text: #bfbfbf;
|
||||
--info-tooltip-border-color: #3e3e3e;
|
||||
--link: #bfbfbf;
|
||||
--link-primary: rgb(125, 175, 216);
|
||||
--link-visited: #757575;
|
||||
@@ -151,6 +153,8 @@
|
||||
--green: #228822;
|
||||
--green-bright: #3bc54c;
|
||||
--icon: #c6c6c6;
|
||||
--info-tooltip-text: #333;
|
||||
--info-tooltip-border-color: gray;
|
||||
--link: #0000ff;
|
||||
--link-primary: #369;
|
||||
--link-visited: #551a8b;
|
||||
|
||||
@@ -214,6 +214,11 @@ const Mod = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.feed}>
|
||||
{process.env.NODE_ENV !== 'production' && (
|
||||
<button className={styles.debugButton} onClick={reset}>
|
||||
Reset Feed
|
||||
</button>
|
||||
)}
|
||||
<Virtuoso
|
||||
increaseViewportBy={{ bottom: 1200, top: 600 }}
|
||||
totalCount={feed?.length || 0}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Fragment, useState } from 'react';
|
||||
import { Account, setAccount, useAccount } from '@plebbit/plebbit-react-hooks';
|
||||
import styles from './wallet-settings.module.css';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import InfoTooltip from '../../../components/info-tooltip';
|
||||
|
||||
interface Wallet {
|
||||
chainTicker: string;
|
||||
@@ -184,6 +185,9 @@ const CryptoWalletsForm = ({ account }: { account: Account | undefined }) => {
|
||||
i18nKey='add_wallet'
|
||||
components={{ 1: <button key={`addWalletButton-${walletsArray.length}`} onClick={() => setWalletsArray([...walletsArray, defaultWalletObject])} /> }}
|
||||
/>
|
||||
<InfoTooltip
|
||||
content={`Link crypto wallets to your account by proving ownership through signed messages. Communities can verify your token/NFT holdings to grant posting privileges or special access.`}
|
||||
/>
|
||||
</div>
|
||||
{walletsInputs}
|
||||
</>
|
||||
|
||||
@@ -16,6 +16,7 @@ import Markdown from '../../components/markdown';
|
||||
import Embed from '../../components/post/embed';
|
||||
import { FormattingHelpTable } from '../../components/reply-form/reply-form';
|
||||
import styles from './submit-page.module.css';
|
||||
import InfoTooltip from '../../components/info-tooltip';
|
||||
|
||||
const isAndroid = Capacitor.getPlatform() === 'android';
|
||||
const isElectron = window.electronApi?.isElectron === true;
|
||||
@@ -75,7 +76,12 @@ const UrlField = () => {
|
||||
{url && isValidURL(url) ? (
|
||||
<div className={styles.mediaPreview}>{mediaError ? <span className={styles.mediaError}>{t('no_media_found')}</span> : mediaComponent}</div>
|
||||
) : (
|
||||
<div className={styles.description}>{t('submit_url_description')}</div>
|
||||
<div className={styles.description}>
|
||||
{t('submit_url_description')}
|
||||
<InfoTooltip
|
||||
content={`Seedit also supports links from the following sites: YouTube, Twitter/X, Reddit, Twitch, TikTok, Instagram, Odysee, Bitchute, Streamable, Spotify, and SoundCloud.`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
useSubplebbit,
|
||||
useSubscribe,
|
||||
} from '@plebbit/plebbit-react-hooks';
|
||||
import { isUserOwner, Roles } from '../../lib/utils/user-utils';
|
||||
import { isUserOwnerOrAdmin, Roles } from '../../lib/utils/user-utils';
|
||||
import { isValidURL } from '../../lib/utils/url-utils';
|
||||
import { isCreateSubplebbitView, isSubplebbitSettingsView } from '../../lib/utils/view-utils';
|
||||
import useSubplebbitSettingsStore from '../../stores/use-subplebbit-settings-store';
|
||||
@@ -374,7 +374,14 @@ const SubplebbitSettings = () => {
|
||||
navigate('/', { replace: true });
|
||||
}
|
||||
|
||||
const isReadOnly = (!settings && isInSubplebbitSettingsView) || (!isConnectedToRpc && isInCreateSubplebbitView);
|
||||
const userAddress = account?.author?.address;
|
||||
const userIsOwnerOrAdmin = isUserOwnerOrAdmin(roles, userAddress);
|
||||
|
||||
// General fields can be edited by owners/admins even without RPC connection
|
||||
const isReadOnly = (!settings && isInSubplebbitSettingsView && !userIsOwnerOrAdmin) || (!isConnectedToRpc && isInCreateSubplebbitView && !userIsOwnerOrAdmin);
|
||||
|
||||
// Challenges are always read-only when not connected to RPC
|
||||
const isChallengesReadOnly = !isConnectedToRpc;
|
||||
|
||||
const { publishSubplebbitEditOptions, resetSubplebbitSettingsStore, setSubplebbitSettingsStore, title: storeTitle } = useSubplebbitSettingsStore();
|
||||
const { error: publishSubplebbitEditError, publishSubplebbitEdit } = usePublishSubplebbitEdit(publishSubplebbitEditOptions);
|
||||
@@ -551,8 +558,6 @@ const SubplebbitSettings = () => {
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
const userAddress = account?.author?.address;
|
||||
const userIsOwner = isUserOwner(roles, userAddress);
|
||||
const loadingStateString = useStateString(subplebbit);
|
||||
|
||||
if (!hasLoaded && !isInCreateSubplebbitView) {
|
||||
@@ -577,14 +582,17 @@ const SubplebbitSettings = () => {
|
||||
<Sidebar subplebbit={subplebbit} />
|
||||
</div>
|
||||
)}
|
||||
{isReadOnly && !userIsOwner && <div className={styles.infobar}>{t('owner_settings_notice')}</div>}
|
||||
{isReadOnly && !userIsOwnerOrAdmin && <div className={styles.infobar}>{t('owner_settings_notice')}</div>}
|
||||
{!isReadOnly && userIsOwnerOrAdmin && !isConnectedToRpc && (
|
||||
<div className={styles.infobar}>editing anti-spam challenges requires running a full node (or connecting via RPC)</div>
|
||||
)}
|
||||
<Title isReadOnly={isReadOnly} />
|
||||
<Description isReadOnly={isReadOnly} />
|
||||
{!isInCreateSubplebbitView && <Address isReadOnly={isReadOnly} />}
|
||||
<Logo isReadOnly={isReadOnly} />
|
||||
<Rules isReadOnly={isReadOnly} />
|
||||
<Moderators isReadOnly={isReadOnly} />
|
||||
<Challenges isReadOnly={isReadOnly} readOnlyChallenges={subplebbit?.challenges} challengeNames={challengeNames} challengesSettings={rpcChallenges} />
|
||||
<Challenges isReadOnly={isChallengesReadOnly} readOnlyChallenges={subplebbit?.challenges} challengeNames={challengeNames} challengesSettings={rpcChallenges} />
|
||||
{!isInCreateSubplebbitView && <JSONSettings isReadOnly={isReadOnly} />}
|
||||
<div className={styles.saveOptions}>
|
||||
{!isInCreateSubplebbitView && !isReadOnly && (
|
||||
|
||||
@@ -7,6 +7,7 @@ import { VitePWA } from 'vite-plugin-pwa';
|
||||
import reactScan from '@react-scan/vite-plugin-react-scan';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -19,7 +20,8 @@ export default defineConfig({
|
||||
]
|
||||
}
|
||||
}),
|
||||
!isProduction && reactScan({
|
||||
// Only include React Scan in development mode - never in production builds
|
||||
(isDevelopment || (!isProduction && process.env.NODE_ENV !== 'production')) && reactScan({
|
||||
showToolbar: true,
|
||||
playSound: true,
|
||||
}),
|
||||
@@ -86,12 +88,13 @@ export default defineConfig({
|
||||
navigateFallbackDenylist: [/^\/api/, /^\/_(.*)/],
|
||||
|
||||
runtimeCaching: [
|
||||
// Fix index.html not refreshing on new versions
|
||||
// Always get fresh HTML from network first
|
||||
{
|
||||
urlPattern: ({ url }) => url.pathname === '/' || url.pathname === '/index.html',
|
||||
handler: 'StaleWhileRevalidate',
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'html-cache'
|
||||
cacheName: 'html-cache',
|
||||
networkTimeoutSeconds: 3
|
||||
}
|
||||
},
|
||||
// PNG caching
|
||||
|
||||
Reference in New Issue
Block a user