// ==UserScript==
// @name LearnableMeta to Anki Exporter
// @namespace https://learnablemeta.com/
// @version 1.0.0
// @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-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>',
'<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);
})();