--- name: 导出至 Anki description: 将当前选中的元素或图片快速生成带有正面文本的 Anki 卡片。 author: ymjr version: 1.0.0 license: MIT usage: 选中画板元素后运行,弹出非模态悬浮窗(可拖拽标题栏移动),不影响画板操作。 features: - 悬浮非模态 UI 面板(支持自由拖拽) - 导出失败时智能提示检查 AnkiConnect 服务 --- /* ```javascript */ var locales = { zh: { notice_select: "请先选中需要导出的连线或元素!", export_title: "🚀 导出至 Anki", anki_front: "正面内容 (Front)", anki_front_ph: "输入正面文本内容...", tags_label: "标签 (逗号分隔)", tags_ph: "例如: Obsidian, Excalidraw", btn_export: "确认导出", btn_exporting: "发送中...", settings_toggle: "⚙️ 接口设置", anki_url: "AnkiConnect 接口", export_success: "✅ 成功导出到 Anki!", export_fail: "❌ 导出失败!请检查:\n1. 是否已打开 Anki\n2. 是否已安装并启动 AnkiConnect 插件" }, en: { notice_select: "Please select elements to export first!", export_title: "🚀 Export to Anki", anki_front: "Front Content", anki_front_ph: "Enter front text...", tags_label: "Tags (Comma separated)", tags_ph: "e.g., Obsidian, Excalidraw", btn_export: "Confirm Export", btn_exporting: "Sending...", settings_toggle: "⚙️ API Settings", anki_url: "AnkiConnect URL", export_success: "✅ Successfully exported to Anki!", export_fail: "❌ Export failed! Please check:\n1. Is Anki open?\n2. Is AnkiConnect running?" } }; const selectedEls = ea.getViewSelectedElements(); if (selectedEls.length === 0) { new Notice(t("notice_select")); return; } await ea.addElementsToView(); const targetView = ea.targetView; const plugin = ea.plugin; // 提前生成 SVG Base64 function embedFontsInSVG(svg, plugin) { if (!plugin.settings.exportSVGFont) return svg; const defs = svg.querySelector("defs") || document.createElement("defs"); if(!svg.querySelector("defs")) svg.prepend(defs); let fonts = ``; defs.innerHTML += fonts; return svg; } let base64SVG = ""; try { let svgNode = await targetView.svg(targetView.getScene(true), undefined, true); if (plugin.settings.exportSVGFont) { svgNode = embedFontsInSVG(svgNode, plugin); } base64SVG = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgNode.outerHTML.replaceAll(" ", " "))))}`; } catch(e) { console.warn("SVG Generate Warn:", e); } // 读取设置 let settings = ea.getScriptSettings() || {}; if (!settings["ymjr_anki_config"]) { settings["ymjr_anki_config"] = { "anki url": "[http://127.0.0.1:8765](http://127.0.0.1:8765)" }; ea.setScriptSettings(settings); } const currentConfig = Object.assign({}, settings["ymjr_anki_config"]); // 清理旧 UI 并注入新 UI if (ExcalidrawAutomate.plugin._ymjr_ankiPanel) { ExcalidrawAutomate.plugin._ymjr_ankiPanel.remove(); } document.getElementById("ymjr-anki-style")?.remove(); const styleEl = document.createElement("style"); styleEl.id = "ymjr-anki-style"; styleEl.innerHTML = ` .ymjr-anki-panel { position: fixed; bottom: 30px; right: 30px; z-index: 999999; background: var(--background-primary); border: 1px solid var(--background-modifier-border); border-radius: 12px; width: 340px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); display: flex; flex-direction: column; overflow: hidden; font-family: var(--font-interface); color: var(--text-normal); animation: slideInUp 0.3s cubic-bezier(0.16, 1, 0.3, 1); } .ymjr-header { padding: 14px 18px; font-weight: 600; font-size: 1.1em; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--background-modifier-border); background: var(--background-secondary-alt); cursor: grab; user-select: none; } .ymjr-header:active { cursor: grabbing; } .ymjr-close { cursor: pointer; opacity: 0.6; transition: 0.2s; font-size: 1.2em; border: none; background: transparent; color: inherit; padding: 0;} .ymjr-close:hover { opacity: 1; color: var(--text-error); } .ymjr-body { padding: 18px; display: flex; flex-direction: column; gap: 14px; } .ymjr-form-group { display: flex; flex-direction: column; gap: 6px; } .ymjr-form-group label { font-size: 0.85em; opacity: 0.8; font-weight: 500; } .ymjr-input { background: var(--background-modifier-form-field); border: 1px solid var(--background-modifier-border); border-radius: 6px; padding: 8px 10px; color: var(--text-normal); font-size: 0.95em; outline: none; resize: vertical; min-height: 38px; } .ymjr-input:focus { border-color: var(--interactive-accent); box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2); } .ymjr-settings-toggle { font-size: 0.85em; cursor: pointer; opacity: 0.7; user-select: none; } .ymjr-settings-panel { background: var(--background-secondary); border-radius: 8px; padding: 12px; display: flex; flex-direction: column; gap: 10px; border: 1px solid var(--background-modifier-border); } .ymjr-btn-submit { background: var(--interactive-accent); color: var(--text-on-accent); border: none; border-radius: 8px; padding: 10px; font-weight: 600; font-size: 1em; cursor: pointer; transition: 0.2s; margin-top: 5px; } .ymjr-btn-submit:hover { opacity: 0.9; transform: translateY(-1px); } .ymjr-btn-submit:disabled { opacity: 0.5; cursor: not-allowed; } @keyframes slideInUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } `; document.head.appendChild(styleEl); const panel = document.createElement("div"); panel.className = "ymjr-anki-panel"; panel.innerHTML = `