feat(md-deck): Add fallback copy dialog for clipboard failure
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { Show, createSignal, onMount } from "solid-js";
|
||||||
|
import { Portal } from "solid-js/web";
|
||||||
import type { DeckStore } from "./hooks/deckStore";
|
import type { DeckStore } from "./hooks/deckStore";
|
||||||
|
|
||||||
export interface DeckHeaderProps {
|
export interface DeckHeaderProps {
|
||||||
@@ -9,8 +11,28 @@ export interface DeckHeaderProps {
|
|||||||
*/
|
*/
|
||||||
export function DeckHeader(props: DeckHeaderProps) {
|
export function DeckHeader(props: DeckHeaderProps) {
|
||||||
const { store } = props;
|
const { store } = props;
|
||||||
|
const [showCopyFallback, setShowCopyFallback] = createSignal(false);
|
||||||
|
const [fallbackCode, setFallbackCode] = createSignal("");
|
||||||
|
let textareaRef: HTMLTextAreaElement | undefined;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (showCopyFallback() && textareaRef) {
|
||||||
|
textareaRef.focus();
|
||||||
|
textareaRef.select();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
store.actions.copyCode((code) => {
|
||||||
|
setFallbackCode(code);
|
||||||
|
setShowCopyFallback(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeFallback = () => setShowCopyFallback(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div class="flex items-center gap-2 border-b border-gray-200 pb-2 mb-4">
|
<div class="flex items-center gap-2 border-b border-gray-200 pb-2 mb-4">
|
||||||
{/* 编辑按钮 */}
|
{/* 编辑按钮 */}
|
||||||
<button
|
<button
|
||||||
@@ -34,11 +56,48 @@ export function DeckHeader(props: DeckHeaderProps) {
|
|||||||
|
|
||||||
{/* 复制代码按钮 */}
|
{/* 复制代码按钮 */}
|
||||||
<button
|
<button
|
||||||
onClick={() => store.actions.copyCode()}
|
onClick={handleCopy}
|
||||||
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-purple-100 text-purple-600 hover:bg-purple-200"
|
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-purple-100 text-purple-600 hover:bg-purple-200"
|
||||||
>
|
>
|
||||||
📋 复制代码
|
📋 复制代码
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 剪贴板不可用时的手动复制弹窗 */}
|
||||||
|
<Show when={showCopyFallback()}>
|
||||||
|
<Portal>
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 bg-black/50 z-60 flex items-center justify-center p-4"
|
||||||
|
onClick={closeFallback}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-white rounded-lg shadow-xl w-full max-w-lg p-4"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h3 class="font-bold mb-2">复制代码</h3>
|
||||||
|
<p class="text-sm text-gray-600 mb-3">
|
||||||
|
自动复制失败(当前站点可能不是 HTTPS),请手动复制下面的代码:
|
||||||
|
</p>
|
||||||
|
<textarea
|
||||||
|
ref={textareaRef}
|
||||||
|
readonly
|
||||||
|
value={fallbackCode()}
|
||||||
|
class="w-full h-40 border border-gray-300 rounded px-3 py-2 text-xs font-mono bg-gray-50 resize-none"
|
||||||
|
onFocus={(e) => e.currentTarget.select()}
|
||||||
|
onClick={(e) => e.currentTarget.select()}
|
||||||
|
/>
|
||||||
|
<div class="flex justify-end gap-2 mt-3">
|
||||||
|
<button
|
||||||
|
onClick={closeFallback}
|
||||||
|
class="px-3 py-1.5 rounded text-sm font-medium cursor-pointer bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export interface DeckActions {
|
|||||||
clearError: () => void;
|
clearError: () => void;
|
||||||
|
|
||||||
generateCode: (backLayersStr?: string) => string;
|
generateCode: (backLayersStr?: string) => string;
|
||||||
copyCode: (backLayersStr?: string) => Promise<void>;
|
copyCode: (fallback?: (code: string) => void) => Promise<void>;
|
||||||
|
|
||||||
setExporting: (exporting: boolean) => void;
|
setExporting: (exporting: boolean) => void;
|
||||||
exportDeck: () => void;
|
exportDeck: () => void;
|
||||||
@@ -514,15 +514,19 @@ export function createDeckStore(initialSrc: string = ""): DeckStore {
|
|||||||
return parts.join("");
|
return parts.join("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyCode = async (backLayersStr?: string) => {
|
const copyCode = async (fallback?: (code: string) => void) => {
|
||||||
const code = generateCode(backLayersStr);
|
const code = generateCode();
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(code);
|
await navigator.clipboard.writeText(code);
|
||||||
alert("已复制到剪贴板!");
|
alert("已复制到剪贴板!");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("复制失败:", err);
|
console.error("复制失败:", err);
|
||||||
|
if (fallback) {
|
||||||
|
fallback(code);
|
||||||
|
} else {
|
||||||
alert("复制失败,请手动复制");
|
alert("复制失败,请手动复制");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setExporting = (exporting: boolean) =>
|
const setExporting = (exporting: boolean) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user