您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Export LearnableMeta maps to Anki txt files
当前为
// ==UserScript== // @name LearnableMeta to Anki Exporter // @namespace https://learnablemeta.com/ // @version 1.0.1 // @description Export LearnableMeta maps to Anki txt files // @match https://learnablemeta.com/maps/* // @grant GM_addStyle // @grant GM_download // @grant GM_setValue // @grant GM_getValue // @connect * // @run-at document-end // @author BennoGHG // @license MIT // ==/UserScript== (function() { 'use strict'; function $(s) { return document.querySelector(s); } function $$(s) { return Array.from(document.querySelectorAll(s)); } function sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); } // Enhanced CSS styles - Production Ready GM_addStyle([ '@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");', '#lm-window { position: fixed; top: 50px; left: calc(100vw - 380px); width: 360px; min-width: 320px; min-height: 450px; max-width: 600px; max-height: 80vh;', 'background: linear-gradient(135deg, #1e1e1e 0%, #2a2a2a 100%); color: #f0f0f0; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;', 'border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08);', 'z-index: 2147483647; overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);', 'backdrop-filter: blur(20px); border: 1px solid rgba(255,255,255,0.1); user-select: none; resize: none; }', '#lm-window.dragging { transition: none; cursor: move; }', '#lm-window.resizing { transition: none; }', '#lm-window.hidden { transform: translateX(420px) scale(0.9); opacity: 0; pointer-events: none; }', '#lm-header { background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); padding: 16px 20px;', 'cursor: move; user-select: none; display: flex; align-items: center; justify-content: space-between;', 'border-radius: 16px 16px 0 0; position: relative; box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2); }', '#lm-header:active { cursor: grabbing; }', '#lm-title { font-size: 15px; font-weight: 600; color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.2); }', '#lm-hide-btn { background: rgba(255,255,255,0.15); border: none; color: white; width: 28px; height: 28px;', 'border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center;', 'font-size: 14px; transition: all 0.2s ease; font-weight: 500; }', '#lm-hide-btn:hover { background: rgba(255,255,255,0.25); transform: scale(1.05); }', '#lm-content { padding: 20px; display: flex; flex-direction: column; gap: 16px; height: calc(100% - 64px); overflow-y: auto; }', '#lm-content::-webkit-scrollbar { width: 6px; }', '#lm-content::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 3px; }', '#lm-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }', '#lm-content::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.3); }', '#lm-show-btn { position: fixed; top: 50px; right: 20px; background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);', 'border: none; color: white; width: 48px; height: 48px; border-radius: 50%; cursor: pointer;', 'display: none; align-items: center; justify-content: center; font-size: 18px; z-index: 2147483646;', 'box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3); transition: all 0.3s ease;', 'border: 2px solid rgba(255,255,255,0.1); }', '#lm-show-btn:hover { transform: scale(1.05); box-shadow: 0 12px 30px rgba(59, 130, 246, 0.4); }', '.lm-section { background: rgba(255,255,255,0.03); padding: 16px; border-radius: 10px;', 'border: 1px solid rgba(255,255,255,0.08); backdrop-filter: blur(10px); }', '.lm-section label { font-size: 12px; margin-bottom: 8px; display: block; color: #a0a0a0; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; }', '.lm-section input[type=text] { width: 100%; padding: 10px 12px; border-radius: 6px;', 'border: 1px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.05); color: #f0f0f0;', 'box-sizing: border-box; font-family: Inter; transition: all 0.2s ease; font-size: 14px; }', '.lm-section input[type=text]:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); background: rgba(255,255,255,0.08); }', '.lm-section input[type=range] { width: 100%; margin: 8px 0; accent-color: #3b82f6; }', '.lm-button { padding: 10px 16px; border: none; border-radius: 8px; font-size: 12px; cursor: pointer;', 'margin-bottom: 8px; width: 100%; font-family: Inter; font-weight: 500; transition: all 0.2s ease;', 'text-transform: uppercase; letter-spacing: 0.5px; position: relative; overflow: hidden; }', '.lm-button.primary { background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); color: white; }', '.lm-button.primary:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(139, 92, 246, 0.3); }', '.lm-button.secondary { background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); color: white; }', '.lm-button.secondary:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(59, 130, 246, 0.3); }', '.lm-button.warning { background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: white; }', '.lm-button.warning:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(245, 158, 11, 0.3); }', '.lm-button:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; }', '.lm-progress-bar { width: 100%; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden; margin: 12px 0; }', '.lm-progress-fill { height: 100%; background: linear-gradient(90deg, #3b82f6, #8b5cf6); width: 0%; transition: width 0.3s ease; border-radius: 3px; }', '.lm-slider-container { display: flex; align-items: center; gap: 12px; margin-top: 8px; }', '.lm-slider-value { background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: white; padding: 4px 10px;', 'border-radius: 12px; font-size: 11px; font-weight: 600; min-width: 20px; text-align: center; }', '#lm-status { font-size: 11px; color: #10b981; background: rgba(16, 185, 129, 0.1); padding: 12px; border-radius: 8px;', 'border: 1px solid rgba(16, 185, 129, 0.2); font-weight: 500; }', '#lm-meta-count { font-size: 10px; color: #9ca3af; text-align: center; margin-top: 6px; }', '#lm-credits { font-size: 10px; color: #6b7280; text-align: center; padding: 12px 0 8px 0; border-top: 1px solid rgba(255,255,255,0.05); margin-top: 12px; opacity: 0.7; }', '.lm-resize-handle { position: absolute; background: transparent; transition: background 0.2s ease; }', '.lm-resize-se { bottom: 0; right: 0; width: 16px; height: 16px; cursor: se-resize; }', '.lm-resize-s { bottom: 0; left: 16px; right: 16px; height: 4px; cursor: s-resize; }', '.lm-resize-e { right: 0; top: 16px; bottom: 16px; width: 4px; cursor: e-resize; }', '.lm-resize-se:hover { background: rgba(59, 130, 246, 0.2); }', '.lm-resize-s:hover, .lm-resize-e:hover { background: rgba(59, 130, 246, 0.15); }', 'body.lm-dragging { cursor: move !important; user-select: none !important; }', 'body.lm-resizing { user-select: none !important; cursor: inherit !important; }', 'body.lm-resizing * { pointer-events: none !important; }', '@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }', '#lm-window { animation: fadeIn 0.3s ease-out; }', '@media (max-width: 768px) { #lm-window { width: calc(100vw - 40px); left: 20px; right: 20px; } }' ].join('\n')); function updateStatus(message) { var status = document.getElementById('lm-status'); if (status) status.textContent = message; } function updateProgress(current, total) { var progressFill = document.querySelector('.lm-progress-fill'); var metaCount = document.getElementById('lm-meta-count'); if (progressFill) { var percent = total > 0 ? (current / total) * 100 : 0; progressFill.style.width = percent + '%'; } if (metaCount) { metaCount.textContent = 'Processing: ' + current + '/' + total; } } function sanitizeFilename(filename) { if (!filename) return 'LearnableMeta_Export'; return filename .replace(/[<>:"/\\|?*]/g, '') .replace(/[^\w\s\-\.]/g, '') .replace(/\s+/g, '_') .replace(/_{2,}/g, '_') .replace(/^_+|_+$/g, '') .substring(0, 100) || 'LearnableMeta_Export'; } function downloadFile(content, filename, mimeType) { var sanitizedFilename = sanitizeFilename(filename); console.log('📥 Downloading:', sanitizedFilename); try { var blob = new Blob([content], { type: mimeType }); var url = URL.createObjectURL(blob); var link = document.createElement('a'); link.href = url; link.download = sanitizedFilename; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); setTimeout(() => URL.revokeObjectURL(url), 1000); updateStatus('✅ Downloaded: ' + sanitizedFilename); } catch (error) { console.error('❌ Download failed:', error); updateStatus('❌ Download failed: ' + error.message); } } function waitForTable() { return new Promise(function(resolve) { var attempts = 0; var maxAttempts = 20; // 10 seconds max function checkTable() { attempts++; var table = document.querySelector('table'); if (table && table.querySelectorAll('td').length > 0) { resolve({ cells: table.querySelectorAll('td'), hasCheckboxes: document.querySelectorAll('input[type="checkbox"]').length > 0, checkboxes: document.querySelectorAll('input[type="checkbox"]') }); } else if (attempts >= maxAttempts) { throw new Error('Table not found after ' + maxAttempts + ' attempts'); } else { setTimeout(checkTable, 500); } } checkTable(); }); } function isMetaSelected(metaName, checkboxes) { if (!checkboxes || checkboxes.length === 0) return true; for (var i = 0; i < checkboxes.length; i++) { var parent = checkboxes[i].closest('tr, div, li') || checkboxes[i].parentElement; if (parent && parent.textContent.includes(metaName)) { return checkboxes[i].checked; } } return true; } function countSelectedMetas(tableCells, checkboxes) { var selectedCount = 0, totalCount = 0; for (var i = 0; i < tableCells.length; i++) { var metaName = tableCells[i].textContent.trim(); if (metaName) { totalCount++; if (isMetaSelected(metaName, checkboxes)) selectedCount++; } } return { selected: selectedCount, total: totalCount }; } function findContentDiv(metaName) { var divs = document.querySelectorAll('div'); for (var i = 0; i < divs.length; i++) { if (divs[i].textContent.includes(metaName) && (divs[i].querySelector('img') || divs[i].querySelector('p'))) { return divs[i]; } } return null; } function extractImages(container, maxImages) { var imgs = container.querySelectorAll('img'); var result = []; for (var i = 0; i < imgs.length && result.length < maxImages; i++) { var src = imgs[i].src; if (!src || src.indexOf('http') !== 0) continue; if (src.includes('logo') || src.includes('icon') || src.includes('nav') || src.includes('menu') || src.includes('header') || src.includes('_app/')) continue; if ((imgs[i].width > 0 && imgs[i].width < 50) || (imgs[i].height > 0 && imgs[i].height < 50)) continue; result.push(src); } return result; } function extractDescription(container) { var elements = container.querySelectorAll('p, li, div, span'); var bestDescription = ''; var bestScore = 0; for (var i = 0; i < elements.length; i++) { var text = elements[i].textContent.trim(); if (text.length < 20 || text.length > 1000) continue; var lower = text.toLowerCase(); if (lower.includes('meta list') || lower.includes('home') || lower.includes('plonkit.net') || lower.includes('www.') || text === 'Play' || text === 'Maps') continue; var score = 0; if (text.includes('.') || text.includes('!')) score += 10; if (lower.includes('note:')) score += 20; if (text.length > 50) score += text.length / 10; if (lower.includes('used') || lower.includes('typically') || lower.includes('common') || lower.includes('found')) score += 5; if (score > bestScore) { bestScore = score; bestDescription = text; } } return bestDescription.replace(/Meta List[^.]*\./gi, '') .replace(/Play\s*/gi, '') .replace(/\s+/g, ' ') .trim(); } function cleanDescription(description) { if (!description) return ''; return description.replace(/",LearnableMeta/g, '') .replace(/,LearnableMeta/g, '') .replace(/LearnableMeta$/g, '') .replace(/\s+/g, ' ') .trim(); } function cleanMetaTitle(title) { return title ? title.replace(/\s*\(\d+\)\s*$/, '').trim() : ''; } function setButtonsEnabled(enabled) { var buttons = document.querySelectorAll('.lm-button'); for (var i = 0; i < buttons.length; i++) { buttons[i].disabled = !enabled; } } function scrapeMetas(maxImages) { updateStatus('⏳ Waiting for meta table...'); setButtonsEnabled(false); return waitForTable().then(function(tableData) { var tableCells = tableData.cells; var checkboxes = tableData.hasCheckboxes ? Array.from(tableData.checkboxes) : null; var metas = [], processedCount = 0; var counts = countSelectedMetas(tableCells, checkboxes); updateStatus(checkboxes ? '🔍 Processing ' + counts.selected + '/' + counts.total + ' selected metas...' : '🔍 Processing all ' + counts.total + ' metas...'); function processNextCell(index) { if (index >= tableCells.length) { updateStatus('✅ Found ' + metas.length + ' metas with content'); setButtonsEnabled(true); return Promise.resolve(metas); } var cell = tableCells[index]; var metaName = cell.textContent.trim(); if (!metaName || !isMetaSelected(metaName, checkboxes)) { return processNextCell(index + 1); } processedCount++; updateProgress(processedCount, counts.selected || counts.total); updateStatus('📝 Processing (' + processedCount + '): ' + metaName); cell.scrollIntoView({ behavior: 'smooth', block: 'center' }); cell.click(); return sleep(800).then(function() { try { var contentDiv = findContentDiv(metaName); if (contentDiv) { var images = extractImages(contentDiv, maxImages); var description = cleanDescription(extractDescription(contentDiv)); var cleanTitle = cleanMetaTitle(metaName); if (images.length > 0 || description) { metas.push({ title: cleanTitle, images: images, description: description || cleanTitle }); } } } catch (error) { console.warn('⚠️ Error processing meta:', metaName, error); } return processNextCell(index + 1); }); } return processNextCell(0); }).catch(function(error) { setButtonsEnabled(true); throw error; }); } function createPerfectTxtExport(deckName, metas) { updateStatus('📝 Creating production-ready Anki file...'); var timestamp = new Date().toLocaleString(); var instructions = [ '# 🎯 PRODUCTION ANKI IMPORT FILE', '# ==============================', '# Deck: ' + deckName, '# Cards: ' + metas.length, '# Created: ' + timestamp, '# Format: Premium styled cards with responsive design', '# Quality: Production ready with error handling', '#', '# 📥 IMPORT INSTRUCTIONS:', '# 1. Open Anki Desktop', '# 2. File → Import', '# 3. Select this TXT file', '# 4. Import Settings:', '# • Type: "Text separated by tabs or semicolons"', '# • Field separator: Tab', '# • Field 1 → Front', '# • Field 2 → Back', '# • Field 3 → Tags', '# • ✅ Allow HTML in fields', '# • Deck: "' + deckName + '"', '# 5. Click Import', '#', '# 🎨 CARD DESIGN:', '# • Mobile-responsive layout', '# • High-quality image display', '# • Professional typography', '# • Optimized for learning', '#' ].join('\n') + '\n'; var csvContent = instructions + 'Front\tBack\tTags\n'; for (var i = 0; i < metas.length; i++) { var meta = metas[i]; var front = ''; if (meta.images.length > 0) { var imageStyles = 'max-width: 100%; max-height: 400px; width: auto; height: auto; display: block; margin: 15px auto; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); object-fit: contain;'; var imageHtml = meta.images.map(function(img) { return '<img src="' + img + '" alt="' + meta.title + '" style="' + imageStyles + '" loading="lazy">'; }).join(''); front = '<div style="text-align: center; padding: 25px; background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); border-radius: 16px; margin: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.08); min-height: 200px; display: flex; flex-direction: column; justify-content: center;">' + imageHtml + '</div>'; } else { front = '<div style="text-align: center; padding: 60px 20px; background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%); border-radius: 16px; margin: 12px; color: #1e40af; font-size: 48px; min-height: 200px; display: flex; align-items: center; justify-content: center;">🗺️<div style="font-size: 16px; margin-top: 10px; color: #64748b;">No image available</div></div>'; } var back = '<div style="font-family: Inter, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif; max-width: 700px; margin: 0 auto; background: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 20px 40px rgba(0,0,0,0.1); border: 1px solid #e5e7eb;"><div style="background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); color: white; padding: 32px 24px; text-align: center;"><h1 style="margin: 0; font-size: 28px; font-weight: 700; text-shadow: 0 2px 4px rgba(0,0,0,0.2); line-height: 1.2;">' + meta.title + '</h1></div><div style="padding: 32px 24px; line-height: 1.7; color: #374151;"><div style="background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); border-left: 4px solid #3b82f6; padding: 24px; border-radius: 0 12px 12px 0; font-size: 16px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); margin-bottom: 16px;">' + meta.description + '</div><div style="font-size: 12px; color: #9ca3af; text-align: center; padding-top: 16px; border-top: 1px solid #e5e7eb;">LearnableMeta Export</div></div></div>'; var tags = 'LearnableMeta ' + deckName.replace(/\s+/g, '_') + ' geography visual_learning production_ready'; csvContent += front + '\t' + back + '\t' + tags + '\n'; } var filename = sanitizeFilename(deckName) + '.txt'; downloadFile(csvContent, filename, 'text/plain;charset=utf-8'); } function checkSelection() { updateStatus('🔍 Analyzing selection...'); setButtonsEnabled(false); waitForTable().then(function(tableData) { var counts = countSelectedMetas(tableData.cells, tableData.checkboxes); setButtonsEnabled(true); if (tableData.hasCheckboxes) { updateStatus('📊 Selection: ' + counts.selected + ' of ' + counts.total + ' metas'); alert('Selection Status:\n\n' + '✅ Selected: ' + counts.selected + ' metas\n' + '📊 Total available: ' + counts.total + ' metas\n\n' + (counts.selected === 0 ? '⚠️ Please select some metas to export!' : '🚀 Ready to export ' + counts.selected + ' selected metas!')); } else { updateStatus('📊 Will process all ' + counts.total + ' metas'); alert('Export Status:\n\n' + '📊 Found: ' + counts.total + ' metas\n' + '🚀 Will process all metas when exported\n\n' + 'No selection controls detected.'); } }).catch(function(error) { setButtonsEnabled(true); updateStatus('❌ Error: ' + error.message); alert('Error checking selection:\n' + error.message); }); } function exportData() { var deckName = document.getElementById('lm-deck').value.trim() || 'LearnableMeta'; var maxImages = parseInt(document.getElementById('lm-range').value) || 2; updateStatus('🚀 Starting export process...'); scrapeMetas(maxImages).then(function(metas) { if (metas.length === 0) { updateStatus('⚠️ No content found to export'); alert('Export Failed:\n\nNo metas with content were found.\n\nTips:\n• Make sure metas are loaded\n• Check if any metas are selected\n• Verify the page has content'); return; } updateStatus('📦 Creating download file...'); createPerfectTxtExport(deckName, metas); // Success notification setTimeout(function() { updateStatus('🎉 Export completed successfully!'); }, 1000); }).catch(function(error) { console.error('💥 Export failed:', error); updateStatus('❌ Export failed: ' + error.message); alert('Export Error:\n\n' + error.message + '\n\nPlease try again or check the console for details.'); }); } function createPanel() { var mapTitle = (document.querySelector('h1, h2, title') || {}).textContent || 'LearnableMeta'; // Clean up the map title mapTitle = mapTitle.replace(/LearnableMeta\s*[-|]\s*/gi, '').trim(); var window = document.createElement('div'); window.id = 'lm-window'; var showBtn = document.createElement('button'); showBtn.id = 'lm-show-btn'; showBtn.innerHTML = '📚'; showBtn.title = 'Show Anki Exporter'; window.innerHTML = [ '<div id="lm-header">', '<div id="lm-title">📚 Anki Exporter</div>', '<button id="lm-hide-btn" title="Hide Window">×</button>', '</div>', '<div id="lm-content">', '<div class="lm-section">', '<label>Deck Name</label>', '<input id="lm-deck" type="text" value="' + sanitizeFilename(mapTitle) + '" placeholder="Enter deck name">', '</div>', '<div class="lm-section">', '<label>Images per Card (0-5)</label>', '<div class="lm-slider-container">', '<input id="lm-range" type="range" min="0" max="5" value="2">', '<span id="lm-slider-value" class="lm-slider-value">2</span>', '</div></div>', '<div class="lm-section">', '<button id="lm-export-main" class="lm-button primary">🚀 Export</button>', '<button id="lm-check-selection" class="lm-button warning">📊 Check Selection</button>', '<div class="lm-progress-bar"><div class="lm-progress-fill"></div></div>', '<div id="lm-meta-count"></div>', '</div>', '<div id="lm-status">✅ Production ready! Click Export to begin.</div>', '<div id="lm-credits">Made by BennoGHG</div>', '</div>', '<div class="lm-resize-handle lm-resize-se"></div>', '<div class="lm-resize-handle lm-resize-s"></div>', '<div class="lm-resize-handle lm-resize-e"></div>' ].join(''); document.body.appendChild(window); document.body.appendChild(showBtn); // FIXED DRAG FUNCTIONALITY var isDragging = false; var dragOffset = { x: 0, y: 0 }; var header = document.getElementById('lm-header'); header.addEventListener('mousedown', startDrag); function startDrag(e) { if (e.target.id === 'lm-hide-btn') return; isDragging = true; var rect = window.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; window.classList.add('dragging'); document.body.classList.add('lm-dragging'); document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', stopDrag); e.preventDefault(); } function onDrag(e) { if (!isDragging) return; var newX = e.clientX - dragOffset.x; var newY = e.clientY - dragOffset.y; var maxX = document.documentElement.clientWidth - window.offsetWidth; var maxY = document.documentElement.clientHeight - window.offsetHeight; newX = Math.max(0, Math.min(newX, maxX)); newY = Math.max(0, Math.min(newY, maxY)); window.style.left = newX + 'px'; window.style.top = newY + 'px'; } function stopDrag() { isDragging = false; window.classList.remove('dragging'); document.body.classList.remove('lm-dragging'); document.removeEventListener('mousemove', onDrag); document.removeEventListener('mouseup', stopDrag); } // FIXED RESIZE FUNCTIONALITY var isResizing = false; var resizeType = ''; var resizeStart = { x: 0, y: 0, width: 0, height: 0 }; // Use window.querySelector instead of document.querySelector var resizeSE = window.querySelector('.lm-resize-se'); var resizeS = window.querySelector('.lm-resize-s'); var resizeE = window.querySelector('.lm-resize-e'); if (resizeSE) resizeSE.addEventListener('mousedown', function(e) { startResize(e, 'se'); }); if (resizeS) resizeS.addEventListener('mousedown', function(e) { startResize(e, 's'); }); if (resizeE) resizeE.addEventListener('mousedown', function(e) { startResize(e, 'e'); }); function startResize(e, type) { isResizing = true; resizeType = type; var rect = window.getBoundingClientRect(); resizeStart.x = e.clientX; resizeStart.y = e.clientY; resizeStart.width = rect.width; resizeStart.height = rect.height; window.classList.add('resizing'); document.body.classList.add('lm-resizing'); document.addEventListener('mousemove', onResize); document.addEventListener('mouseup', stopResize); e.preventDefault(); e.stopPropagation(); } function onResize(e) { if (!isResizing) return; var deltaX = e.clientX - resizeStart.x; var deltaY = e.clientY - resizeStart.y; var newWidth = resizeStart.width; var newHeight = resizeStart.height; if (resizeType.includes('e')) { newWidth = Math.max(320, Math.min(600, resizeStart.width + deltaX)); } if (resizeType.includes('s')) { newHeight = Math.max(450, Math.min(window.innerHeight * 0.9, resizeStart.height + deltaY)); } window.style.width = newWidth + 'px'; window.style.height = newHeight + 'px'; } function stopResize() { isResizing = false; window.classList.remove('resizing'); document.body.classList.remove('lm-resizing'); document.removeEventListener('mousemove', onResize); document.removeEventListener('mouseup', stopResize); } // Hide/Show functionality var hideBtn = document.getElementById('lm-hide-btn'); var isHidden = false; hideBtn.addEventListener('click', function() { if (!isHidden) { window.classList.add('hidden'); showBtn.style.display = 'flex'; isHidden = true; } }); showBtn.addEventListener('click', function() { if (isHidden) { window.classList.remove('hidden'); showBtn.style.display = 'none'; isHidden = false; } }); // Event listeners document.getElementById('lm-range').addEventListener('input', function(e) { document.getElementById('lm-slider-value').textContent = e.target.value; }); document.getElementById('lm-export-main').addEventListener('click', exportData); document.getElementById('lm-check-selection').addEventListener('click', checkSelection); // Keyboard shortcuts document.addEventListener('keydown', function(e) { if (e.ctrlKey || e.metaKey) { if (e.key === 'h' && !isHidden) { e.preventDefault(); hideBtn.click(); } else if (e.key === 'e' && !isHidden) { e.preventDefault(); exportData(); } } }); } // Initialize with error handling setTimeout(function() { try { createPanel(); updateStatus('✅ Ready! Select Metas for Export.'); console.log('✅ LearnableMeta Anki Exporter v1.0 loaded successfully'); } catch (error) { console.error('❌ Failed to initialize Anki Exporter:', error); } }, 1000); })();