Improve clipboard copy fallback
Avoid stale page scripts and select the returned URL when browser clipboard APIs are blocked on plain HTTP access.
This commit is contained in:
+48
-6
@@ -166,6 +166,8 @@ class PublicPasteHandler(BaseHTTPRequestHandler):
|
|||||||
self.send_response(HTTPStatus.OK)
|
self.send_response(HTTPStatus.OK)
|
||||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||||
self.send_header("Content-Length", str(len(body)))
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.send_header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
|
||||||
|
self.send_header("Pragma", "no-cache")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(body)
|
self.wfile.write(body)
|
||||||
|
|
||||||
@@ -174,6 +176,7 @@ class PublicPasteHandler(BaseHTTPRequestHandler):
|
|||||||
self.send_response(status)
|
self.send_response(status)
|
||||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||||
self.send_header("Content-Length", str(len(body)))
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(body)
|
self.wfile.write(body)
|
||||||
|
|
||||||
@@ -228,8 +231,9 @@ INDEX_HTML = f"""<!doctype html>
|
|||||||
.row {{ display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 14px; }}
|
.row {{ display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 14px; }}
|
||||||
.status {{ min-height: 24px; color: #59636e; }}
|
.status {{ min-height: 24px; color: #59636e; }}
|
||||||
.result {{ display: none; margin-top: 18px; padding: 14px; border: 1px solid #d8dee6; border-radius: 12px; background: #f9fafb; word-break: break-all; }}
|
.result {{ display: none; margin-top: 18px; padding: 14px; border: 1px solid #d8dee6; border-radius: 12px; background: #f9fafb; word-break: break-all; }}
|
||||||
|
.urlbox {{ box-sizing: border-box; width: 100%; margin-top: 8px; border: 1px solid #ccd3da; border-radius: 10px; padding: 10px 12px; font: 15px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }}
|
||||||
.error {{ color: #b42318; }}
|
.error {{ color: #b42318; }}
|
||||||
@media (prefers-color-scheme: dark) {{ body {{ background: #111827; color: #f9fafb; }} main {{ background: #1f2937; }} p,.status {{ color: #cbd5e1; }} textarea,.result {{ background: #111827; color: #f9fafb; border-color: #374151; }} }}
|
@media (prefers-color-scheme: dark) {{ body {{ background: #111827; color: #f9fafb; }} main {{ background: #1f2937; }} p,.status {{ color: #cbd5e1; }} textarea,.result,.urlbox {{ background: #111827; color: #f9fafb; border-color: #374151; }} }}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -239,12 +243,13 @@ INDEX_HTML = f"""<!doctype html>
|
|||||||
<textarea id="text" maxlength="{MAX_REQUEST_BYTES}" placeholder="在这里输入文本..."></textarea>
|
<textarea id="text" maxlength="{MAX_REQUEST_BYTES}" placeholder="在这里输入文本..."></textarea>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<button id="submit" type="button">完成</button>
|
<button id="submit" type="button">完成</button>
|
||||||
<button id="copy" type="button" style="display:none">再次复制链接</button>
|
<button id="copy" type="button" style="display:none">再次复制/选中链接</button>
|
||||||
<span id="status" class="status"></span>
|
<span id="status" class="status"></span>
|
||||||
</div>
|
</div>
|
||||||
<section id="result" class="result" aria-live="polite">
|
<section id="result" class="result" aria-live="polite">
|
||||||
<div>Provider: <strong id="provider"></strong></div>
|
<div>Provider: <strong id="provider"></strong></div>
|
||||||
<div>URL: <a id="url" href="#" target="_blank" rel="noopener noreferrer"></a></div>
|
<div>URL: <a id="url" href="#" target="_blank" rel="noopener noreferrer"></a></div>
|
||||||
|
<input id="urlInput" class="urlbox" readonly value="" aria-label="Paste URL">
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
@@ -255,6 +260,7 @@ INDEX_HTML = f"""<!doctype html>
|
|||||||
const resultEl = document.getElementById('result');
|
const resultEl = document.getElementById('result');
|
||||||
const providerEl = document.getElementById('provider');
|
const providerEl = document.getElementById('provider');
|
||||||
const urlEl = document.getElementById('url');
|
const urlEl = document.getElementById('url');
|
||||||
|
const urlInput = document.getElementById('urlInput');
|
||||||
let currentUrl = '';
|
let currentUrl = '';
|
||||||
|
|
||||||
function setStatus(message, isError = false) {{
|
function setStatus(message, isError = false) {{
|
||||||
@@ -262,16 +268,51 @@ INDEX_HTML = f"""<!doctype html>
|
|||||||
statusEl.className = isError ? 'status error' : 'status';
|
statusEl.className = isError ? 'status error' : 'status';
|
||||||
}}
|
}}
|
||||||
|
|
||||||
async function copyUrl() {{
|
function selectUrl() {{
|
||||||
if (!currentUrl) return;
|
urlInput.focus();
|
||||||
|
urlInput.select();
|
||||||
|
urlInput.setSelectionRange(0, urlInput.value.length);
|
||||||
|
}}
|
||||||
|
|
||||||
|
function legacyCopy() {{
|
||||||
|
selectUrl();
|
||||||
|
try {{
|
||||||
|
return document.execCommand('copy');
|
||||||
|
}} catch (err) {{
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
async function copyUrl({{ automatic = false }} = {{}}) {{
|
||||||
|
if (!currentUrl) return false;
|
||||||
|
|
||||||
|
// Async Clipboard only works in secure contexts in most browsers.
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {{
|
||||||
try {{
|
try {{
|
||||||
await navigator.clipboard.writeText(currentUrl);
|
await navigator.clipboard.writeText(currentUrl);
|
||||||
setStatus('链接已复制到剪贴板');
|
setStatus('链接已复制到剪贴板');
|
||||||
|
return true;
|
||||||
}} catch (err) {{
|
}} catch (err) {{
|
||||||
setStatus('复制失败,请手动复制链接', true);
|
// Fall through to execCommand.
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
// execCommand works in many HTTP/IP scenarios when called from a click.
|
||||||
|
if (legacyCopy()) {{
|
||||||
|
setStatus('链接已复制到剪贴板');
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
|
||||||
|
selectUrl();
|
||||||
|
setStatus(
|
||||||
|
automatic
|
||||||
|
? '浏览器拦截了自动复制,链接已选中,请按 Ctrl+C / ⌘+C 复制'
|
||||||
|
: '浏览器拦截了复制,链接已选中,请按 Ctrl+C / ⌘+C 复制',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
|
||||||
submit.addEventListener('click', async () => {{
|
submit.addEventListener('click', async () => {{
|
||||||
const value = text.value;
|
const value = text.value;
|
||||||
if (!value.trim()) {{
|
if (!value.trim()) {{
|
||||||
@@ -294,10 +335,11 @@ INDEX_HTML = f"""<!doctype html>
|
|||||||
providerEl.textContent = data.provider || '';
|
providerEl.textContent = data.provider || '';
|
||||||
urlEl.textContent = currentUrl;
|
urlEl.textContent = currentUrl;
|
||||||
urlEl.href = currentUrl;
|
urlEl.href = currentUrl;
|
||||||
|
urlInput.value = currentUrl;
|
||||||
resultEl.style.display = 'block';
|
resultEl.style.display = 'block';
|
||||||
copy.style.display = 'inline-block';
|
copy.style.display = 'inline-block';
|
||||||
setStatus('提交成功,正在复制链接...');
|
setStatus('提交成功,正在复制链接...');
|
||||||
await copyUrl();
|
await copyUrl({{ automatic: true }});
|
||||||
}} catch (err) {{
|
}} catch (err) {{
|
||||||
setStatus(err instanceof Error ? err.message : '提交失败', true);
|
setStatus(err instanceof Error ? err.message : '提交失败', true);
|
||||||
}} finally {{
|
}} finally {{
|
||||||
|
|||||||
Reference in New Issue
Block a user