Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c68acfd78b | ||
|
|
10ea48bf04 |
No files matched your search
@@ -87,7 +87,9 @@ public static class EmbeddedFrontend
|
||||
? "public, max-age=31536000, immutable"
|
||||
: "no-store";
|
||||
|
||||
if (path.Equals("index.html", StringComparison.Ordinal))
|
||||
// Dedicated workers enforce the CSP returned with their own script response.
|
||||
// Give bundled JS the same app policy so the crypto worker can call the same-origin API.
|
||||
if (path.Equals("index.html", StringComparison.Ordinal) || path.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
|
||||
context.Response.Headers["Content-Security-Policy"] = contentSecurityPolicy;
|
||||
|
||||
if (!HttpMethods.IsHead(context.Request.Method))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { supportsApiContract } from '../contract';
|
||||
|
||||
export type ApiError = Error & { status?: number; code?: string };
|
||||
let apiBase = '/api/v1';
|
||||
let apiBase = new URL('/api/v1', self.location.origin).toString();
|
||||
let contractCheck: Promise<void> | undefined;
|
||||
|
||||
export function setApiBase(value: string): void {
|
||||
@@ -11,7 +11,10 @@ export function setApiBase(value: string): void {
|
||||
}
|
||||
|
||||
export const fail = (status: number, code: string): ApiError => Object.assign(new Error(code), { status, code });
|
||||
export const apiPath = (path: string): string => `${apiBase.replace(/\/$/, '')}${path}`;
|
||||
export const apiPath = (path: string): string => new URL(
|
||||
`${apiBase.replace(/\/$/, '')}${path}`,
|
||||
self.location.origin,
|
||||
).toString();
|
||||
|
||||
function ensureApiContract(): Promise<void> {
|
||||
contractCheck ??= fetch(apiPath('/info'), {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useGlobalPaste } from '@/lib/useGlobalPaste';
|
||||
import { useChannels } from '@/store/channels';
|
||||
import { useShares } from '@/store/shares';
|
||||
import { solveCreationCaptcha } from '@/core-bridge/captcha';
|
||||
import { errorMessage } from '@/core-bridge/errors';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { ChannelList } from './ChannelList';
|
||||
import { CreateSheet } from './CreateSheet';
|
||||
@@ -22,7 +23,7 @@ export function Home() {
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [scanOpen, setScanOpen] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [failed, setFailed] = useState(false);
|
||||
const [failedMessage, setFailedMessage] = useState('');
|
||||
const [shareText, setShareText] = useState('');
|
||||
const [shareFiles, setShareFiles] = useState<File[]>([]);
|
||||
|
||||
@@ -32,15 +33,15 @@ export function Home() {
|
||||
async (p: ClipboardPayload) => {
|
||||
if (busy || (p.files.length === 0 && !p.text.trim())) return false;
|
||||
setBusy(true);
|
||||
setFailed(false);
|
||||
setFailedMessage('');
|
||||
try {
|
||||
const captchaToken = await solveCreationCaptcha();
|
||||
open(await createDraft(p, captchaToken));
|
||||
setShareFiles([]);
|
||||
setShareText('');
|
||||
return true;
|
||||
} catch {
|
||||
setFailed(true);
|
||||
} catch (error) {
|
||||
setFailedMessage(errorMessage(error));
|
||||
return false;
|
||||
} finally {
|
||||
setBusy(false);
|
||||
@@ -71,7 +72,7 @@ export function Home() {
|
||||
onFilesChange={setShareFiles}
|
||||
onShare={share}
|
||||
/>
|
||||
{failed ? <p role="alert" className={styles.error}>创建分享失败,请重试。</p> : null}
|
||||
{failedMessage ? <p role="alert" className={styles.error}>创建分享失败:{failedMessage}</p> : null}
|
||||
|
||||
<ChannelList kind="share" title="我的分享" onOpen={open} />
|
||||
|
||||
|
||||
@@ -58,8 +58,7 @@
|
||||
}
|
||||
|
||||
.editor:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.editor::placeholder {
|
||||
|
||||
Reference in new issue
Block a user