mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
Add isolated Pages demo mode with sample vault data
This commit is contained in:
@@ -19,6 +19,9 @@ interface RegisterValues {
|
||||
|
||||
interface AuthViewsProps {
|
||||
mode: 'login' | 'register' | 'locked';
|
||||
relaxedLoginInput?: boolean;
|
||||
authPlaceholder?: string;
|
||||
unlockPlaceholder?: string;
|
||||
pendingAction: 'login' | 'register' | 'unlock' | null;
|
||||
unlockReady: boolean;
|
||||
unlockPreparing: boolean;
|
||||
@@ -46,6 +49,7 @@ function PasswordField(props: {
|
||||
onInput: (v: string) => void;
|
||||
autoFocus?: boolean;
|
||||
autoComplete?: string;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
return (
|
||||
@@ -59,6 +63,7 @@ function PasswordField(props: {
|
||||
onInput={(e) => props.onInput((e.currentTarget as HTMLInputElement).value)}
|
||||
autoFocus={props.autoFocus}
|
||||
autoComplete={props.autoComplete}
|
||||
placeholder={props.placeholder}
|
||||
/>
|
||||
<button type="button" className="eye-btn" onClick={() => setShow((v) => !v)}>
|
||||
{show ? <EyeOff size={16} /> : <Eye size={16} />}
|
||||
@@ -90,6 +95,7 @@ export default function AuthViews(props: AuthViewsProps) {
|
||||
value={props.unlockPassword}
|
||||
autoFocus
|
||||
autoComplete="current-password"
|
||||
placeholder={props.unlockPlaceholder}
|
||||
onInput={props.onChangeUnlock}
|
||||
/>
|
||||
<div className="auth-support-row">
|
||||
@@ -217,9 +223,10 @@ export default function AuthViews(props: AuthViewsProps) {
|
||||
<span>{t('txt_email')}</span>
|
||||
<input
|
||||
className="input"
|
||||
type="email"
|
||||
type={props.relaxedLoginInput ? 'text' : 'email'}
|
||||
value={props.loginValues.email}
|
||||
autoComplete="username"
|
||||
placeholder={props.authPlaceholder}
|
||||
onInput={(e) => props.onChangeLogin({ ...props.loginValues, email: (e.currentTarget as HTMLInputElement).value })}
|
||||
/>
|
||||
</label>
|
||||
@@ -227,6 +234,7 @@ export default function AuthViews(props: AuthViewsProps) {
|
||||
label={t('txt_master_password')}
|
||||
value={props.loginValues.password}
|
||||
autoComplete="current-password"
|
||||
placeholder={props.authPlaceholder}
|
||||
onInput={(v) => props.onChangeLogin({ ...props.loginValues, password: v })}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { toBufferSource } from '@/lib/crypto';
|
||||
import { downloadBytesAsFile, readResponseBytesWithProgress } from '@/lib/download';
|
||||
import NotFoundPage from '@/components/NotFoundPage';
|
||||
import StandalonePageFrame from '@/components/StandalonePageFrame';
|
||||
import { getDemoPublicSend, IS_DEMO_MODE } from '@/lib/demo';
|
||||
import { t } from '@/lib/i18n';
|
||||
|
||||
interface PublicSendPageProps {
|
||||
@@ -108,6 +109,17 @@ export default function PublicSendPage(props: PublicSendPageProps) {
|
||||
setNotFound(false);
|
||||
setLoading(true);
|
||||
try {
|
||||
if (IS_DEMO_MODE) {
|
||||
const demoSend = getDemoPublicSend(props.accessId);
|
||||
if (!demoSend) {
|
||||
setNotFound(true);
|
||||
setSendData(null);
|
||||
return;
|
||||
}
|
||||
setSendData(demoSend);
|
||||
setNeedPassword(false);
|
||||
return;
|
||||
}
|
||||
if (!hasUsableSendKey(props.keyPart)) {
|
||||
setNotFound(true);
|
||||
setSendData(null);
|
||||
@@ -153,6 +165,11 @@ export default function PublicSendPage(props: PublicSendPageProps) {
|
||||
setDownloadPercent(null);
|
||||
setError('');
|
||||
try {
|
||||
if (IS_DEMO_MODE) {
|
||||
const bytes = new TextEncoder().encode('NodeWarden demo file Send.\nThis download is generated locally in demo mode.\n');
|
||||
downloadBytesAsFile(bytes, sendData.decFileName || sendData.file?.fileName || 'nodewarden-demo-send.txt', 'application/octet-stream');
|
||||
return;
|
||||
}
|
||||
const url = await accessPublicSendFile(sendData.id, sendData.file.id, props.keyPart, password || undefined);
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) throw new Error(t('txt_download_failed'));
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { firstCipherUri, hostFromUri, websiteIconUrl } from '@/lib/website-utils';
|
||||
|
||||
const ICON_LOAD_ROOT_MARGIN = '180px 0px';
|
||||
const SHOULD_LOAD_DEMO_BRAND_ICONS = __NODEWARDEN_DEMO__;
|
||||
|
||||
interface WebsiteIconProps {
|
||||
cipher: Cipher;
|
||||
@@ -24,6 +25,21 @@ export default function WebsiteIcon(props: WebsiteIconProps) {
|
||||
const [shouldLoad, setShouldLoad] = useState(() => (host ? getWebsiteIconStatus(host) === 'loaded' : true));
|
||||
const [status, setStatus] = useState(() => (host ? getWebsiteIconStatus(host) : 'idle'));
|
||||
const [imageUrl, setImageUrl] = useState(() => (host ? getWebsiteIconImageUrl(host) : ''));
|
||||
const [demoIconUrl, setDemoIconUrl] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!SHOULD_LOAD_DEMO_BRAND_ICONS || !host) {
|
||||
setDemoIconUrl('');
|
||||
return;
|
||||
}
|
||||
let disposed = false;
|
||||
void import('@/lib/demo-brand-icons').then(({ demoBrandIconUrl }) => {
|
||||
if (!disposed) setDemoIconUrl(demoBrandIconUrl(host));
|
||||
});
|
||||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, [host]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!host) {
|
||||
@@ -72,6 +88,7 @@ export default function WebsiteIcon(props: WebsiteIconProps) {
|
||||
}, [host, shouldLoad, status]);
|
||||
|
||||
useEffect(() => {
|
||||
if (demoIconUrl) return;
|
||||
if (!host || !src || !shouldLoad || status === 'loaded' || status === 'error') return;
|
||||
let disposed = false;
|
||||
void preloadWebsiteIcon(host, src).then((nextStatus) => {
|
||||
@@ -82,7 +99,21 @@ export default function WebsiteIcon(props: WebsiteIconProps) {
|
||||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, [host, src, shouldLoad, status]);
|
||||
}, [demoIconUrl, host, src, shouldLoad, status]);
|
||||
|
||||
if (demoIconUrl) {
|
||||
return (
|
||||
<span className="list-icon-stack" ref={nodeRef}>
|
||||
<img
|
||||
className="list-icon loaded"
|
||||
src={demoIconUrl}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (!host || status === 'error') {
|
||||
return <span className="list-icon-fallback">{props.fallback ?? <Globe size={18} />}</span>;
|
||||
@@ -103,3 +134,4 @@ export default function WebsiteIcon(props: WebsiteIconProps) {
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user