1import { ShareCode } from '../utils/share_code.js'; 2 3export class ShareModule { 4 constructor() { 5 this.modal = document.getElementById('share-modal'); 6 this.preview = document.getElementById('card-preview'); 7 this.imageEl = document.getElementById('card-image'); 8 this.promptEl = document.getElementById('card-prompt-text'); 9 this.authorEl = document.getElementById('card-author-name'); 10 this.urlEl = document.getElementById('card-url-text'); 11 this.qrContainer = document.getElementById('card-qr-code'); 12 13 this.authorInput = document.getElementById('share-author-input'); 14 this.downloadBtn = document.getElementById('share-download-btn'); 15 this.copyBtn = document.getElementById('share-copy-btn'); 16 this.closeBtn = document.getElementById('share-close-btn'); 17 18 this.currentImageId = null; 19 20 this.init(); 21 } 22 23 init() { 24 if (!this.modal) return; 25 26 // ç»å®äºä»¶ 27 this.closeBtn.addEventListener('click', () => this.hide()); 28 this.modal.addEventListener('click', (e) => { 29 if (e.target === this.modal) this.hide(); 30 }); 31 32 this.authorInput.addEventListener('input', (e) => { 33 this.updateAuthorDisplay(e.target.value.trim() || 'Anonymous', this.currentInviteCode); 34 }); 35 36 this.downloadBtn.addEventListener('click', () => this.downloadCard()); 37 if (this.copyBtn) { 38 this.copyBtn.addEventListener('click', () => this.copyCard()); 39 } 40 } 41 42 updateAuthorDisplay(name, inviteCode) { 43 // ä¿åå½åç inviteCodeï¼ä»¥ä¾¿å¨ input äºä»¶ä¸ä½¿ç¨ 44 this.currentInviteCode = inviteCode; 45 46 this.authorEl.innerHTML = `@${name}`; 47 48 if (inviteCode) { 49 const codeSpan = document.createElement('span'); 50 codeSpan.className = 'text-slate-400 font-normal text-xs ml-2 tracking-wide'; 51 codeSpan.textContent = `é请ç : ${inviteCode}`; 52 this.authorEl.appendChild(codeSpan); 53 } 54 } 55 56 open(imageUrl, prompt, imageId, username, referenceImages = [], inviteCode = null) { 57 this.currentImageId = imageId; 58 59 // 1. 设置å 容 60 // 使ç¨ä»£çæç´æ¥è®¾ç½® src (注æè·¨åé®é¢ï¼html2canvas éè¦ useCORS: true 䏿å¡å¨æ¯æ) 61 // è¿éå设 imageUrl æ¯åæºçæè æ¯æ CORS 62 this.imageEl.crossOrigin = "anonymous"; 63 this.imageEl.src = imageUrl; 64 65 this.promptEl.textContent = this.truncatePrompt((prompt || 'No prompt provided.').trim(), 150); 66 67 // å¤çåè徿¾ç¤º 68 const refContainer = document.getElementById('card-ref-images'); 69 if (refContainer) { 70 if (referenceImages && referenceImages.length > 0) { 71 refContainer.innerHTML = ''; 72 referenceImages.forEach(ref => { 73 const img = document.createElement('img'); 74 img.src = ref; 75 img.crossOrigin = "anonymous"; 76 img.className = 'w-12 h-12 rounded object-cover border border-slate-200 shadow-sm bg-white'; 77 refContainer.appendChild(img); 78 }); 79 refContainer.parentElement.classList.remove('hidden'); 80 } else { 81 refContainer.parentElement.classList.add('hidden'); 82 } 83 } 84 85 // 设置é»è®¤ä½è å 86 const defaultName = username || 'Architect'; 87 this.authorInput.value = defaultName; 88 89 // æ´æ°ä½è åæ¾ç¤ºï¼å å«é请ç 90 this.updateAuthorDisplay(defaultName, inviteCode); 91 92 // 2. çæäºç»´ç (ä½¿ç¨ ShareCode æ··æ·) 93 this.qrContainer.innerHTML = ''; 94 95 const shareCode = ShareCode.encode(imageId); 96 const shareUrl = `${window.location.origin}/share/${shareCode}`; 97 98 // æ´æ°æ¾ç¤ºç龿¥ææ¬ (廿å议头ï¼ä¿æç¾è§) 99 if (this.urlEl) { 100 this.urlEl.textContent = shareUrl.replace(/^https?:\/\//, ''); 101 } 102 103 new QRCode(this.qrContainer, { 104 text: shareUrl, 105 width: 70, // å¯¹åº CSS çå é¨å°ºå¯¸ 106 height: 70, 107 colorDark : "#000000", 108 colorLight : "#ffffff", 109 correctLevel : QRCode.CorrectLevel.L 110 }); 111 112 // 3. æ¾ç¤º Modal 113 this.modal.classList.remove('hidden'); 114 } 115 116 hide() { 117 this.modal.classList.add('hidden'); 118 } 119 120 truncatePrompt(text, maxLength) { 121 if (!text) return ''; 122 if (text.length <= maxLength) return text; 123 return text.slice(0, maxLength) + '...'; 124 } 125 126 async copyCard() { 127 if (!this.preview) return; 128 129 const originalBtnText = this.copyBtn.innerHTML; 130 this.copyBtn.disabled = true; 131 this.copyBtn.innerHTML = `<svg class="animate-spin h-5 w-5 text-slate-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>`; 132 133 try { 134 // çå¾ å¾çå è½½ 135 if (!this.imageEl.complete) { 136 await new Promise((resolve) => { 137 this.imageEl.onload = resolve; 138 this.imageEl.onerror = resolve; 139 }); 140 } 141 142 const canvas = await html2canvas(this.preview, { 143 useCORS: true, 144 scale: 2, 145 backgroundColor: null, 146 logging: false 147 }); 148 149 canvas.toBlob(async (blob) => { 150 try { 151 await navigator.clipboard.write([ 152 new ClipboardItem({ 'image/png': blob }) 153 ]); 154 155 // æååé¦ 156 this.copyBtn.innerHTML = `<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
156<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg> <span>å·²å¤å¶</span>`; 157 setTimeout(() => { 158 this.copyBtn.disabled = false; 159 this.copyBtn.innerHTML = originalBtnText; 160 }, 2000); 161 } catch (err) { 162 console.error('Clipboard write failed:', err); 163 alert('å¤å¶å¤±è´¥ï¼æ¨çæµè§å¨å¯è½ä¸æ¯ææ¤åè½'); 164 this.copyBtn.disabled = false; 165 this.copyBtn.innerHTML = originalBtnText; 166 } 167 }); 168 169 } catch (error) { 170 console.error('Card copy generation failed:', error); 171 alert('å¡ççæå¤±è´¥ï¼è¯·éè¯'); 172 this.copyBtn.disabled = false; 173 this.copyBtn.innerHTML = originalBtnText; 174 } 175 } 176 177 async downloadCard() { 178 if (!this.preview) return; 179 180 const originalBtnText = this.downloadBtn.innerHTML; 181 this.downloadBtn.disabled = true; 182 this.downloadBtn.innerHTML = `<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> çæä¸...`; 183 184 try { 185 // çå¾ å¾çå è½½å®æï¼å¦ææ¯å设置 srcï¼ 186 if (!this.imageEl.complete) { 187 await new Promise((resolve) => { 188 this.imageEl.onload = resolve; 189 this.imageEl.onerror = resolve; // å³ä½¿å¤±è´¥ä¹ç»§ç» 190 }); 191 } 192 193 const canvas = await html2canvas(this.preview, { 194 useCORS: true, 195 scale: 2, // 髿¸ 196 backgroundColor: null, // éæèæ¯ 197 logging: false 198 }); 199 200 // 触åä¸è½½ 201 const link = document.createElement('a'); 202 link.download = `jzai-card-${this.currentImageId || Date.now()}.png`; 203 link.href = canvas.toDataURL('image/png'); 204 document.body.appendChild(link); 205 link.click(); 206 document.body.removeChild(link); 207 208 // å¯éï¼å ³éå¼¹çª 209 // this.hide(); 210 211 } catch (error) { 212 console.error('Card generation failed:', error); 213 alert('å¡ççæå¤±è´¥ï¼è¯·éè¯'); 214 } finally { 215 this.downloadBtn.disabled = false; 216 this.downloadBtn.innerHTML = originalBtnText; 217 } 218 } 219}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.