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";
|
||||
|
||||
export interface DeckHeaderProps {
|
||||
@@ -9,36 +11,93 @@ export interface DeckHeaderProps {
|
||||
*/
|
||||
export function DeckHeader(props: DeckHeaderProps) {
|
||||
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 (
|
||||
<div class="flex items-center gap-2 border-b border-gray-200 pb-2 mb-4">
|
||||
{/* 编辑按钮 */}
|
||||
<button
|
||||
onClick={() => store.actions.setIsEditing(!store.state.isEditing)}
|
||||
class={`px-3 py-1 rounded text-sm font-medium transition-colors cursor-pointer ${
|
||||
store.state.isEditing && !store.state.fixed
|
||||
? "bg-blue-100 text-blue-600"
|
||||
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
{store.state.isEditing ? "✓ 编辑中" : "✏️ 编辑"}
|
||||
</button>
|
||||
<>
|
||||
<div class="flex items-center gap-2 border-b border-gray-200 pb-2 mb-4">
|
||||
{/* 编辑按钮 */}
|
||||
<button
|
||||
onClick={() => store.actions.setIsEditing(!store.state.isEditing)}
|
||||
class={`px-3 py-1 rounded text-sm font-medium transition-colors cursor-pointer ${
|
||||
store.state.isEditing && !store.state.fixed
|
||||
? "bg-blue-100 text-blue-600"
|
||||
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
{store.state.isEditing ? "✓ 编辑中" : "✏️ 编辑"}
|
||||
</button>
|
||||
|
||||
{/* 导出 PDF 按钮 */}
|
||||
<button
|
||||
onClick={() => store.actions.exportDeck()}
|
||||
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-green-100 text-green-600 hover:bg-green-200"
|
||||
>
|
||||
📥 导出 PDF
|
||||
</button>
|
||||
{/* 导出 PDF 按钮 */}
|
||||
<button
|
||||
onClick={() => store.actions.exportDeck()}
|
||||
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-green-100 text-green-600 hover:bg-green-200"
|
||||
>
|
||||
📥 导出 PDF
|
||||
</button>
|
||||
|
||||
{/* 复制代码按钮 */}
|
||||
<button
|
||||
onClick={() => store.actions.copyCode()}
|
||||
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>
|
||||
</div>
|
||||
{/* 复制代码按钮 */}
|
||||
<button
|
||||
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"
|
||||
>
|
||||
📋 复制代码
|
||||
</button>
|
||||
</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;
|
||||
|
||||
generateCode: (backLayersStr?: string) => string;
|
||||
copyCode: (backLayersStr?: string) => Promise<void>;
|
||||
copyCode: (fallback?: (code: string) => void) => Promise<void>;
|
||||
|
||||
setExporting: (exporting: boolean) => void;
|
||||
exportDeck: () => void;
|
||||
@@ -514,14 +514,18 @@ export function createDeckStore(initialSrc: string = ""): DeckStore {
|
||||
return parts.join("");
|
||||
};
|
||||
|
||||
const copyCode = async (backLayersStr?: string) => {
|
||||
const code = generateCode(backLayersStr);
|
||||
const copyCode = async (fallback?: (code: string) => void) => {
|
||||
const code = generateCode();
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
alert("已复制到剪贴板!");
|
||||
} catch (err) {
|
||||
console.error("复制失败:", err);
|
||||
alert("复制失败,请手动复制");
|
||||
if (fallback) {
|
||||
fallback(code);
|
||||
} else {
|
||||
alert("复制失败,请手动复制");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user