1/** 2 * i18n TRANSLATIONS 3 */ 4 5/** 6 * GA4 TRACKING HELPER 7 * Safely sends events to Google Analytics 4 8 */ 9function trackEvent(eventName, params = {}) { 10 if (typeof gtag === 'function') { 11 const enrichedParams = { 12 'attributed_lp': sessionStorage.getItem('original_lp') || 'direct', 13 'current_path': window.location.pathname, 14 ...params 15 }; 16 gtag('event', eventName, enrichedParams); 17 } else { 18 console.debug(`[GA4] Event: ${eventName}`, params); 19 } 20} 21 22const langMap = { 23 'en': { 24 seoTitle: "MD2PDF - Free Markdown to PDF Converter Online | md2pdf.cc", 25 seoDescription: "Export Mermaid.js diagrams (Flowcharts, Sequence, Gantt) to professional PDF documents instantly. 100% Client-side and private.", 26 h1Title: "Free Mermaid to PDF Converter", 27 h2Title: "Convert Mermaid Diagrams to Professional PDF", 28 p1: "Seamlessly transform Mermaid.js diagrams into professional PDF documents. Supports flowcharts, sequence diagrams, and more.", 29 p2: "MD2PDF (md2pdf.cc) is optimized for technical documentation, providing the best way to export Mermaid charts via Markdown.", 30 li1: "Privacy First: All processing occurs in the browser.", 31 li2: "Support for Flowcharts, Sequence Diagrams, Gantt Charts.", 32 li3: "Instant high-quality SVG to PDF rendering.", 33 mermaidTitle: "Mermaid Diagram Rendering", 34 aiExportTitle: "AI Chat to PDF Export", 35 newFile: "New File", newFolder: "New Folder", rename: "Rename", download: "Download MD", delete: "Delete", 36 syncDrive: "Cloud Sync (2-way)", exportPdf: "Export PDF", feedback: "Feedback", editorPh: "# Start your prompt journey...", 37 confirmDelete: "Delete this item?", welcomeTitle: "My Reports", welcomeFile: "Welcome.md", 38 welcomeContent: `# MD2PDF.cc\n## **The Professional Engine for the AI Era**\n<span style="color: #7c3aed; font-weight: bold;">Privacy First ⢠100% Client-Side</span> ⢠Publication Quality PDF\n\n---\n\n## ⨠Key Features\n\n### 1. Math & Science ($LaTeX$)\nPerfect for DeepSeek, GPT-4o, or Claude outputs:\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. Diagrams (Mermaid)\nRender flowcharts and diagrams instantly:\n\`\`\`mermaid\ngraph LR\n A[AI Chat] --> B{MD2PDF}\n B -->|Parse| C[PDF Report]\n B -->|Style| D[Beautiful Layout]\n\`\`\`\n\n### 3. Professional Features\n- [x] **Mermaid.js** support\n- [x] **GitHub Callouts**\n- [x] **Academic Footnotes**\n- [ ] **Custom CSS** (Coming soon)\n\n#### GitHub-Style Callouts\n> [!NOTE]\n> This is a **NOTE** for neutral but important information.\n\n> [!TIP]\n> This is a **TIP** to help users do things better or faster.\n\n> [!WARNING]\n> This is a **WARNING** for potentially dangerous or critical actions.\n\n> [!DANGER]\n> This is a **DANGER** block for critical errors or data loss risks.\n\n#### Academic Footnotes\nMarkdown is powerful[^1], but MD2PDF makes it elegant[^2].\n\n[^1]: Standard Markdown supports basic text formatting.\n[^2]: MD2PDF adds professional PDF output and advanced syntax.\n\n---\n\n## ð Modern Note-Taking Support (Obsidian & Notion)\nCompatible with your favorite note-taking syntax:\n\n- **Highlights**: You can ==highlight important text== easily.\n- **WikiLinks**: Support for [[Internal Links]] styling.\n\n---\n\n## ð Tech-Native Formatting\n\`\`\`javascript\n// Clean and professional code blocks\nfunction helloWorld() {\n console.log("Hello from MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ API for Developers\nAutomate your workflow:\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð Click **"Export PDF"** to see the magic.`, 39 aiPrompt: "AI Assistant", guideTitle: "AI Assistant Integration", guideSub: "Instant Professional PDF", howItWorks: "How it works", 40 guideDesc: "MD2PDF enables AI agents to generate professional reports. Use the system prompt below so the AI provides direct URL commands for the address bar to generate documents instantly.", 41 copyPrompt: "Copy System Prompt", benefit1: "Pro Layout", benefit2: "Privacy First", 42 legalTitle: "Security & Terms", dataSecurity: "Data Security", termsOfService: "Terms of Service", privacyPolicy: "Privacy Policy", 43 dataSecurityDesc: "MD2PDF is a client-side application. Your Markdown content and files are stored only in your browser's local storage. We do not have servers that store your private documents, ensuring maximum privacy and security.", 44 termsDesc: "Use of md2pdf.cc is governed by Quanta Flow Inc. (Wyoming). We provide no warranties for data persistence in browser storage.", 45 privacyDesc: "Privacy-first: Documents are processed locally. We use minimal tracking for analytics and future advertising purposes.", 46 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 47 terms: "Terms", privacy: "Privacy", security: "Security", 48 themeLight: "Light", themeDark: "Dark", themeSepia: "Sepia", themeSolarized: "Solarized", themeOcean: "Ocean", 49 viewEditor: "Full Editor", viewSplit: "Split View", viewPreview: "Full Preview", 50 modalCancel: "Cancel", modalConfirm: "Confirm", newItemName: "Enter name", 51 renameTitle: "Rename", newFileTitle: "New File", newFolderTitle: "New Folder", 52 exporting: "Exporting...", exportSuccess: "Export Successful!", exportFail: "
52Export failed. Content might be too large.", 53 pdfStyle: "PDF Style", styleStandard: "Standard (GitHub)", styleAcademic: "Academic (Elegant)", styleModern: "Modern (Clean)", styleDefault: "System Default", 54 styleDefaultSimple: "Default", styleStandardSimple: "Standard", styleAcademicSimple: "Academic", styleModernSimple: "Modern", 55 styleUpdated: "Default PDF Style Updated", 56 syncStart: "Starting Bidirectional Sync...", syncSuccess: "All files synced to Google Drive!", syncFail: "Sync failed: ", syncReauth: "Authorization required. Please tap Sync again to connect Google Drive.", 57 installMsg: "Install MD2PDF for a better experience", installBtn: "Install Now", installSuccess: "App installed successfully!", 58 chromeInstallMsg: "Install MD2PDF as a Desktop App for quick access", chromeInstallBtn: "Install App", 59 iosInstallMsg: "Tap the share button then 'Add to Home Screen' to install as an App", 60 copy: "Copy", copyJson: "Copy JSON", copied: "Copied to clipboard", 61 language: "Language", theme: "Theme", copyContent: "Copy Content", copyEditor: "Copy Editor", copyPreview: "Copy Preview", 62 sidebarSubTitle: "Automated Documentation", 63 paste: "Paste from Clipboard", 64 recommended: "Recommended", 65 pdfSettings: "PDF Settings", pdfSettingsTitle: "PDF Customization", pdfPageSetup: "Page Setup", pdfCover: "Cover Page", 66 docTitle: "Document Title", docSubtitle: "Subtitle / Author", pdfTOC: "Table of Contents", pdfHeaderFooter: "Header & Footer", 67 pdfHeader: "Header", pdfFooter: "Footer", pdfMacros: "Macros: {date}, {title}, {pagenum}, {totalpages}", modalSave: "Save & Close", 68 pdfLeft: "Left", pdfCenter: "Center", pdfRight: "Right", 69 pdfBranding: "The Professional AI Artifacts to PDF Engine", 70 exportSettings: "Export Settings", 71 coverShowHeader: "Show header/footer on cover", 72 disableBrowserHF: "ð¡ Tips: Disable browser H/F", 73 printDialogTip: "ð¡ In the print dialog, please disable browser's default headers/footers to use custom settings", 74 printDialogNote: "Note: Please turn off 'Headers and footers' in the print dialog", 75 headerLogoLeft: "Header Logo (Left)", 76 headerLogoRight: "Header Logo (Right)", 77 watermarkText: "Watermark Text", 78 uploadLogo: "Upload", 79 logoUrl: "Logo URL", 80 logoAndWatermark: "Logo & Watermark", 81 clear: "Clear", 82 watermark: "Watermark", 83 feedbackBtnTitle: "Share your feedback", 84 feedbackModalTitle: "Share Your Feedback ð¬", 85 feedbackModalDesc: "Help us improve MD2PDF â your feedback matters!", 86 feedbackRatingLabel: "How's your experience?", 87 feedbackMsgLabel: "Your feedback", 88 feedbackMsgPh: "Tell us what you think â features, bugs, improvements...", 89 feedbackEmailPh: "[email protected]", 90 feedbackSubmitBtn: "Send Feedback", 91 feedbackSending: "Sending...", 92 feedbackSuccessTitle: "Thank you! ð", 93 feedbackSuccessMsg: "Your feedback has been received. We'll carefully review it and use it to make MD2PDF even better.", 94 feedbackErrorMsg: "Failed to send. Please check your connection and try again.", 95 feedbackNewsletterTitle: "Subscribe to our newsletter", 96 feedbackNewsletterDesc: "Markdown tips, MD2PDF updates & productivity tricks â English only, low frequency.", 97 feedbackEmailRequired: "Please enter a valid email address.", 98 exportBrandingTitle: "Remove PDF branding", 99 exportBrandingFree: "Free exports include the MD2PDF branding page.", 100 exportBrandingActive: "Branding-free export is active.", 101 exportViewPlans: "View plans", 102 account: "Account", 103 exportOneTime: "One-time payment, all devices", 104 exportMonthly: "Cancel anytime; active while subscribed", 105 exportFullPricing: "View full pricing", 106 licenseActivated: "MD2PDF access activated on this device." 107 }, 108 'zh': { 109 seoTitle: "MD2PDF - å è´¹ Markdown 转 PDF å·¥å · (å¨çº¿ãéç§) | md2pdf.cc", 110 seoDescription: "ä¸é®å° Mermaid.js æµç¨å¾ãåºåå¾ãçç¹å¾è½¬æ¢ä¸ºä¸ä¸ PDFã纯å端å¤çï¼éç§å®å ¨ï¼æ éç»å½ã", 111 h1Title: "å è´¹ Mermaid 转 PDF å·¥å ·", 112 h2Title: "å° Mermaid å¾è¡¨è½¬æ¢ä¸ºä¸ä¸ PDF", 113 p1: "æ ç¼å° Mermaid.js å¾è¡¨è½¬æ¢ä¸ºä¸ä¸ PDF ææ¡£ãæ¯ææµç¨å¾ãåºåå¾ãçç¹å¾çå¤ç§ç±»åã", 114 p2: "MD2PDF (md2pdf.cc) ä¸ä¸ºææ¯ææ¡£ä¼åï¼æ¯å¯¼åº Mermaid å¾è¡¨çæä½³ Markdown æ¹æ¡ã", 115 li1: "éç§ä¼å ï¼ææå¤çå卿µè§å¨ä¸å®æï¼ç»ä¸ä¸ä¼ æå¡å¨ã", 116 li2: "å®åæ¯ææµç¨å¾ãæ¶åºå¾ãçç¹å¾ãç±»å¾çã", 117 li3: "峿¶çæé«è´¨é SVG å° PDF çå®ç¾è½¬æ¢ã", 118 mermaidTitle: "Mermaid æµç¨å¾æ¯æ", 119 aiExportTitle: "AI è天å 容导åº", 120 newFile: "æ°å»ºæä»¶", newFolder: "æ°å»ºæä»¶å¤¹", rename: "éå½å", download: "ä¸è½½æºç ", delete: "å é¤", 121 syncDrive: "äºç«¯åå忥", exportPdf: "å¯¼åº PDF", feedback: "åé¦", editorPh: "# å¼å§ä½ çæç¤ºè¯ä¹æ ...", 122 confirmDelete: "确认å é¤ï¼", welcomeTitle: "æçææ¡£", welcomeFile: "欢è¿ä½¿ç¨ MD2PDF", 123 welcomeContent: `# MD2PDF.cc\n## **为 AI æ¶ä»£èççä¸ä¸æç弿**\n<span style="color: #7c3aed; font-weight: bold;">éç§å ¨ææ§ · 100% æ¬å°æ¸²æ</span>
123 · åºç级 A4 转æ¢\n\n---\n\n## â¨ æ ¸å¿åè½\n\n### 1. æ°å¦ä¸ç§å¦ ($LaTeX$)\nå®ç¾æ¯æ DeepSeek, GPT-4o æ Claude 渲æï¼\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. å¾è¡¨æ¸²æ (Mermaid)\nç¬é´å°ä»£ç 转为æµç¨å¾ï¼\n\`\`\`mermaid\ngraph LR\n A[AI è天] --> B{MD2PDF}\n B -->|è§£æ| C[PDF æ¥å]\n B -->|æ ·å¼| D[ç²¾ç¾å¸å±]\n\`\`\`\n\n### 3. ä¸ä¸æçåè½\n- [x] **Mermaid.js** 渲æ\n- [x] **GitHub å¼å· (Callouts)**\n- [x] **妿¯çº§è§æ (Footnotes)**\n- [ ] **èªå®ä¹ CSS** (å³å°æ¨åº)\n\n#### GitHub å¼å·æ¯æ\n> [!NOTE]\n> è¿æ¯ä¸ä¸ª **NOTE** (夿³¨)ï¼ç¨äºçªåºæ¾ç¤ºä¸æ§ä½éè¦çä¿¡æ¯ã\n\n> [!TIP]\n> è¿æ¯ä¸ä¸ª **TIP** (æç¤º)ï¼å¸®å©ç¨æ·æ´é«æå°å®æä»»å¡ã\n\n> [!WARNING]\n> è¿æ¯ä¸ä¸ª **WARNING** (è¦å)ï¼æéç¨æ·æ³¨ææ½å¨çé£é©ã\n\n> [!DANGER]\n> è¿æ¯ä¸ä¸ª **DANGER** (å±é©)ï¼ç¨äºè¦ç¤ºä¸¥éé误æé£é©ã\n\n#### 妿¯çº§è§æ æ¯æ\nMarkdown é常强大[^1]ï¼ä½ MD2PDF è®©å®æ´ä¼é [^2]ã\n\n[^1]: æ å Markdown æ¯æåºç¡ææ¬æ ¼å¼ã\n[^2]: MD2PDF å¢å äºä¸ä¸ PDF è¾åºåé«çº§è¯æ³æ¯æã\n\n---\n\n## ð ç°ä»£ç¬è®°æ¯æ (Obsidian & Notion)\nå ¼å®¹ä½ æå欢çç¬è®°è¯æ³ï¼\n\n- **ææ¬é«äº®**ï¼ä½ å¯ä»¥åå¨ Obsidian ä¸ä¸æ · ==é«äº®éç¹å 容==ã\n- **åå龿¥ (WikiLinks)**ï¼æ¯æ [[å é¨é¾æ¥]] æ ·å¼çè¯æ³ã\n\n---\n\n## ð ææ¯åçæç\n\`\`\`javascript\n// ç®æ´ä¸ä¸ç代ç åæ¾ç¤º\nfunction welcome() {\n console.log("欢è¿ä½¿ç¨ MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ å¼åè API\nèªå¨åä½ ç工使µï¼\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð ç¹å» **âå¯¼åº PDFâ** æ¥çææã`, 124 aiPrompt: "AI 婿", guideTitle: "AI 婿鿿å", guideSub: "æéçæä¸ä¸ PDF", howItWorks: "æ ¸å¿è½å", 125 guideDesc: "MD2PDF æ¯æ AI æºè½ä½çæä¸ä¸æ¥åã请使ç¨ä¸æ¹çç³»ç»æç¤ºè¯ï¼è®© AI ä¸ºä½ æä¾ç´æ¥çå°åæ å½ä»¤ï¼å¤å¶å°æµè§å¨å³å¯ç¬é´çæ PDFã", 126 copyPrompt: "å¤å¶ AI ç³»ç»æç¤ºè¯", benefit1: "ä¸ä¸æç", seoBenefit: "ä¸ä¸º AI çæå 容ä¼åï¼ç¡®ä¿ PDF 转æ¢è´¨éè¾¾å°åºç级æ åã", 127 benefit2: "éç§å®å ¨", privacyBenefit: "ææææ¡£å¤çåå¨å®¢æ·ç«¯å®æãéè¿é¾æ¥ä¼ éçæ°æ®ä» ç¨äºå³æ¶æ¸²æï¼ç»ä¸ä¸ä¼ è³ä»»ä½æå¡å¨ã", 128 legalTitle: "å®å ¨ä¸æ¡æ¬¾", dataSecurity: "æ°æ®å®å ¨", termsOfService: "使ç¨åè®®", privacyPolicy: "éç§æ¿ç", 129 dataSecurityDesc: "MD2PDF æ¯ä¸ä¸ªå®¢æ·ç«¯åºç¨ãæ¨ç Markdown å 容åæä»¶ä» åå¨å¨æµè§å¨çæ¬å°åå¨ï¼Local Storageï¼ä¸ãæä»¬ä¸è®¾å卿¨ç§æææ¡£çæå¡å¨ï¼ç¡®ä¿æé«çéç§åå®å ¨æ§ã", 130 termsDesc: "æ¬æå¡ç± Quanta Flow Inc. (ç¾å½æä¿æå·) æä¾ãç¨æ·é¡»åæç¸å ³åè®®ï¼æä»¬ä¸å¯¹æµè§å¨æ¬å°åå¨çæ°æ®ä¸¢å¤±æ¿æ 责任ã", 131 privacyDesc: "éç§ä¼å ï¼ææææ¡£å卿¬å°å¤çãæä»¬ä» æ¶éå°éå¿åæ°æ®ç¨äºåæï¼æªæ¥å¯è½å å«ç»è¿åè§å¤çç广åå±ç¤ºã", 132 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 133 terms: "æå¡æ¡æ¬¾", privacy: "éç§æ¿ç", security: "å®å ¨è¯´æ", 134 themeLight: "æäº®", themeDark: "æé»", themeSepia: "æ¤ç¼", themeSolarized: "ç»å ¸", themeOcean: "æ·±è", 135 viewEditor: "å ¨å±ç¼è¾", viewSplit: "å屿¨¡å¼", viewPreview: "å ¨å±é¢è§", 136 modalCancel: "åæ¶", modalConfirm: "确认", newItemName: "请è¾å ¥åç§°", 137 renameTitle: "éå½å", newFileTitle: "æ°å»ºæä»¶", newFolderTitle: "æ°å»ºæä»¶å¤¹", 138 exporting: "æ£å¨å¯¼åº...", exportSuccess: "å¯¼åºæåï¼", exportFail: "导åºå¤±è´¥ï¼å 容å¯è½è¿å¤§ã", 139 pdfStyle: "导åºç飿 ¼", styleStandard: "æ å (GitHub 飿 ¼)", styleAcademic: "妿¯ (å ¸é å®ä½)", styleModern: "ç°ä»£ (æç®æ 衬线)", styleDefault: "é»è®¤å¯¼åºç飿 ¼", 140 styleDefaultSimple: "é»è®¤", styleStandardSimple: "æ å", styleAcademicSimple: "妿¯", styleModernSimple: "ç°ä»£", 141 styleUpdated: "é»è®¤å¯¼åºé£æ ¼å·²æ´æ°", 142 syncStart: "å¼å§åå忥 (Google Drive)...", syncSuccess: "æææä»¶å·²åæ¥å° Google Driveï¼", syncFail: "åæ¥å¤±è´¥: ", syncReauth: "éè¦éæ°ææï¼è¯·å次ç¹å»ãäºç«¯åæ¥ãæé®ä»¥è¿æ¥ Google Driveã", 143 installMsg: "å®è£ MD2PDF å°æ¬å°ï¼éæ¶éå°å¼å¯åä½", installBtn: "ç«
143å³å®è£ ", installSuccess: "åºç¨å®è£ æåï¼æ¨ç°å¨å¯ä»¥ä»åºç¨åè¡¨ç´æ¥å¯å¨ã", 144 chromeInstallMsg: "æ£æµå° Chrome æµè§å¨ï¼å»ºè®®å®è£ 为 App 以è·å¾æä½³ä½éª", chromeInstallBtn: "å®è£ 为 App", 145 iosInstallMsg: "ç¹å»ä¸æ¹å享æé®ï¼éæ©âæ·»å å°ä¸»å±å¹âï¼å³å¯å App 䏿 ·ä½¿ç¨", 146 copy: "å¤å¶å 容", copyJson: "å¤å¶ JSON", copied: "å·²å¤å¶å°åªè´´æ¿", 147 language: "éæ©è¯è¨", theme: "主é¢éæ©", copyContent: "å¤å¶å 容", copyEditor: "å¤å¶ç¼è¾åº", copyPreview: "å¤å¶é¢è§åº", 148 sidebarSubTitle: "èªå¨åææ¡£æç", 149 paste: "ä»åªè´´æ¿ç²è´´", 150 recommended: "ä¸ºæ¨æ¨è", 151 pdfSettings: "PDF 设置", pdfSettingsTitle: "PDF èªå®ä¹", pdfPageSetup: "页é¢è®¾ç½®", pdfCover: "å°é¢é¡µ", 152 docTitle: "ææ¡£æ é¢", docSubtitle: "坿 é¢ / ä½è ", pdfTOC: "ç®å½é¡µ (TOC)", pdfHeaderFooter: "页çä¸é¡µè", 153 pdfHeader: "页ç", pdfFooter: "页è", pdfMacros: "æ¯æåé: {date}, {title}, {pagenum}, {totalpages}", modalSave: "ä¿å设置", 154 pdfLeft: "左侧", pdfCenter: "ä¸é´", pdfRight: "å³ä¾§", 155 pdfBranding: "ä¸ä¸çº§ AI 婿 PDF æç弿", 156 exportSettings: "导åºè®¾ç½®", 157 coverShowHeader: "å°é¢æ¾ç¤ºé¡µç/页è", 158 disableBrowserHF: "ð¡ æç¤ºï¼è¯·å ³éæµè§å¨é»è®¤é¡µç页è", 159 printDialogTip: "ð¡ 卿å°å¯¹è¯æ¡ä¸ï¼è¯·å ³éæµè§å¨é»è®¤ç页ç页èé项ï¼ä»¥ä½¿ç¨èªå®ä¹è®¾ç½®", 160 printDialogNote: "æç¤ºï¼è¯·å¨æå°å¯¹è¯æ¡ä¸å ³éã页çå页èãé项", 161 headerLogoLeft: "页ç左侧 Logo", 162 headerLogoRight: "页çå³ä¾§ Logo", 163 watermarkText: "æåæ°´å°", 164 uploadLogo: "ä¸ä¼ å¾ç", 165 logoUrl: "å¾ç龿¥", 166 logoAndWatermark: "Logo 䏿°´å°", 167 clear: "æ¸ é¤", 168 watermark: "æ°´å°æå", 169 feedbackBtnTitle: "åäº«ä½ çåé¦", 170 feedbackModalTitle: "åäº«ä½ çåé¦ ð¬", 171 feedbackModalDesc: "叮婿们æ¹å MD2PDF â ä½ çæ¯æ¡åé¦é½å¾éè¦ï¼", 172 feedbackRatingLabel: "ä½ ç使ç¨ä½éªå¦ä½ï¼", 173 feedbackMsgLabel: "ä½ çåé¦", 174 feedbackMsgPh: "è¯´è¯´ä½ çæ³æ³ââåè½ãé®é¢ã建议é½å¯ä»¥...", 175 feedbackEmailPh: "[email protected]", 176 feedbackSubmitBtn: "æäº¤åé¦", 177 feedbackSending: "æäº¤ä¸...", 178 feedbackSuccessTitle: "æè°¢ä½ ï¼ð", 179 feedbackSuccessMsg: "ä½ çåé¦å·²æ¶å°ï¼æä»¬ä¼è®¤çé è¯»æ¯æ¡æè§ï¼å¹¶ç¨å®ä¸ææ¹å MD2PDFã", 180 feedbackErrorMsg: "æäº¤å¤±è´¥ï¼è¯·æ£æ¥ç½ç»åéè¯ã", 181 feedbackNewsletterTitle: "è®¢é æä»¬çé®ä»¶å表", 182 feedbackNewsletterDesc: "Markdown æå·§ãMD2PDF äº§åæ´æ°ä¸æçå·¥å ·ââä» è±æï¼ä½é¢åéã", 183 feedbackEmailRequired: "请è¾å ¥ææççµåé®ç®±å°åã", 184 exportBrandingTitle: "ç§»é¤ PDF åçä¿¡æ¯", 185 exportBrandingFree: "å 费导åºä¼å å« MD2PDF åç页ã", 186 exportBrandingActive: "å·²å¯ç¨æ åçä¿¡æ¯å¯¼åºã", 187 exportViewPlans: "æ¥çæ¹æ¡", 188 account: "è´¦æ·", 189 exportOneTime: "䏿¬¡æ§ä»æ¬¾ï¼ææè®¾å¤å¯ç¨", 190 exportMonthly: "å¯éæ¶åæ¶ï¼è®¢é æææå å¯ç¨", 191 exportFullPricing: "æ¥ç宿´ä»·æ ¼æ¹æ¡", 192 licenseActivated: "MD2PDF æç已卿¤è®¾å¤ä¸æ¿æ´»ã" 193 }, 194 'tw': { 195 seoTitle: "MD2PDF - å è²» Markdown è½ PDF å·¥å · (ç·ä¸ãé±ç§) | md2pdf.cc", 196 seoDescription: "ä¸éµå° Mermaid.js æµç¨åãåºååãçç¹åè½æçºå°æ¥ PDFãç´å端èçï¼é±ç§å®å ¨ï¼ç¡éç»éã", 197 h1Title: "å è²» Mermaid è½ PDF å·¥å ·", 198 h2Title: "å° Mermaid åè¡¨è½æçºå°æ¥ PDF", 199 p1: "ç¡ç¸«å° Mermaid.js åè¡¨è½æçºå°æ¥ PDF ææªãæ¯æ´æµç¨åãåºååãçç¹åçå¤ç¨®é¡åã", 200 p2: "MD2PDF (md2pdf.cc) å°çºæè¡ææªåªåï¼æ¯å¯åº Mermaid å表çæä½³ Markdown æ¹æ¡ã", 201 li1: "é±ç§åªå ï¼ææèçåå¨ç覽å¨ä¸å®æï¼çµä¸ä¸å³ä¼ºæå¨ã", 202 li2: "å®åæ¯æ´æµç¨åãæåºåãçç¹åãé¡åçã", 203 li3: "峿çæé«å質 SVG å° PDF çå®ç¾è½æã", 204 mermaidTitle: "Mermaid æµç¨åæ¯æ´", 205 aiExportTitle: "AI èå¤©å §å®¹å¯åº", 206 newFile: "æ°å»ºæªæ¡", newFolder: "æ°å»ºè³æå¤¾", rename: "éæ°å½å", download: "ä¸è¼æºç¢¼", delete: "åªé¤", 207 syncDrive: "é²ç«¯éå忥", exportPdf: "å¯åº PDF", feedback: "åé¥", editorPh: "# éå§æ¨çæç¤ºè©ä¹æ ...", 208 confirmDelete: "確èªåªé¤ï¼", welcomeTitle: "æçæä»¶", welcomeFile: "æ¡è¿ä½¿ç¨ MD2PDF", 209 welcomeContent: `# MD2PDF.cc\n## **çº AI æä»£èççå°æ¥æç弿**\n<span style="color: #7c3aed; font-weight: bold;">é±ç§å ¨ææ§ · 100% æ¬å°æ¸²æ</span>
209 · åºçç´ A4 è½æ\n\n---\n\n## â¨ æ ¸å¿åè½\n\n### 1. æ¸å¸èç§å¸ ($LaTeX$)\nå®ç¾æ¯æ DeepSeek, GPT-4o æ Claude 渲æï¼\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. å表渲æ (Mermaid)\nç¬éå°ä»£ç¢¼è½çºæµç¨åï¼\n\`\`\`mermaid\ngraph LR\n A[AI è天] --> B{MD2PDF}\n B -->|è§£æ| C[PDF å ±å]\n B -->|樣å¼| D[ç²¾ç¾ä½å±]\n\`\`\`\n\n### 3. å°æ¥æçåè½\n- [x] **Mermaid.js** 渲æ\n- [x] **GitHub å¼è (Callouts)**\n- [x] **å¸è¡ç´è§æ¨ (Footnotes)**\n- [ ] **èªå®ç¾© CSS** (å³å°æ¨åº)\n\n#### GitHub å¼èæ¯æ\n> [!NOTE]\n> 鿝ä¸å **NOTE** (å註)ï¼ç¨æ¼çªåºé¡¯ç¤ºä¸æ§ä½éè¦çè³è¨ã\n\n> [!TIP]\n> 鿝ä¸å **TIP** (æç¤º)ï¼å¹«å©ç¨æ¶æ´é«æå°å®æä»»åã\n\n> [!WARNING]\n> 鿝ä¸å **WARNING** (è¦å)ï¼æéç¨æ¶æ³¨ææ½å¨ç風éªã\n\n> [!DANGER]\n> 鿝ä¸å **DANGER** (å±éª)ï¼ç¨æ¼è¦ç¤ºå´éé¯èª¤æé¢¨éªã\n\n#### å¸è¡ç´è§æ¨æ¯æ\nMarkdown é常強大[^1]ï¼ä½ MD2PDF è®å®æ´åªé [^2]ã\n\n[^1]: æ¨æº Markdown æ¯æåºç¤ææ¬æ ¼å¼ã\n[^2]: MD2PDF å¢å äºå°æ¥ PDF 輸åºåé«ç´èªæ³æ¯æã\n\n---\n\n## ð ç¾ä»£çè¨æ¯æ (Obsidian & Notion)\nç¸å®¹æ¨æåæ¡ççè¨èªæ³ï¼\n\n- **ææ¬é«äº®**ï¼æ¨å¯ä»¥åå¨ Obsidian ä¸ä¸æ¨£ ==é«äº®éé»å §å®¹==ã\n- **éåé£çµ (WikiLinks)**ï¼æ¯æ [[å §é¨é£çµ]] 樣å¼çèªæ³ã\n\n---\n\n## ð æè¡åçæç\n\`\`\`javascript\n// ç°¡æ½å°æ¥ç代碼å¡é¡¯ç¤º\nfunction welcome() {\n console.log("æ¡è¿ä½¿ç¨ MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ éç¼è API\nèªå忍ç工使µç¨ï¼\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð 黿 **ãå¯åº PDFã** æ¥çææã`, 210 aiPrompt: "AI 婿", guideTitle: "AI 婿ä¸éµå¯åºæå", guideSub: "極éçæå°æ¥ PDF", howItWorks: "æ ¸å¿è½å", 211 copyPrompt: "è¤è£½ AI 系統æç¤ºè©", benefit1: "æçåªå", benefit2: "é±ç§å®å ¨", 212 legalTitle: "å®å ¨èæ¢æ¬¾", dataSecurity: "æ¸æå®å ¨", termsOfService: "使ç¨åè°", privacyPolicy: "é±ç§æ¿ç", 213 dataSecurityDesc: "MD2PDF æ¯ä¸ä¸ªå®¢æ·ç«¯åºç¨ãæ¨ç Markdown å §å®¹åæªæ¡å å²åå¨ç覽å¨çæ¬å°å²åï¼Local Storageï¼ä¸ãæåä¸è¨å²åæ¨ç§æææªçæåå¨ï¼ç¢ºä¿æé«çé±ç§åå®å ¨æ§ã", 214 termsDesc: "æ¬æåç± Quanta Flow Inc. (ç¾åæ·ä¿æå·) æä¾ãç¨æ¶é åæç¸éåè°ï¼æåä¸å°çè¦½å¨æ¬å°å²åçæ¸æä¸å¤±æ¿æè²¬ä»»ã", 215 privacyDesc: "é±ç§åªå ï¼ææææªå卿¬å°èçãæåå æ¶éå°éå¿åæ¸æç¨æ¼åæï¼æªä¾å¯è½å å«ç¶éåè¦èçç廣åå±ç¤ºã", 216 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 217 terms: "æåæ¢æ¬¾", privacy: "é±ç§æ¿ç", security: "å®å ¨èªªæ", 218 themeLight: "æäº®", themeDark: "æé»", themeSepia: "è·ç¼", themeSolarized: "ç¶å ¸", themeOcean: "æ·±è", 219 viewEditor: "å ¨è¢å¹ç·¨è¼¯", viewSplit: "å屿¨¡å¼", viewPreview: "å ¨è¢å¹é 覽", 220 modalCancel: "確èª", modalConfirm: "確èª", newItemName: "è«è¼¸å ¥å稱", 221 renameTitle: "éæ°å½å", newFileTitle: "æ°å»ºæªæ¡", newFolderTitle: "æ°å»ºè³æå¤¾", 222 pdfStyle: "å°åºçé¢¨æ ¼", styleStandard: "æ¨æº (GitHub é¢¨æ ¼)", styleAcademic: "å¸è¡ (å ¸é å®é«)", styleModern: "ç¾ä»£ (極簡ç¡è¥¯ç·)", styleDefault: "é»èªå°åºçé¢¨æ ¼", 223 styleDefaultSimple: "é»èª", styleStandardSimple: "æ¨æº", styleAcademicSimple: "å¸è¡", styleModernSimple: "ç¾ä»£", 224 styleUpdated: "é»èª PDF é¢¨æ ¼å·²æ´æ°", 225 syncStart: "éå§éå忥 (Google Drive)...", syncSuccess: "æææªæ¡å·²åæ¥å° Google Driveï¼", syncFail: "忥失æ: ", syncReauth: "éè¦éæ°ææ¬ï¼è«åæ¬¡é»æãé²ç«¯åæ¥ãæé以飿¥ Google Driveã", 226 installMsg: "å®è£ MD2PDF å°æ¬å°ï¼é¨æé¨å°éååµä½", installBtn: "ç«
226å³å®è£", installSuccess: "æç¨å®è£æåï¼æ¨ç¾å¨å¯ä»¥ä»åºç¨åè¡¨ç´æ¥å¯å¨ã", 227 iosInstallMsg: "黿䏿¹å享æéï¼é¸æãå å ¥ä¸»ç«é¢ãï¼å³å¯å App 䏿 ·ä½¿ç¨", 228 copy: "è¤è£½å §å®¹", copyJson: "è¤è£½ JSON", copied: "å·²è¤è£½å°åªè´´æ¿", 229 language: "鏿èªè¨", theme: "主é¡é¸æ", copyContent: "è¤è£½å §å®¹", copyEditor: "è¤è£½ç·¨è¼¯å", copyPreview: "è¤è£½é 覽å", 230 sidebarSubTitle: "èªååææªæç", 231 recommended: "çºæ¨æ¨è¦", 232 pdfSettings: "PDF è¨å®", pdfSettingsTitle: "PDF èªå®ç¾©", pdfPageSetup: "é é¢è¨ç½®", pdfCover: "å°é¢é ", 233 docTitle: "æä»¶æ¨é¡", docSubtitle: "坿¨é¡ / ä½è ", pdfTOC: "ç®éé (TOC)", pdfHeaderFooter: "é é¦èé å°¾", 234 pdfHeader: "é é¦", pdfFooter: "é å°¾", pdfMacros: "æ¯æ´è®æ¸: {date}, {title}, {pagenum}, {totalpages}", modalSave: "å²åè¨ç½®", 235 pdfLeft: "å·¦å´", pdfCenter: "ä¸é", pdfRight: "å³å´", 236 pdfBranding: "å°æ¥ç´ AI 婿 PDF æç弿", 237 exportSettings: "å¯åºè¨å®", 238 coverShowHeader: "å°é¢é¡¯ç¤ºé é¦/é å°¾", 239 disableBrowserHF: "ð¡ æç¤ºï¼è«ééç覽å¨é è¨é é¦é å°¾", 240 printDialogTip: "ð¡ 卿å°å°è©±æ¡ä¸ï¼è«ééç覽å¨é è¨çé é¦é å°¾é¸é ï¼ä»¥ä½¿ç¨èªè¨è¨ç½®", 241 printDialogNote: "æç¤ºï¼è«å¨æå°å°è©±æ¡ä¸ééãé é¦åé å°¾ãé¸é ", 242 headerLogoLeft: "é çå·¦å´ Logo", 243 headerLogoRight: "é çå³å´ Logo", 244 watermarkText: "æå浮水å°", 245 uploadLogo: "ä¸å³åç", 246 logoUrl: "åçé£çµ", 247 logoAndWatermark: "Logo èæµ®æ°´å°", 248 clear: "æ¸ é¤", 249 watermark: "æµ®æ°´å°æå", 250 feedbackBtnTitle: "åäº«ä½ çåé¥", 251 feedbackModalTitle: "åäº«ä½ çåé¥ ð¬", 252 feedbackModalDesc: "幫婿忹å MD2PDF â ä½ çæ¯æ¢åé¥é½å¾éè¦ï¼", 253 feedbackRatingLabel: "ä½ ç使ç¨é«é©å¦ä½ï¼", 254 feedbackMsgLabel: "ä½ çåé¥", 255 feedbackMsgPh: "èªªèªªä½ çæ³æ³ââåè½ãåé¡ã建è°é½å¯ä»¥...", 256 feedbackEmailPh: "[email protected]", 257 feedbackSubmitBtn: "æäº¤åé¥", 258 feedbackSending: "æäº¤ä¸...", 259 feedbackSuccessTitle: "æè¬ä½ ï¼ð", 260 feedbackSuccessMsg: "ä½ çåé¥å·²æ¶å°ï¼æåæèªçé±è®æ¯æ¢æè¦ï¼ä¸¦æçºæ¹å MD2PDFã", 261 feedbackErrorMsg: "æäº¤å¤±æï¼è«æª¢æ¥ç¶²è·¯å¾é試ã", 262 feedbackNewsletterTitle: "è¨é±æåçé»åå ±", 263 feedbackNewsletterDesc: "Markdown æå·§ãMD2PDF ç¢åæ´æ°èæçå·¥å ·ââå è±æï¼ä½é »ç¼éã", 264 feedbackEmailRequired: "è«è¼¸å ¥ææçé»åéµä»¶å°åã" 265 }, 266 'es': { 267 seoTitle: "MD2PDF - Convertidor Gratis de Markdown a PDF | md2pdf.cc", 268 seoDescription: "Convierte chats de IA en PDFs profesionales al instante. Soporta fórmulas KaTeX, diagramas Mermaid y sintaxis Obsidian. 100% del lado del cliente, sin registro.", 269 h1Title: "Convertidor Profesional de ChatGPT y DeepSeek a PDF", 270 mermaidTitle: "Renderizado de Diagramas Mermaid", 271 aiExportTitle: "Exportación de Chat de IA a PDF", 272 newFile: "Nuevo archivo", newFolder: "Nueva carpeta", rename: "Renombrar", download: "Descargar MD", delete: "Eliminar", 273 syncDrive: "Sincronización (2-vÃas)", exportPdf: "Exportar PDF", feedback: "Comentarios", editorPh: "# Comienza tu viaje...", 274 confirmDelete: "¿Eliminar este elemento?", welcomeTitle: "Mis informes", welcomeFile: "Bienvenido.md", 275 welcomeContent: `# MD2PDF.cc\n## **El motor profesional para la era de la IA**\n<span style="color: #7c3aed; font-weight: bold;">Privacidad primero ⢠100% del lado del cliente</span> ⢠PDF de calidad de publicación\n\n---\n\n## ⨠CaracterÃsticas principales\n\n### 1. Matemáticas y Ciencias ($LaTeX$)\nPerfectos para DeepSeek, GPT-4o o Claude:\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. Diagramas (Mermaid)\nRenderiza diagramas de flujo al instante:\n\\\`\\\`\\\`mermaid\ngraph LR\n A[AI Chat] --> B{MD2PDF}\n B -->|Parsear| C[Informe PDF]\n B -->|Estilo| D[Hermoso diseño]\n\\\`\\\`\\\`\n\n### 3. Funciones Profesionales\n- [x] Soporte **Mermaid.js**\n- [x] **GitHub Callouts**\n- [x] **Notas al pie académicas**\n- [ ] **CSS personalizado** (Próximamente)\n\n#### Callouts estilo GitHub\n> [!NOTE]\n> Esta es una **NOTA** para información neutral pero importante.\n\n> [!TIP]\n> Esta es una **SUGERENCIA** para ayudar a los usuarios a hacer las cosas mejor.\n\n> [!WARNING]\n> Esta es una **ADVERTENCIA** para acciones potencialmente peligrosas.\n\n> [!DANGER]\n> Este es un bloque de **PELIGRO** para errores crÃticos o riesgos.\n\n#### Notas al pie académicas\nMarkdown es potente[^1], pero MD2PDF lo hace elegante[^2].\n\n[^1]: El Markdown estándar admite formato de texto básico.\n[^2]: MD2PDF añade salida PDF profesional y sintaxis avanzada.\n\n---\n\n## ð Soporte para notas modernas (Obsidian & Notion)\nCompatible con tu sintaxis favorita para tomar notas:\n\n- **Resaltado**: Puedes ==resaltar texto importante== fácilmente.\n- **WikiLinks**: Soporte para el estilo de [[Enlaces internos]].\n\n---\n\n## ð Formato Nativo Técnico\n\\\`\\\`\\\`javascript\n// Bloques de código limpios y profesionales\nfunction holaMundo() {\n console.log(\\"¡Hola de
275sde MD2PDF!\\");\n}\n\\\`\\\`\\\`\n\n---\n\n## ð ï¸ API para desarrolladores\nAutomatiza tu flujo de trabajo:\n\\\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\\\`\n\nð Haz clic en **\\"Exportar PDF\\"** para ver la magia.`, 276 aiPrompt: "Asistente AI", guideTitle: "GuÃa de exportación AI", guideSub: "PDF profesional instantáneo", howItWorks: "CaracterÃsticas", 277 copyPrompt: "Copiar Prompt", benefit1: "Diseño Pro", benefit2: "Privacidad", 278 legalTitle: "Seguridad y Términos", dataSecurity: "Seguridad de Datos", termsOfService: "Términos de Uso", privacyPolicy: "PolÃtica de Privacidad", 279 dataSecurityDesc: "MD2PDF es una aplicación del lado del cliente. Su contenido de Markdown y archivos se almacenan únicamente en el almacenamiento local de su navegador. No mantenemos servidores que almacenen sus documentos privados, garantizando la máxima privacidad y seguridad.", 280 termsDesc: "El uso de md2pdf.cc está regido por Quanta Flow Inc. (Wyoming). No ofrecemos garantÃas por la persistencia de datos en el almacenamiento del navegador.", 281 privacyDesc: "Privacidad primero: los documentos se procesan localmente. Usamos seguimiento mÃnimo para análisis y futuros fines publicitarios.", 282 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 283 terms: "Términos", privacy: "Privacidad", security: "Seguridad", 284 themeLight: "Claro", themeDark: "Oscuro", themeSepia: "Sepia", themeSolarized: "Solarized", themeOcean: "Océano", 285 viewEditor: "Editor Completo", viewSplit: "Vista Dividida", viewPreview: "Vista Previa", 286 modalCancel: "Cancelar", modalConfirm: "Confirmar", newItemName: "Ingrese nombre", 287 renameTitle: "Renombrar", newFileTitle: "Nuevo Archivo", newFolderTitle: "Nueva Carpeta", 288 exporting: "Exportando...", exportSuccess: "¡Exportación exitosa!", exportFail: "Error de exportación. El contenido puede ser demasiado grande.", 289 pdfStyle: "Estilo PDF", styleStandard: "Estándar (GitHub)", styleAcademic: "Académico (Elegante)", styleModern: "Moderno (Limpio)", styleDefault: "Predeterminado del sistema", 290 styleDefaultSimple: "Predeterm.", styleStandardSimple: "Estándar", styleAcademicSimple: "Académico", styleModernSimple: "Moderno", 291 styleUpdated: "Estilo PDF actualizado", 292 syncStart: "Iniciando sincronización bidireccional...", syncSuccess: "¡Todos los archivos sincronizados!", syncFail: "Sincronización fallida: ", syncReauth: "Se requiere autorización. Toca Sincronizar nuevamente para conectar Google Drive.", 293 installMsg: "Instalar MD2PDF en el escritorio", installBtn: "Instalar", installSuccess: "¡Aplicación instalada con éxito!", 294 iosInstallMsg: "Toca el botón compartir y luego 'Añadir a la pantalla de inicio' para instalarlo", 295 copy: "Copiar", copyJson: "Copiar JSON", copied: "Copiado al portapapeles", 296 language: "Idioma", theme: "Tema", copyContent: "Copiar Contenido", copyEditor: "Copiar Editor", copyPreview: "Copiar Vista Previa", 297 sidebarSubTitle: "Documentación Automatizada", 298 paste: "Pegar desde el portapapeles", 299 recommended: "Recomendado", 300 pdfSettings: "Ajustes PDF", pdfSettingsTitle: "Personalización PDF", pdfPageSetup: "Configuración de Página", pdfCover: "Portada", 301 docTitle: "TÃtulo del Documento", docSubtitle: "SubtÃtulo / Autor", pdfTOC: "Ãndice (TOC)", pdfHeaderFooter: "Encabezado y Pie de página", 302 pdfHeader: "Encabezado", pdfFooter: "Pie de página", pdfMacros: "Macros: {date}, {title}, {pagenum}, {totalpages}", modalSave: "Guardar y Cerrar", 303 pdfLeft: "Izquierda", pdfCenter: "Centro", pdfRight: "Derecha", 304 pdfBranding: "El motor profesional de PDF para artefactos de IA", 305 exportSettings: "Configuración de exportación", 306 coverShowHeader: "Mostrar encabezado/pie en portada", 307 disableBrowserHF: "ð¡ Consejos: Desactivar H/P", 308 printDialogTip: "ð¡ En el diálogo de impresión, desactive los encabezados/pies predeterminados del navegador para usar configuraciones personalizadas", 309 printDialogNote: "Nota: Desactive 'Encabezados y pies de página' en el diálogo de impresión", 310 headerLogoLeft: "Logo de encabezado (izq.)", 311 headerLogoRight: "Logo de encabezado (der.)", 312 watermarkText: "Marca de agua", 313 uploadLogo: "Subir", 314 logoUrl: "URL del logo", 315 logoAndWatermark: "Logo y Marca de Agua", 316 clear: "Eliminar", 317 watermark: "Marca de agua", 318 feedbackBtnTitle: "Compartir opinión", 319 feedbackModalTitle: "Compartir tu opinión ð¬", 320 feedbackModalDesc: "Ayúdanos a mejorar MD2PDF â ¡tu opinión importa!", 321 feedbackRatingLabel: "¿Cómo es tu experiencia?", 322 feedbackMsgLabel: "Tu opinión", 323 feedbackMsgPh: "Cuéntanos qué piensas â funciones, errores, mejoras...", 324 feedbackEmailPh: "[email protected]", 325 feedbackSubmitBtn: "Enviar opinión", 326 feedbackSending: "Enviando...", 327 feedbackSuccessTitle: "¡Gracias! ð", 328 feedbackSuccessMsg: "Tu opinión ha sido recibida. La leeremos con cuidado y la usaremos para mejorar MD2PDF.", 329 feedbackErrorMsg: "Error al enviar. Comprueba tu conexión e inténtalo de nuevo.", 330 feedbackNewsletterTitle: "SuscrÃbete a nuestro boletÃn", 331 feedbackNewsletterDesc: "Trucos de Markdown, actualizaciones de MD2PDF y consejos de productividad â solo en inglés, baja frecuencia.", 332 feedbackEmailRequired: "Por favor, introduce una dirección de correo válida." 333 }, 334 'de': { 335 seoTitle: "MD2PDF - Kostenloser Markdown zu PDF Konverter | md2pdf.cc", 336 seoDescription: "Verwandeln Sie KI-Chats sofort in professionelle PDFs. Unterstützt KaTeX-Formeln, Mermaid-Diagramme und Obsidian-Syntax. 100% Client-seitig, kein Login.", 337 h1Title: "Professioneller ChatGPT & DeepSeek zu PDF Konverter", 338 mermaidTitle: "Mermaid Diagramm Rendering", 339 aiExportTitle: "AI Chat zu PDF Export", 340 newFile: "Neue Datei", newFolder: "Neuer Ordner", rename: "Umbenennen", download: "MD herunterladen", delete: "Löschen", 341 syncDrive: "Cloud-Synchronisierung (2-Wege)", exportPdf: "PDF exportieren", feedback: "Feedback", editorPh: "# Starten Sie Ihre Reise...", 342 confirmDelete: "Dieses Element löschen?", welcomeTitle: "Meine Berichte", welcomeFile: "Willkommen.md", 343 welcomeContent: `# MD2PDF.cc\n## **Die professionelle Engine für die KI-Ãra**\n<span style="color: #7c3aed; font-weight: bold;">Datenschutz zuerst ⢠100% Client-seitig</span> ⢠PDF in Publikationsqualität\n\n---\n\n## ⨠Hauptmerkmale\n\n### 1. Mathematik & Wissenschaft ($LaTeX$)\nPerfekt für DeepSeek, GPT-4o oder Claude-Ausgaben:\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. Diagramme (Mermaid)\nFlussdiagramme sofort rendern:\n\`\`\`mermaid\ngraph LR\n A[KI-Chat] --> B{MD2PDF}\n B -->|Parse| C[PDF-Bericht]\n B -->|Stil| D[Schönes Layout]\n\`\`\`\n\n### 3. Professionelle Funktionen\n- [x] **Mermaid.js** Unterstützung\n- [x] **GitHub-Callouts**\n- [x] **Akademische FuÃnoten**\n- [ ] **Benutzerdefiniertes CSS** (Demnächst)\n\n#### GitHub-Style Callouts\n> [!NOTE]\n> Dies ist eine **NOTIZ** für neutrale, aber wichtige Informationen.\n\n> [!TIP]\n> Dies ist ein **TIPP**, um Benutzern zu helfen, Dinge besser oder schneller zu machen.\n\n> [!WARNING]\n> Dies ist eine **WARNUNG** für potenziell gefährliche oder kritische Aktionen.\n\n> [!DANGER]\n> Dies ist ein **GEFAHR**-Block für kritische Fehler oder Datenverlustrisiken.\n\n#### Akademische FuÃnoten\nMarkdown ist mächtig[^1], aber MD2PDF macht es elegant[^2].\n\n[^1]: Standard-Markdown unterstützt grundlegende Textformatierung.\n[^2]: MD2PDF bietet professionelle PDF-Ausgabe und erweiterte Syntax.\n\n---\n\n## ð Unterstützung für moderne Notizen (Obsidian & Notion)\nKompatibel mit Ihrer bevorzugten Notiz-Syntax:\n\n- **Highlights**: Sie können ==wichtigen Text markieren==.\n- **WikiLinks**: Unterstützung für [[Interne Links]].\n\n---\n\n## ð Technologie-åç Formatierung\n\`\`\`javascript\n// Saubere und professionelle Codeblöcke\nfunction helloWorld() {\n console.log("Hallo von MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ API für Entwickler\nAutomatisieren Sie Ihren Workflow:\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}
343\`\n\nð Klicken Sie auf **"PDF exportieren"**, um die Magie zu sehen.`, 344 aiPrompt: "KI-Assistent", guideTitle: "KI-Assistent Integration", guideSub: "Sofortiges professionelles PDF", howItWorks: "Funktionsweise", 345 guideDesc: "MD2PDF ermöglicht es KI-Agenten, professionelle Berichte zu erstellen. Verwenden Sie den folgenden System-Prompt, damit die KI direkte URL-Befehle für die Adressleiste bereitstellt, um Dokumente sofort zu generieren.", 346 copyPrompt: "System-Prompt kopieren", benefit1: "Pro Layout", benefit2: "Datenschutz zuerst", 347 legalTitle: "Sicherheit & Bedingungen", dataSecurity: "Datensicherheit", termsOfService: "Nutzungsbedingungen", privacyPolicy: "Datenschutzerklärung", 348 dataSecurityDesc: "MD2PDF ist eine clientseitige Anwendung. Ihre Markdown-Inhalte und Dateien werden nur im lokalen Speicher Ihres Browsers gespeichert. Wir haben keine Server, die Ihre privaten Dokumente speichern, was maximale Privatsphäre und Sicherheit gewährleistet.", 349 termsDesc: "Die Nutzung von md2pdf.cc unterliegt Quanta Flow Inc. (Wyoming). Wir geben keine Garantien für die Datenausdauer im Browserspeicher.", 350 privacyDesc: "Datenschutz zuerst: Dokumente werden lokal verarbeitet. Wir verwenden minimales Tracking für Analysen und zukünftige Werbezwecke.", 351 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 352 terms: "Bedingungen", privacy: "Datenschutz", security: "Sicherheit", 353 themeLight: "Hell", themeDark: "Dunkel", themeSepia: "Sepia", themeSolarized: "Solarized", themeOcean: "Ozean", 354 viewEditor: "Vollbild Editor", viewSplit: "Geteilte Ansicht", viewPreview: "Vollbild Vorschau", 355 modalCancel: "Abbrechen", modalConfirm: "Bestätigen", newItemName: "Namen eingeben", 356 renameTitle: "Umbenennen", newFileTitle: "Neue Datei", newFolderTitle: "Neuer Ordner", 357 exporting: "Exportieren...", exportSuccess: "Export erfolgreich!", exportFail: "Export fehlgeschlagen. Der Inhalt ist möglicherweise zu groÃ.", 358 pdfStyle: "PDF-Stil", styleStandard: "Standard (GitHub)", styleAcademic: "Akademisch (Elegant)", styleModern: "Modern (Clean)", styleDefault: "Systemstandard", 359 styleDefaultSimple: "Standard", styleStandardSimple: "Klassisch", styleAcademicSimple: "Akademisch", styleModernSimple: "Modern", 360 styleUpdated: "Standard-PDF-Stil aktualisiert", 361 syncStart: "Synchronisierung startet...", syncSuccess: "Dateien mit Google Drive synchronisiert!", syncFail: "Synchronisierung fehlgeschlagen: ", syncReauth: "Autorisierung erforderlich. Tippen Sie erneut auf Synchronisieren, um Google Drive zu verbinden.", 362 installMsg: "Installieren Sie MD2PDF für eine bessere Erfahrung", installBtn: "Jetzt installieren", installSuccess: "App erfolgreich installiert!", 363 chromeInstallMsg: "MD2PDF als Desktop-App installieren", chromeInstallBtn: "App installieren", 364 iosInstallMsg: "Tippen Sie auf Teilen und dann auf 'Zum Home-Bildschirm'", 365 copy: "Kopieren", copyJson: "JSON kopieren", copied: "In Zwischenablage kopiert", 366 language: "Sprache", theme: "Thema", copyContent: "Inhalt kopieren", copyEditor: "Editor kopieren", copyPreview: "Vorschau kopieren", 367 sidebarSubTitle: "Automatisierte Dokumentation", 368 paste: "Aus Zwischenablage einfügen", 369 recommended: "Empfohlen", 370 pdfSettings: "PDF-Einstellungen", pdfSettingsTitle: "PDF-Anpassung", pdfPageSetup: "Seiteneinrichtung", pdfCover: "Titelseite", 371 docTitle: "Dokumenttitel", docSubtitle: "Untertitel / Autor", pdfTOC: "Inhaltsverzeichnis", pdfHeaderFooter: "Kopf- & FuÃzeile", 372 pdfHeader: "Kopfzeile", pdfFooter: "FuÃzeile", pdfMacros: "Makros: {date}, {title}, {pagenum}, {totalpages}", modalSave: "Speichern & SchlieÃen", 373 pdfLeft: "Links", pdfCenter: "Mitte", pdfRight: "Rechts", 374 pdfBranding: "Die professionelle KI-Artefakt-zu-PDF-Engine", 375 exportSettings: "Export-Einstellungen", 376 coverShowHeader: "Kopf/Fuà auf Cover zeigen", 377 disableBrowserHF: "ð¡ Tipp: Browser K/F deaktivieren", 378 printDialogTip: "ð¡ Deaktivieren Sie im Druckdialog die Standard-Kopf-/FuÃzeilen des Browsers", 379 printDialogNote: "Hinweis: Bitte schalten Sie 'Kopf- und FuÃzeilen' im Druckdialog aus", 380 headerLogoLeft: "Kopfzeilen-Logo (Links)", 381 headerLogoRight: "Kopfzeilen-Logo (Rechts)", 382 watermarkText: "Wasserzeichen-Text", 383 uploadLogo: "Hochladen", 384 logoUrl: "Logo-URL", 385 logoAndWatermark: "Logo & Wasserzeichen", 386 clear: "Löschen", 387 watermark: "Wasserzeichen", 388 feedbackBtnTitle: "Feedback teilen", 389 feedbackModalTitle: "Feedback teilen ð¬", 390 feedbackModalDesc: "Helfen Sie uns, MD2PDF zu verbessern â Ihr Feedback ist wichtig!", 391 feedbackRatingLabel: "Wie ist Ihre Erfahrung?", 392 feedbackMsgLabel: "Ihr Feedback", 393 feedbackMsgPh: "Teilen Sie Ihre Gedanken mit â Funktionen, Fehler, Verbesserungen...", 394 feedbackEmailPh: "[email protected]", 395 feedbackSubmitBtn: "Feedback senden", 396 feedbackSending: "Wird gesendet...", 397 feedbackSuccessTitle: "Vielen Dank! ð", 398 feedbackSuccessMsg: "Ihr Feedback wurde empfangen. Wir lesen es sorgfältig und nutzen es zur Verbesserung von MD2PDF.", 399 feedbackErrorMsg: "Senden fehlgeschlagen. Bitte überprüfen Sie Ihre Verbindung und versuchen Sie es erneut.", 400 feedbackNewsletterTitle: "Unseren Newsletter abonnieren", 401 feedbackNewsletterDesc: "Markdown-Tipps, MD2PDF-Updates & Produktivitätstricks â nur auf Englisch, niedrige Frequenz.", 402 feedbackEmailRequired: "Bitte geben Sie eine gültige E-Mail-Adresse ein." 403 }, 404 'ru': { 405 seoTitle: "MD2PDF - ÐеÑплаÑнÑй конвеÑÑÐµÑ Markdown в PDF | md2pdf.cc", 406 seoDescription: "Ðгновенно пÑевÑаÑайÑе ÑаÑÑ ÐРв пÑоÑеÑÑионалÑнÑе PDF. ÐоддеÑжка ÑоÑмÑл KaTeX, диагÑамм Mermaid и ÑинÑакÑиÑа Obsidian. 100% на ÑÑоÑоне клиенÑа, без ÑегиÑÑÑаÑии.", 407 h1Title: "ÐÑоÑеÑÑионалÑнÑй конвеÑÑÐµÑ ChatGPT и DeepSeek в PDF", 408 mermaidTitle: "ÐÑÑиÑовка диагÑамм Mermaid", 409 aiExportTitle: "ÐкÑпоÑÑ ÐÐ ÑаÑов в PDF", 410 newFile: "ÐовÑй Ñайл", newFolder: "ÐÐ¾Ð²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°", rename: "ÐеÑеименоваÑÑ", download: "СкаÑаÑÑ MD", delete: "УдалиÑÑ", 411 syncDrive: "ÐблаÑÐ½Ð°Ñ ÑÐ¸Ð½Ñ ÑонизаÑиÑ", exportPdf: "ÐкÑпоÑÑ Ð² PDF", feedback: "ÐÑзÑв", editorPh: "# ÐаÑниÑе Ñвой пÑÑÑ...", 412 confirmDelete: "УдалиÑÑ ÑÑÐ¾Ñ ÑлеменÑ?", welcomeTitle: "Ðои оÑÑеÑÑ", welcomeFile: "ÐобÑо-пожаловаÑÑ.md", 413 welcomeContent: `# MD2PDF.cc\n## **ÐÑоÑеÑÑионалÑнÑй движок Ð´Ð»Ñ ÑÑÑ ÐÐ**\n<span style="color: #7c3aed; font-weight: bold;">ÐÑиваÑноÑÑÑ Ð¿Ñежде вÑего ⢠100% на ÑÑоÑоне клиенÑа</sp
413an> ⢠PDF вÑÑокого каÑеÑÑва\n\n---\n\n## ⨠ÐлÑÑевÑе оÑобенноÑÑи\n\n### 1. ÐаÑемаÑика и наÑка ($LaTeX$)\nÐдеалÑно Ð´Ð»Ñ DeepSeek, GPT-4o или Claude:\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. ÐиагÑÐ°Ð¼Ð¼Ñ (Mermaid)\nÐÐ³Ð½Ð¾Ð²ÐµÐ½Ð½Ð°Ñ Ð¾ÑÑиÑовка блок-ÑÑ ÐµÐ¼:\n\`\`\`mermaid\ngraph LR\n A[AI Chat] --> B{MD2PDF}\n B -->|Parse| C[PDF Report]\n B -->|Style| D[Beautiful Layout]\n\`\`\`\n\n### 3. ÐÑоÑеÑÑионалÑнÑе ÑÑнкÑии\n- [x] ÐоддеÑжка **Mermaid.js**\n- [x] **GitHub Callouts**\n- [x] **ÐкадемиÑеÑкие ÑноÑки**\n- [ ] **ÐолÑзоваÑелÑÑкий CSS** (СкоÑо)\n\n#### Callouts в ÑÑиле GitHub\n> [!NOTE]\n> ÐÑо **ÐÐ ÐÐÐЧÐÐÐÐ** Ð´Ð»Ñ Ð½ÐµÐ¹ÑÑалÑной, но важной инÑоÑмаÑии.\n\n> [!TIP]\n> ÐÑо **СÐÐÐТ**, коÑоÑÑй Ð¿Ð¾Ð¼Ð¾Ð¶ÐµÑ Ð¿Ð¾Ð»ÑзоваÑелÑм делаÑÑ Ð²ÐµÑи лÑÑÑе или бÑÑÑÑее.\n\n> [!WARNING]\n> ÐÑо **ÐÐ ÐÐУÐÐ ÐÐÐÐÐÐÐ** о поÑенÑиалÑно опаÑнÑÑ Ð´ÐµÐ¹ÑÑвиÑÑ .\n\n> [!DANGER]\n> ÐÑо блок **ÐÐÐСÐÐСТЬ** Ð´Ð»Ñ ÐºÑиÑиÑеÑÐºÐ¸Ñ Ð¾Ñибок или ÑиÑков поÑеÑи даннÑÑ .\n\n#### ÐкадемиÑеÑкие ÑноÑки\nMarkdown â ÑÑо моÑно[^1], но MD2PDF Ð´ÐµÐ»Ð°ÐµÑ ÐµÐ³Ð¾ ÑлеганÑнÑм[^2].\n\n[^1]: СÑандаÑÑнÑй Markdown поддеÑÐ¶Ð¸Ð²Ð°ÐµÑ Ð±Ð°Ð·Ð¾Ð²Ð¾Ðµ ÑоÑмаÑиÑование ÑекÑÑа.\n[^2]: MD2PDF добавлÑÐµÑ Ð¿ÑоÑеÑÑионалÑнÑй вÑвод PDF и ÑаÑÑиÑеннÑй ÑинÑакÑиÑ.\n\n---\n\n## ð СовÑеменнÑе замеÑки (Obsidian & Notion)\nСовмеÑÑимо Ñ Ð²Ð°Ñим лÑбимÑм ÑинÑакÑиÑом:\n\n- **ÐÑделение**: ÐÑ Ð¼Ð¾Ð¶ÐµÑе легко ==вÑделÑÑÑ Ð²Ð°Ð¶Ð½Ñй ÑекÑÑ==.\n- **WikiLinks**: ÐоддеÑжка [[ÐнÑÑÑÐµÐ½Ð½Ð¸Ñ ÑÑÑлок]].\n\n---\n\n## ð Ð¢ÐµÑ Ð½Ð¾Ð»Ð¾Ð³Ð¸ÑнÑй ÑоÑмаÑ\n\`\`\`javascript\n// ЧиÑÑÑе и пÑоÑеÑÑионалÑнÑе блоки кода\nfunction helloWorld() {\n console.log("ÐÑÐ¸Ð²ÐµÑ Ð¾Ñ MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ API Ð´Ð»Ñ ÑазÑабоÑÑиков\nÐвÑомаÑизиÑÑйÑе Ñвой ÑабоÑий пÑоÑеÑÑ:\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð ÐажмиÑе **"ÐкÑпоÑÑ Ð² PDF"**, ÑÑÐ¾Ð±Ñ ÑвидеÑÑ Ð¼Ð°Ð³Ð¸Ñ.`, 414 aiPrompt: "ÐÐ-аÑÑиÑÑенÑ", guideTitle: "ÐнÑегÑаÑÐ¸Ñ ÐÐ-аÑÑиÑÑенÑа", guideSub: "ÐгновеннÑй пÑоÑеÑÑионалÑнÑй PDF", howItWorks: "Ðак ÑÑо ÑабоÑаеÑ", 415 guideDesc: "MD2PDF позволÑÐµÑ ÐÐ-агенÑам ÑоздаваÑÑ Ð¿ÑоÑеÑÑионалÑнÑе оÑÑеÑÑ. ÐÑполÑзÑйÑе ÑиÑÑемнÑй пÑÐ¾Ð¼Ð¿Ñ Ð½Ð¸Ð¶Ðµ, ÑÑÐ¾Ð±Ñ ÐРпÑедоÑÑавлÑл пÑÑмÑе URL-ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ Ð´Ð»Ñ Ð°Ð´ÑеÑной ÑÑÑоки Ð´Ð»Ñ Ð¼Ð³Ð½Ð¾Ð²ÐµÐ½Ð½Ð¾Ð¹ генеÑаÑии докÑменÑов.", 416 copyPrompt: "ÐопиÑоваÑÑ ÑиÑÑемнÑй пÑомпÑ", benefit1: "ÐÑо-макеÑ", benefit2: "ÐÑиваÑноÑÑÑ", 417 legalTitle: "ÐезопаÑноÑÑÑ Ð¸ ÑÑловиÑ", dataSecurity: "ÐезопаÑноÑÑÑ Ð´Ð°Ð½Ð½ÑÑ ", termsOfService: "УÑÐ»Ð¾Ð²Ð¸Ñ Ð¸ÑполÑзованиÑ", privacyPolicy: "ÐолиÑика конÑиденÑиалÑноÑÑи", 418 dataSecurityDesc: "MD2PDF â ÑÑо клиенÑÑкое пÑиложение. ÐÐ°Ñ ÐºÐ¾Ð½ÑÐµÐ½Ñ Markdown и ÑÐ°Ð¹Ð»Ñ Ñ ÑанÑÑÑÑ ÑолÑко в локалÑном Ñ ÑанилиÑе ваÑего бÑаÑзеÑа. У Ð½Ð°Ñ Ð½ÐµÑ ÑеÑвеÑов, Ñ ÑанÑÑÐ¸Ñ Ð²Ð°Ñи докÑменÑÑ, ÑÑо обеÑпеÑÐ¸Ð²Ð°ÐµÑ Ð¿Ð¾Ð»Ð½ÑÑ Ð±ÐµÐ·Ð¾Ð¿Ð°ÑноÑÑÑ.", 419 termsDesc: "ÐÑполÑзование md2pdf.cc ÑегÑлиÑÑеÑÑÑ Quanta Flow Inc. (Ðайоминг). ÐÑ Ð½Ðµ даем гаÑанÑий ÑÐ¾Ñ ÑанноÑÑи даннÑÑ Ð² бÑаÑзеÑе.", 420 privacyDesc: "ÐÑиваÑноÑÑÑ Ð¿Ñежде вÑего: докÑменÑÑ Ð¾Ð±ÑабаÑÑваÑÑÑÑ Ð»Ð¾ÐºÐ°Ð»Ñно. ÐÑ Ð¸ÑполÑзÑем минималÑное оÑÑлеживание Ð´Ð»Ñ Ð°Ð½Ð°Ð»Ð¸Ñики.", 421 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 422 terms: "УÑловиÑ", privacy: "ÐÑиваÑноÑÑÑ", security: "ÐезопаÑноÑÑÑ", 423 themeLight: "СвеÑлаÑ", themeDark: "ТемнаÑ", themeSepia: "СепиÑ", themeSolarized: "Solarized", themeOcean: "Ðкеан", 424 viewEditor: "РедакÑоÑ", viewSplit: "РазделеннÑй вид", viewPreview: "ÐÑедпÑоÑмоÑÑ", 425 modalCancel: "ÐÑмена", modalConfirm: "ÐодÑвеÑдиÑÑ", newItemName: "ÐведиÑе имÑ", 426 renameTitle: "ÐеÑеименоваÑÑ", newFileTitle: "ÐовÑй Ñайл", newFolderTitle: "ÐÐ¾Ð²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°", 427 exporting: "ÐкÑпоÑÑ...", exportSuccess: "ÐкÑпоÑÑ Ð²Ñполнен ÑÑпеÑно!", exportFail: "ÐÑибка ÑкÑпоÑÑа. Ðозможно, конÑÐµÐ½Ñ ÑлиÑком велик.", 428 pdfStyle: "СÑÐ¸Ð»Ñ PDF", styleStandard: "СÑандаÑÑ (GitHub)", styleAcademic: "ÐкадемиÑеÑкий (Elegant)", styleModern: "ÐодеÑн (Clean)", styleDefault: "СиÑÑемнÑй по ÑмолÑаниÑ", 429 styleDefaultSimple: "Ðо ÑмолÑ.", styleStandardSimple: "СÑандаÑÑ", styleAcademicSimple: "Ðкад.", styleModernSimple: "ÐодеÑн", 430 styleUpdated: "СÑÐ¸Ð»Ñ PDF по ÑмолÑÐ°Ð½Ð¸Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½", 431 syncStart: "Ð¡Ð¸Ð½Ñ ÑонизаÑиÑ...", syncSuccess: "ÐÑе ÑÐ°Ð¹Ð»Ñ ÑÐ¸Ð½Ñ ÑонизиÑÐ¾Ð²Ð°Ð½Ñ Ñ Google Drive!", syncFail: "ÐÑибка ÑÐ¸Ð½Ñ ÑонизаÑии: ", syncReauth: "ТÑебÑеÑÑÑ Ð°Ð²ÑоÑизаÑиÑ. ÐажмиÑе Â«Ð¡Ð¸Ð½Ñ ÑонизиÑоваÑÑ» еÑÑ Ñаз Ð´Ð»Ñ Ð¿Ð¾Ð´ÐºÐ»ÑÑÐµÐ½Ð¸Ñ Google Drive.", 432 installMsg: "УÑÑановиÑе MD2PDF Ð´Ð»Ñ Ð»ÑÑÑей ÑабоÑÑ", installBtn: "УÑÑановиÑÑ", installSuccess: "ÐÑиложение ÑÑпеÑно ÑÑÑановлено!", 433 chromeInstallMsg: "УÑÑановиÑÑ MD2PDF как деÑкÑопное пÑиложение", chromeInstallBtn: "УÑÑановиÑÑ", 434 iosInstallMsg: "ÐажмиÑе ÐºÐ½Ð¾Ð¿ÐºÑ 'ÐоделиÑÑÑÑ', заÑем 'Ðа ÑкÑан «Ðомой»'", 435 copy: "ÐопиÑоваÑÑ", copyJson: "ÐопиÑоваÑÑ JSON", copied: "СкопиÑовано в бÑÑÐµÑ Ð¾Ð±Ð¼ÐµÐ½Ð°", 436 language: "ЯзÑк", theme: "Тема", copyContent: "ÐопиÑоваÑÑ ÐºÐ¾Ð½ÑенÑ", copyEditor: "ÐопиÑоваÑÑ ÑедакÑоÑ", copyPreview: "ÐопиÑоваÑÑ Ð¿ÑевÑÑ", 437 sidebarSubTitle: "ÐвÑомаÑизаÑÐ¸Ñ Ð´Ð¾ÐºÑменÑаÑии", 438 paste: "ÐÑÑавиÑÑ Ð¸Ð· бÑÑеÑа", 439 recommended: "РекомендÑеÑÑÑ", 440 pdfSettings: "ÐаÑÑÑойки PDF", pdfSettingsTitle: "ÐаÑÑомизаÑÐ¸Ñ PDF", pdfPageSetup: "ÐаÑамеÑÑÑ ÑÑÑаниÑÑ", pdfCover: "ТиÑÑлÑнÑй лиÑÑ", 441 docTitle: "Ðаголовок докÑменÑа", docSubtitle: "Ðодзаголовок / ÐвÑоÑ", pdfTOC: "Ðглавление", pdfHeaderFooter: "ÐолонÑиÑÑÐ
441»Ñ", 442 pdfHeader: "ÐеÑÑ Ð½Ð¸Ð¹ колонÑиÑÑл", pdfFooter: "Ðижний колонÑиÑÑл", pdfMacros: "ÐакÑоÑÑ: {date}, {title}, {pagenum}, {totalpages}", modalSave: "Ð¡Ð¾Ñ ÑаниÑÑ Ð¸ закÑÑÑÑ", 443 pdfLeft: "Слева", pdfCenter: "ЦенÑÑ", pdfRight: "СпÑава", 444 pdfBranding: "ÐÑоÑеÑÑионалÑнÑй движок пÑеобÑÐ°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ ÐÐ-аÑÑеÑакÑов в PDF", 445 exportSettings: "ÐаÑÑÑойки ÑкÑпоÑÑа", 446 coverShowHeader: "ÐоказÑваÑÑ ÐºÐ¾Ð»Ð¾Ð½ÑиÑÑÐ»Ñ Ð½Ð° обложке", 447 disableBrowserHF: "ð¡ СовеÑ: ÐÑклÑÑиÑе ÑÑандаÑÑнÑе колонÑиÑÑлÑ", 448 printDialogTip: "ð¡ Рдиалоге пеÑаÑи оÑклÑÑиÑе ÑÑандаÑÑнÑе колонÑиÑÑÐ»Ñ Ð±ÑаÑзеÑа", 449 printDialogNote: "ÐÑимеÑание: ÐÑклÑÑиÑе «ÐеÑÑ Ð½Ð¸Ðµ и нижние колонÑиÑÑлÑ» в наÑÑÑÐ¾Ð¹ÐºÐ°Ñ Ð¿ÐµÑаÑи", 450 headerLogoLeft: "ÐогоÑип ÑвеÑÑ Ñ (Слева)", 451 headerLogoRight: "ÐогоÑип ÑвеÑÑ Ñ (СпÑава)", 452 watermarkText: "ТекÑÑ Ð²Ð¾Ð´Ñного знака", 453 uploadLogo: "ÐагÑÑзиÑÑ", 454 logoUrl: "URL логоÑипа", 455 logoAndWatermark: "ÐогоÑип и водÑной знак", 456 clear: "ÐÑиÑÑиÑÑ", 457 watermark: "ÐодÑной знак", 458 feedbackBtnTitle: "ÐоделиÑÑÑÑ Ð¾ÑзÑвом", 459 feedbackModalTitle: "ÐоделиÑÑÑÑ Ð¾ÑзÑвом ð¬", 460 feedbackModalDesc: "ÐомогиÑе нам ÑлÑÑÑиÑÑ MD2PDF â Ð²Ð°Ñ Ð¾ÑзÑв важен!", 461 feedbackRatingLabel: "Ðак вам ÑабоÑа Ñ Ð¿Ñиложением?", 462 feedbackMsgLabel: "ÐÐ°Ñ Ð¾ÑзÑв", 463 feedbackMsgPh: "ÐоделиÑеÑÑ Ð¼ÑÑлÑми â ÑÑнкÑии, оÑибки, пÑедложениÑ...", 464 feedbackEmailPh: "ваÑ@email.com", 465 feedbackSubmitBtn: "ÐÑпÑавиÑÑ Ð¾ÑзÑв", 466 feedbackSending: "ÐÑпÑавка...", 467 feedbackSuccessTitle: "СпаÑибо! ð", 468 feedbackSuccessMsg: "ÐÐ°Ñ Ð¾ÑзÑв полÑÑен. ÐÑ Ð²Ð½Ð¸Ð¼Ð°ÑелÑно его пÑоÑиÑаем и иÑполÑзÑем Ð´Ð»Ñ ÑлÑÑÑÐµÐ½Ð¸Ñ MD2PDF.", 469 feedbackErrorMsg: "Ðе ÑдалоÑÑ Ð¾ÑпÑавиÑÑ. ÐÑовеÑÑÑе подклÑÑение и попÑобÑйÑе Ñнова.", 470 feedbackNewsletterTitle: "ÐодпиÑаÑÑÑÑ Ð½Ð° ÑаÑÑÑлкÑ", 471 feedbackNewsletterDesc: "СовеÑÑ Ð¿Ð¾ Markdown, Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ MD2PDF и лайÑÑ Ð°ÐºÐ¸ â ÑолÑко на английÑком, Ñедко.", 472 feedbackEmailRequired: "ÐожалÑйÑÑа, введиÑе коÑÑекÑнÑй адÑÐµÑ ÑлекÑÑонной поÑÑÑ." 473 }, 474 'ja': { 475 seoTitle: "MD2PDF - ç¡æ Markdown PDF 夿ãã¼ã« | md2pdf.cc", 476 seoDescription: "AIãã£ãããç¬æã«æ¬æ ¼çãªPDFã¸ãKaTeXæ°å¼ãMermaidãã¤ã¢ã°ã©ã ãObsidianæ§æã«å¯¾å¿ã100%ã¯ã©ã¤ã¢ã³ããµã¤ãå¦çã§ãã°ã¤ã³ä¸è¦ã", 477 h1Title: "ãããã§ãã·ã§ãã«ãª ChatGPT & DeepSeek PDF 夿ãã¼ã«", 478 mermaidTitle: "Mermaid ãã¤ã¢ã°ã©ã ã®ã¬ã³ããªã³ã°", 479 aiExportTitle: "AI ãã£ããã® PDF æ¸ãåºã", 480 newFile: "æ°è¦ãã¡ã¤ã«", newFolder: "æ°è¦ãã©ã«ã", rename: "ååã®å¤æ´", download: "MDããã¦ã³ãã¼ã", delete: "åé¤", 481 syncDrive: "ã¯ã©ã¦ãåæ", exportPdf: "PDFãæ¸ãåºã", feedback: "ãã£ã¼ãããã¯", editorPh: "# ããã³ããã®æ ãå§ãã¾ããã...", 482 confirmDelete: "ãã®ã¢ã¤ãã ãåé¤ãã¾ããï¼", welcomeTitle: "ãã¤ã¬ãã¼ã", welcomeFile: "ãããã.md", 483 welcomeContent: `# MD2PDF.cc\n## **AIæä»£ã®ããã®ãããã§ãã·ã§ãã«ã¨ã³ã¸ã³**\n<span style="color: #7c3aed; font-weight: bold;">ãã©ã¤ãã·ã¼ç¬¬ä¸ ⢠100% ã¯ã©ã¤ã¢ã³ãå´</span>
483 ⢠åºçå質ã®PDF\n\n---\n\n## â¨ ä¸»ãªæ©è½\n\n### 1. æ°å¦ã¨ç§å¦ ($LaTeX$)\nDeepSeekãGPT-4oãClaudeã®åºåã«æé©ï¼\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. ãã¤ã¢ã°ã©ã (Mermaid)\nããã¼ãã£ã¼ããå³åº§ã«ã¬ã³ããªã³ã°ï¼\n\`\`\`mermaid\ngraph LR\n A[AIãã£ãã] --> B{MD2PDF}\n B -->|è§£æ| C[PDFã¬ãã¼ã]\n B -->|ã¹ã¿ã¤ã«| D[ç¾ããã¬ã¤ã¢ã¦ã]\n\`\`\`\n\n### 3. ãããã§ãã·ã§ãã«æ©è½\n- [x] **Mermaid.js** ãµãã¼ã\n- [x] **GitHub ã³ã¼ã«ã¢ã¦ã**\n- [x] **å¦è¡çãªè注**\n- [ ] **ã«ã¹ã¿ã CSS** (éããªãç»å ´)\n\n#### GitHubã¹ã¿ã¤ã« ã³ã¼ã«ã¢ã¦ã\n> [!NOTE]\n> ãã㯠**NOTE** (åè) ã§ããéè¦ã§ããä¸ç«çãªæ å ±ã«ä½¿ç¨ãã¾ãã\n\n> [!TIP]\n> ãã㯠**TIP** (ãã³ã) ã§ãã使¥ãããè¯ããã¾ãã¯éãè¡ãã®ã«å½¹ç«ã¡ã¾ãã\n\n> [!WARNING]\n> è¿æ¯ **WARNING** (è¦å) ã§ããå±éºãªæä½ãéè¦ãªã¢ã¯ã·ã§ã³ã«ã¤ãã¦è¦åãã¾ãã\n\n> [!DANGER]\n> ãã㯠**DANGER** (å±éº) ã§ããè´å½çãªã¨ã©ã¼ããã¼ã¿æ¶å¤±ã®ãªã¹ã¯ã示ãã¾ãã\n\n#### å¦è¡çãªè注\nMarkdownã¯å¼·å[^1]ã§ãããMD2PDFã¯ãããã¨ã¬ã¬ã³ã[^2]ã«ãã¾ãã\n\n[^1]: æ¨æºã®Markdownã¯åºæ¬çãªããã¹ãè£ é£¾ããµãã¼ããã¾ãã\n[^2]: MD2PDFã¯ãããã§ãã·ã§ãã«ãªPDFåºåã¨é«åº¦ãªæ§æã追å ãã¾ãã\n\n---\n\n## ð ã¢ãã³ãªãã¼ã管çã®ãµãã¼ã (Obsidian & Notion)\nã好ã¿ã®ãã¼ãæ§æã¨äºææ§ãããã¾ãï¼\n\n- **ãã¤ã©ã¤ã**: ==éè¦ãªããã¹ãããã¤ã©ã¤ã== ã§ãã¾ãã\n- **WikiLinks**: [[å é¨ãªã³ã¯]] ã¹ã¿ã¤ã«ã®ãµãã¼ãã\n\n---\n\n## ð æè¡ç³»åããã©ã¼ããã\n\`\`\`javascript\n// æ¸ æ½ã§ãããã§ãã·ã§ãã«ãªã³ã¼ããããã¯\nfunction helloWorld() {\n console.log("Hello from MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ éçºè åã API\nã¯ã¼ã¯ããã¼ãèªååï¼\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð **ãPDFãæ¸ãåºãã** ãã¯ãªãã¯ãã¦éæ³ãä½é¨ãã¦ãã ããã`, 484 aiPrompt: "AIã¢ã·ã¹ã¿ã³ã", guideTitle: "AIã¢ã·ã¹ã¿ã³ãã®çµ±å", guideSub: "é«å質ãªPDFãå³åº§ã«çæ", howItWorks: "æ©è½ã«ã¤ãã¦", 485 guideDesc: "MD2PDFã使ç¨ããã¨ãAIã¨ã¼ã¸ã§ã³ãããããã§ãã·ã§ãã«ãªã¬ãã¼ãã使ã§ãã¾ãã以ä¸ã®ã·ã¹ãã ããã³ããã使ç¨ãã¦ãAIãã¢ãã¬ã¹ãã¼ç¨ã®ç´æ¥URLã³ãã³ããæä¾ããããã¥ã¡ã³ããå³åº§ã«çæã§ããããã«ãã¾ãã", 486 copyPrompt: "ã·ã¹ãã ããã³ãããã³ãã¼", benefit1: "ãã仿§ã®ã¬ã¤ã¢ã¦ã", benefit2: "ãã©ã¤ãã·ã¼éè¦", 487 legalTitle: "ã»ãã¥ãªãã£ã¨è¦ç´", dataSecurity: "ãã¼ã¿ã»ãã¥ãªãã£", termsOfService: "å©ç¨è¦ç´", privacyPolicy: "ãã©ã¤ãã·ã¼ããªã·ã¼", 488 dataSecurityDesc: "MD2PDFã¯ã¯ã©ã¤ã¢ã³ãå´ã¢ããªã±ã¼ã·ã§ã³ã§ããMarkdownã³ã³ãã³ãã¨ãã¡ã¤ã«ã¯ãã©ã¦ã¶ã®ãã¼ã«ã«ã¹ãã¬ã¼ã¸ã«ã®ã¿ä¿åããã¾ãããã©ã¤ãã¼ããªããã¥ã¡ã³ããä¿åãããµã¼ãã¼ã¯ä¸åãªããæå¤§éã®ãã©ã¤ãã·ã¼ã¨ã»ãã¥ãªãã£ã確ä¿ãã¾ãã", 489 termsDesc: "md2pdf.ccã®å©ç¨ã¯ãQuanta Flow Inc. (Wyoming) ã«ãã£ã¦ç®¡çããã¾ãããã©ã¦ã¶ã¹ãã¬ã¼ã¸ã®ãã¼ã¿æ°¸ç¶æ§ã«ã¤ãã¦ã¯ä¿è¨¼ãããã¾ããã", 490 privacyDesc: "ãã©ã¤ãã·ã¼ç¬¬ä¸ï¼ããã¥ã¡ã³ãã¯ãã¼ã«ã«ã§å¦çããã¾ããåæããã³å°æ¥ã®åºåç®çã§æå°éã®ãã©ããã³ã°ã使ç¨ãã¾ãã", 491 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 492 terms: "å©ç¨è¦ç´", privacy: "ãã©ã¤ãã·ã¼", security: "ã»ãã¥ãªãã£", 493 themeLight: "ã©ã¤ã", themeDark: "ãã¼ã¯", themeSepia: "ã»ãã¢", themeSolarized: "Solarized", themeOcean: "ãªã¼ã·ã£ã³", 494 viewEditor: "å ¨ç»é¢ã¨ãã£ã¿", viewSplit: "åå²è¡¨ç¤º", viewPreview: "å ¨ç»é¢ãã¬ãã¥ã¼", 495 modalCancel: "ãã£ã³ã»ã«", modalConfirm: "確èª", newItemName: "ååãå ¥å", 496 renameTitle: "ååã®å¤æ´", newFileTitle: "æ°è¦ãã¡ã¤ã«", newFolderTitle: "æ°è¦ãã©ã«ã", 497 exporting: "æ¸ãåºãä¸...", exportSuccess: "æ¸ãåºãæåï¼", exportFail: "æ¸ãåºãã«å¤±æãã¾ãããå 容ã大ããããå¯è½æ§ãããã¾ãã", 498 pdfStyle: "PDFã¹ã¿ã¤ã«", styleStandard: "æ¨æº (GitHub)", styleAcademic: "ã¢ã«ããã㯠(Elegant)", styleModern: "ã¢ãã³ (Clean)", styleDefault: "ã·ã¹ãã ããã©ã«ã", 499 styleDefaultSimple: "ããã©ã«ã", styleStandardSimple: "æ¨æº", styleAcademicSimple: "å¦è¡", styleModernSimple: "ã¢ãã³", 500 styleUpdated: "ããã©ã«ãã®PDFã¹ã¿ã¤ã«ãæ´æ°ããã¾ãã", 501 syncStart: "åæãéå§ãã¦ãã¾ã...", syncSuccess: "ãã¹ã¦ã®ãã¡ã¤ã«ã Google Drive ã«åæããã¾ããï¼", syncFail: "åæå¤±æ: ", syncReauth: "åèªè¨¼ãå¿ è¦ã§ãããã¯ã©ã¦ãåæããããä¸åº¦ã¿ããã㦠Google Drive ã«æ¥ç¶ãã¦ãã ããã", 502 installMsg: "ããè¯ãä½é¨ã®ããã«MD2PDFãã¤ã³ã¹ãã¼ã«", installBtn: "ä»ããã¤ã³ã¹ãã¼ã«", installSuccess: "ã¢ããªãæ£å¸¸ã«ã¤ã
502³ã¹ãã¼ã«ããã¾ããï¼", 503 chromeInstallMsg: "MD2PDFããã¹ã¯ãããã¢ããªã¨ãã¦ã¤ã³ã¹ãã¼ã«", chromeInstallBtn: "ã¢ããªãã¤ã³ã¹ãã¼ã«", 504 iosInstallMsg: "å ±æãã¿ã³ãã¿ãããã¦ããã¼ã ç»é¢ã«è¿½å ãã鏿", 505 copy: "ã³ãã¼", copyJson: "JSONãã³ãã¼", copied: "ã¯ãªãããã¼ãã«ã³ãã¼ãã¾ãã", 506 language: "è¨èªé¸æ", theme: "ãã¼ã鏿", copyContent: "å 容ãã³ãã¼", copyEditor: "ã¨ãã£ã¿ãã³ãã¼", copyPreview: "ãã¬ãã¥ã¼ãã³ãã¼", 507 sidebarSubTitle: "èªååãããããã¥ã¡ã³ã使", 508 paste: "ã¯ãªãããã¼ãããè²¼ãä»ã", 509 recommended: "ãããã", 510 pdfSettings: "PDFè¨å®", pdfSettingsTitle: "PDFã®ã«ã¹ã¿ãã¤ãº", pdfPageSetup: "ãã¼ã¸è¨å®", pdfCover: "表ç´", 511 docTitle: "ããã¥ã¡ã³ãã¿ã¤ãã«", docSubtitle: "ãµãã¿ã¤ãã« / èè ", pdfTOC: "ç®æ¬¡", pdfHeaderFooter: "ãããã¼ã¨ããã¿ã¼", 512 pdfHeader: "ãããã¼", pdfFooter: "ããã¿ã¼", pdfMacros: "ãã¯ã: {date}, {title}, {pagenum}, {totalpages}", modalSave: "ä¿åãã¦éãã", 513 pdfLeft: "å·¦å¯ã", pdfCenter: "ä¸å¤®å¯ã", pdfRight: "å³å¯ã", 514 pdfBranding: "ãããã§ãã·ã§ãã«ãªAIã¢ã¼ãã£ãã¡ã¯ãPDF夿ã¨ã³ã¸ã³", 515 exportSettings: "æ¸ãåºãè¨å®", 516 coverShowHeader: "表ç´ã«ãããã¼/ããã¿ã¼ã表示", 517 disableBrowserHF: "ð¡ ãã³ãï¼ãã©ã¦ã¶ã®H/Fãç¡å¹ã«ãã", 518 printDialogTip: "ð¡ å°å·ãã¤ã¢ãã°ã§ãã©ã¦ã¶ã®ããã©ã«ãã®ãããã¼/ããã¿ã¼ãç¡å¹ã«ãã¦ãã ãã", 519 printDialogNote: "注æï¼å°å·ãã¤ã¢ãã°ã§ããããã¼ã¨ããã¿ã¼ãããªãã«ãã¦ãã ãã", 520 headerLogoLeft: "ãããã¼ãã´ (å·¦)", 521 headerLogoRight: "ãããã¼ãã´ (å³)", 522 watermarkText: "éããããã¹ã", 523 uploadLogo: "ã¢ãããã¼ã", 524 logoUrl: "ãã´URL", 525 logoAndWatermark: "ãã´ã¨éãã", 526 clear: "ã¯ãªã¢", 527 watermark: "éãã", 528 feedbackBtnTitle: "ãã£ã¼ãããã¯ãéã", 529 feedbackModalTitle: "ãã£ã¼ãããã¯ãéã ð¬", 530 feedbackModalDesc: "MD2PDFã®æ¹åã«ãååãã ãã â ããªãã®ãæè¦ã¯å¤§åã§ãï¼", 531 feedbackRatingLabel: "ãå©ç¨ä½é¨ã¯ãããã§ããï¼", 532 feedbackMsgLabel: "ãã£ã¼ãããã¯", 533 feedbackMsgPh: "æ©è½ããã°ãæ¹åæ¡ãªã©ä½ã§ããæ°è»½ã«ã©ãã...", 534 feedbackEmailPh: "[email protected]", 535 feedbackSubmitBtn: "éä¿¡ãã", 536 feedbackSending: "éä¿¡ä¸...", 537 feedbackSuccessTitle: "ãããã¨ããããã¾ãï¼ð", 538 feedbackSuccessMsg: "ãã£ã¼ãããã¯ãåãåãã¾ãããå 容ãä¸å¯§ã«ç¢ºèªããMD2PDFã®æ¹åã«æ´»ããã¾ãã", 539 feedbackErrorMsg: "éä¿¡ã«å¤±æãã¾ãããæ¥ç¶ã確èªãã¦ããä¸åº¦ã試ããã ããã", 540 feedbackNewsletterTitle: "ãã¥ã¼ã¹ã¬ã¿ã¼ãè³¼èªãã", 541 feedbackNewsletterDesc: "Markdownã®ãã³ããMD2PDFã®ã¢ãããã¼ãï¼çç£æ§ãã¯ãã㯠â è±èªã®ã¿ãä½é »åº¦é ä¿¡ã", 542 feedbackEmailRequired: "æå¹ãªã¡ã¼ã«ã¢ãã¬ã¹ãå ¥åãã¦ãã ããã" 543 }, 544 'ko': { 545 seoTitle: "MD2PDF - ë¬´ë£ Markdown PDF ë³í기 | md2pdf.cc", 546 seoDescription: "AI ì±í ì ì¦ì ì 문ì ì¸ PDFë¡ ë³ííì¸ì. KaTeX ìí ê³µì, Mermaid ë¤ì´ì´ê·¸ë¨, Obsidian ë¬¸ë² ì§ì. 100% í´ë¼ì´ì¸í¸ 측 ì²ë¦¬, ë¡ê·¸ì¸ ë¶íì.", 547 h1Title: "ì 문ì ì¸ ChatGPT ë° DeepSeek PDF ë³í기", 548 mermaidTitle: "Mermaid ë¤ì´ì´ê·¸ë¨ ë ëë§", 549 aiExportTitle: "AI ì±í PDF ë´ë³´ë´ê¸°", 550 newFile: "ì íì¼", newFolder: "ì í´ë", rename: "ì´ë¦ ë°ê¾¸ê¸°", download: "MD ë¤ì´ë¡ë", delete: "ìì ", 551 syncDrive: "í´ë¼ì°ë ë기í", exportPdf: "PDF ë´ë³´ë´ê¸°", feedback: "í¼ëë°±", editorPh: "# í롬íí¸ ì¬ì ì ììíì¸ì...", 552 confirmDelete: "ì´ í목ì ìì íìê² ìµëê¹?", welcomeTitle: "ë´ ë³´ê³ ì", welcomeFile: "íìí©ëë¤.md", 553 welcomeContent: `# MD2PDF.cc\n## **AI ìë를 ìí ì 문 ìì§**\n<span style="color: #7c3aed; font-weight: bold;">ê°ì¸ì ë³´ ë³´í¸ ì°ì ⢠100% í´ë¼ì´ì¸í¸ 측</span> ⢠ì¶í íì§ì PDF\n\n---\n\n## ⨠주ì í¹ì§\n\n### 1. ìí ë° ê³¼í ($LaTeX$)\nDeepSeek, GPT-4o ëë Claude ì¶ë ¥ì ìë²½ ëì:\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. ë¤ì´ì´ê·¸ë¨ (Mermaid)\níë¡ì°ì°¨í¸ë¥¼ ì¦ì ë ëë§:\n\`\`\`mermaid\ngraph LR\n A[AI Chat] --> B{MD2PDF}\n B -->|Parse| C[PDF Report]\n B -->|Style| D[Beautiful Layout]\n\`\`\`\n\n### 3. ì 문 기ë¥\n- [x] **Mermaid.js** ì§ì\n- [x] **GitHub Callouts**\n- [x] **íì ì ê°ì£¼**\n- [ ] **커ì¤í CSS** (ê³§ ì¶ì)\n\n#### GitHub ì¤íì¼ ì½ìì\n> [!NOTE]\n> ì¤ë¦½ì ì´ì§ë§ ì¤ìí ì 보를 ìí **ì°¸ê³ ** íìì ëë¤.\n\n> [!TIP]\n> ìì ì ë ìíê±°ë ë¹ ë¥´ê² íë ë° ëìì´ ëë **í**ì ëë¤.\n\n> [!WARNING]\n> ì ì¬ì ì¼ë¡ ìííê±°ë ì¤ìí ìì ì ëí **ê²½ê³ **ì ëë¤.\n\n> [!DANGER]\n> ì¹ëª ì ì¸ ì¤ë¥ë ë°ì´í° ìì¤ ìíì ì리ë **ìí** ë¸ë¡ì ëë¤.\n\n#### íì ì ê°ì£¼\nM
553arkdownì ê°ë ¥[^1]íì§ë§, MD2PDFë ì´ë¥¼ ì°ì[^2]íê² ë§ëëë¤.\n\n[^1]: íì¤ Markdownì 기본 í ì¤í¸ ììì ì§ìí©ëë¤.\n[^2]: MD2PDFë ì 문ì ì¸ PDF ì¶ë ¥ê³¼ ê³ ê¸ êµ¬ë¬¸ì ì¶ê°í©ëë¤.\n\n---\n\n## ð íëì ì¸ ë ¸í¸ ìì± ì§ì (Obsidian & Notion)\nì í¸íë ë ¸í¸ êµ¬ë¬¸ê³¼ í¸íë©ëë¤:\n\n- **íì´ë¼ì´í¸**: ==ì¤ìí í ì¤í¸ë¥¼ ê³ ì íê²== íìí ì ììµëë¤.\n- **WikiLinks**: [[ë´ë¶ ë§í¬]] ì¤íì¼ ì§ì.\n\n---\n\n## ð 기ì ì¹íì í¬ë§·í \n\`\`\`javascript\n// ê¹ëíê³ ì 문ì ì¸ ì½ë ë¸ë¡\nfunction helloWorld() {\n console.log("Hello from MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ ê°ë°ìì© API\nìí¬íë¡ì° ìëí:\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð **"PDF ë´ë³´ë´ê¸°"**를 í´ë¦íì¬ ë§ë²ì íì¸íì¸ì.`, 554 aiPrompt: "AI ì´ìì¤í´í¸", guideTitle: "AI ì´ìì¤í´í¸ íµí©", guideSub: "ì¦ê°ì ì¸ ì 문 PDF ìì±", howItWorks: "ìë ë°©ì", 555 guideDesc: "MD2PDFë AI ìì´ì í¸ê° ì 문ì ì¸ ë³´ê³ ì를 ìì±í ì ìê² í´ì¤ëë¤. ìëì ìì¤í í롬íí¸ë¥¼ ì¬ì©íì¬ AIê° ì£¼ìì°½ì ì§ì ì ë ¥í ì ìë URL ëª ë ¹ì ì ê³µíëë¡ í¨ì¼ë¡ì¨ ì¦ì 문ì를 ìì±íì¸ì.", 556 copyPrompt: "ìì¤í í롬íí¸ ë³µì¬", benefit1: "íë¡ ë ì´ìì", benefit2: "ê°ì¸ì ë³´ ë³´í¸", 557 legalTitle: "ë³´ì ë° ì½ê´", dataSecurity: "ë°ì´í° ë³´ì", termsOfService: "ìë¹ì¤ ì½ê´", privacyPolicy: "ê°ì¸ì ë³´ ì²ë¦¬ë°©ì¹¨", 558 dataSecurityDesc: "MD2PDFë í´ë¼ì´ì¸í¸ 측 ì í리ì¼ì´ì ì ëë¤. ê·íì Markdown ì½í ì¸ ì íì¼ì ë¸ë¼ì°ì ì ë¡ì»¬ ì ì¥ììë§ ì ì¥ë©ëë¤. ì í¬ë ê·íì ê°ì¸ 문ì를 ì ì¥íë ìë²ë¥¼ ì´ìíì§ ìì ìµê³ ìì¤ì ê°ì¸ì ë³´ ë³´í¸ì ë³´ìì ë³´ì¥í©ëë¤.", 559 termsDesc: "md2pdf.cc ì´ì©ì Quanta Flow Inc. (Wyoming)ì ê´í ì ë°ìµëë¤. ë¸ë¼ì°ì ì ì¥ìì ë°ì´í° ë³´ì¡´ì ëí´ìë ë³´ì¦íì§ ììµëë¤.", 560 privacyDesc: "ê°ì¸ì ë³´ ë³´í¸ ì°ì : 문ìë ë¡ì»¬ìì ì²ë¦¬ë©ëë¤. ë¶ì ë° í¥í ê´ê³ 목ì ì¼ë¡ ìµìíì í¸ëí¹ì ì¬ì©í©ëë¤.", 561 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 562 terms: "ì½ê´", privacy: "ê°ì¸ì ë³´", security: "ë³´ì", 563 themeLight: "ë¼ì´í¸", themeDark: "ë¤í¬", themeSepia: "ì¸í¼ì", themeSolarized: "ìë¼ë¼ì´ì¦ë", themeOcean: "ì¤ì ", 564 viewEditor: "ì ì²´ ìëí°", viewSplit: "ë¶í ë·°", viewPreview: "ì ì²´ 미리보기", 565 modalCancel: "ì·¨ì", modalConfirm: "íì¸", newItemName: "ì´ë¦ ì ë ¥", 566 renameTitle: "ì´ë¦ ë°ê¾¸ê¸°", newFileTitle: "ì íì¼", newFolderTitle: "ì í´ë", 567 exporting: "ë´ë³´ë´ë ì¤...", exportSuccess: "ë´ë³´ë´ê¸° ì±ê³µ!", exportFail: "ë´ë³´ë´ê¸° ì¤í¨. ì½í ì¸ ê° ë무 í´ ì ììµëë¤.", 568 pdfStyle: "PDF ì¤íì¼", styleStandard: "íì¤ (GitHub)", styleAcademic: "íì (Elegant)", styleModern: "모ë (Clean)", styleDefault: "ìì¤í 기본ê°", 569 styleDefaultSimple: "기본", styleStandardSimple: "íì¤", styleAcademicSimple: "íì ", styleModernSimple: "모ë", 570 styleUpdated: "기본 PDF ì¤íì¼ ì ë°ì´í¸ë¨", 571 syncStart: "ë기í ìì ì¤...", syncSuccess: "모ë íì¼ì´ Google Driveì ë기íëììµëë¤!", syncFail: "ë기í ì¤í¨: ", syncReauth: "ì¬ì¸ì¦ì´ íìí©ëë¤. ãí´ë¼ì°ë ë기íã를 ë¤ì ííì¬ Google Driveì ì°ê²°íì¸ì.", 572 installMsg: "ë ëì íê²½ì ìí´ MD2PDF ì¤ì¹", installBtn: "ì§ê¸ ì¤ì¹", installSuccess: "ì±ì´ ì±ê³µì ì¼ë¡ ì¤ì¹ëììµëë¤!", 573 chromeInstallMsg: "MD2PDF를 ë°ì¤í¬í± ì±ì¼ë¡ ì¤ì¹", chromeInstallBtn: "ì± ì¤ì¹", 574 iosInstallMsg: "ê³µì ë²í¼ì ë른 í 'í íë©´ì ì¶ê°'를 ì ííì¸ì", 575 copy: "ë³µì¬", copyJson: "JSON ë³µì¬", copied: "í´ë¦½ë³´ëì ë³µì¬ë¨", 576 language: "ì¸ì´ ì í", theme: "í ë§ ì í", copyContent: "ì½í ì¸ ë³µì¬", copyEditor: "ìëí° ë³µì¬", copyPreview: "미리보기 ë³µì¬",
577 sidebarSubTitle: "ìëíë 문ì ìì ", 578 paste: "í´ë¦½ë³´ëìì ë¶ì¬ë£ê¸°", 579 recommended: "ì¶ì²", 580 pdfSettings: "PDF ì¤ì ", pdfSettingsTitle: "PDF ì¬ì©ì ì ì", pdfPageSetup: "íì´ì§ ì¤ì ", pdfCover: "íì§", 581 docTitle: "문ì ì 목", docSubtitle: "ë¶ì 목 / ì ì", pdfTOC: "목차", pdfHeaderFooter: "ë¨¸ë¦¬ë§ ë° ê¼¬ë¦¬ë§", 582 pdfHeader: "머리ë§", pdfFooter: "꼬리ë§", pdfMacros: "매í¬ë¡: {date}, {title}, {pagenum}, {totalpages}", modalSave: "ì ì¥ ë° ë«ê¸°", 583 pdfLeft: "ì¼ìª½", pdfCenter: "ê°ì´ë°", pdfRight: "ì¤ë¥¸ìª½", 584 pdfBranding: "ì 문ì ì¸ AI ìí°í©í¸ PDF ë³í ìì§", 585 exportSettings: "ë´ë³´ë´ê¸° ì¤ì ", 586 coverShowHeader: "íì§ì 머리ë§/ê¼¬ë¦¬ë§ íì", 587 disableBrowserHF: "ð¡ í: ë¸ë¼ì°ì 기본 머리ë§/ê¼¬ë¦¬ë§ ë¹íì±í", 588 printDialogTip: "ð¡ ì¸ì ëíìììì ë¸ë¼ì°ì ì 기본 머리ë§/꼬리ë§ì ë¹íì±ííì¸ì", 589 printDialogNote: "ì°¸ê³ : ì¸ì ëíìììì 'ë¨¸ë¦¬ë§ ë° ê¼¬ë¦¬ë§' ìµì ì 꺼주ì¸ì", 590 headerLogoLeft: "ë¨¸ë¦¬ë§ ë¡ê³ (ì¼ìª½)", 591 headerLogoRight: "ë¨¸ë¦¬ë§ ë¡ê³ (ì¤ë¥¸ìª½)", 592 watermarkText: "ìí°ë§í¬ í ì¤í¸", 593 uploadLogo: "ì ë¡ë", 594 logoUrl: "ë¡ê³ URL", 595 logoAndWatermark: "ë¡ê³ ë° ìí°ë§í¬", 596 clear: "ì§ì°ê¸°", 597 watermark: "ìí°ë§í¬", 598 feedbackBtnTitle: "í¼ëë°± ê³µì ", 599 feedbackModalTitle: "í¼ëë°± ê³µì ð¬", 600 feedbackModalDesc: "MD2PDF ê°ì ì ëìì 주ì¸ì â ê·íì ìê²¬ì´ ì¤ìí©ëë¤!", 601 feedbackRatingLabel: "ì¬ì© ê²½íì ì´ë ì ¨ëì?", 602 feedbackMsgLabel: "í¼ëë°±", 603 feedbackMsgPh: "ìê°ì ìì ë¡ê² ê³µì í´ ì£¼ì¸ì â 기ë¥, ë²ê·¸, ê°ì ì¬í ë±...", 604 feedbackEmailPh: "[email protected]", 605 feedbackSubmitBtn: "í¼ëë°± ë³´ë´ê¸°", 606 feedbackSending: "ì ì¡ ì¤...", 607 feedbackSuccessTitle: "ê°ì¬í©ëë¤! ð", 608 feedbackSuccessMsg: "í¼ëë°±ì´ ì ìëììµëë¤. ë´ì©ì 꼼꼼í ê²í íê³ MD2PDF ê°ì ì íì©íê² ìµëë¤.", 609 feedbackErrorMsg: "ì ì¡ ì¤í¨. ì°ê²°ì íì¸íê³ ë¤ì ìëí´ ì£¼ì¸ì.", 610 feedbackNewsletterTitle: "ë´ì¤ë í° êµ¬ë í기", 611 feedbackNewsletterDesc: "Markdown í, MD2PDF ì ë°ì´í¸ & ìì°ì± í¸ë¦ â ìì´ ì ì©, ì ë¹ë ë°ì¡.", 612 feedbackEmailRequired: "ì í¨í ì´ë©ì¼ 주ì를 ì ë ¥í´ ì£¼ì¸ì." 613 }, 614 'bg': { 615 seoTitle: "MD2PDF - ÐезплаÑен Markdown кÑм PDF конвеÑÑÐ¾Ñ | md2pdf.cc", 616 seoDescription: "Ðезабавно пÑевÑÑнеÑе AI ÑаÑовеÑе в пÑоÑеÑионални PDF Ñайлове. ÐоддÑÑжа KaTeX ÑоÑмÑли, Mermaid диагÑами и Obsidian ÑинÑакÑиÑ. 100% клиенÑÑка ÑаÑÑ, без Ð²Ñ Ð¾Ð´.", 617 h1Title: "ÐÑоÑеÑионален ChatGPT и DeepSeek конвеÑÑÐ¾Ñ ÐºÑм PDF", 618 mermaidTitle: "Mermaid диагÑами", 619 aiExportTitle: "ÐкÑпоÑÑиÑане на AI ÑаÑ", 620 newFile: "Ðов Ñайл", newFolder: "Ðова папка", rename: "ÐÑеименÑване", download: "ÐзÑеглÑне на MD", delete: "ÐзÑÑиване", 621 syncDrive: "ÐблаÑна ÑÐ¸Ð½Ñ ÑонизаÑиÑ", exportPdf: "ÐкÑпоÑÑиÑане в PDF", feedback: "ÐбÑаÑна вÑÑзка", editorPh: "# ÐапоÑнеÑе ваÑеÑо пÑÑÑване...", 622 confirmDelete: "ÐзÑÑиване на Ñози елеменÑ?", welcomeTitle: "ÐоиÑе оÑÑеÑи", welcomeFile: "ÐобÑе-доÑли.md", 623 welcomeContent: `# MD2PDF.cc\n## **ÐÑоÑеÑионален двигаÑел за еÑаÑа на ÐÐ**\n<span style="color: #7c3aed; font-weight: bold;">ÐовеÑиÑелноÑÑ Ð½Ð° пÑÑво мÑÑÑо ⢠100% клиенÑÑки базиÑан</span> ⢠PDF Ñ ÐºÐ°ÑеÑÑво за пÑбликÑване\n\n---\n\n## ⨠ÐлÑÑови Ñ Ð°ÑакÑеÑиÑÑики\n\n### 1. ÐаÑемаÑика и наÑка ($LaTeX$)\nÐеÑÑекÑно за DeepSeek, GPT-4o или Claude:\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. ÐиагÑами (Mermaid)\nÐезабавно изобÑазÑване на блок-ÑÑ ÐµÐ¼Ð¸:\n\`\`\`mermaid\ngraph LR\n A[AI Chat] --> B{MD2PDF}\n B -->|Parse| C[PDF Report]\n B -->|Style| D[Beautiful Layout]\n\`\`\`\n\n### 3. ÐÑоÑеÑионални ÑÑнкÑии\n- [x] **Mermaid.js** поддÑÑжка\n- [x] **GitHub Callouts**\n- [x] **ÐкадемиÑни бележки**\n- [ ] **ÐеÑÑонализиÑан CSS** (ÐÑаквайÑе ÑкоÑо)\n\n#### GitHub-Style Callouts\n> [!NOTE]\n>
623 Това е **ÐÐÐÐÐÐÐ** за неÑÑÑална, но важна инÑоÑмаÑиÑ.\n\n> [!TIP]\n> Това е **СЪÐÐТ**, койÑо помага на поÑÑебиÑелиÑе да пÑавÑÑ Ð½ÐµÑаÑа по-добÑе.\n\n> [!WARNING]\n> Това е **ÐÐ ÐÐУÐÐ ÐÐÐÐÐÐÐ** за поÑенÑиално опаÑни дейÑÑвиÑ.\n\n> [!DANGER]\n> Това е блок за **ÐÐÐСÐÐСТ** пÑи кÑиÑиÑни гÑеÑки или ÑиÑк Ð¾Ñ Ð·Ð°Ð³Ñба на данни.\n\n#### ÐкадемиÑни бележки\nMarkdown е моÑен[^1], но MD2PDF го пÑави елеганÑен[^2].\n\n[^1]: СÑандаÑÑниÑÑ Markdown поддÑÑжа оÑновно ÑекÑÑово ÑоÑмаÑиÑане.\n[^2]: MD2PDF Ð´Ð¾Ð±Ð°Ð²Ñ Ð¿ÑоÑеÑионален PDF Ð¸Ð·Ñ Ð¾Ð´ и ÑазÑиÑен ÑинÑакÑиÑ.\n\n---\n\n## ð ÐодеÑна поддÑÑжка за бележки (Obsidian & Notion)\nСÑвмеÑÑим Ñ Ð»ÑÐ±Ð¸Ð¼Ð¸Ñ Ð²Ð¸ ÑинÑакÑÐ¸Ñ Ð·Ð° бележки:\n\n- **ÐкÑенÑи**: ÐожеÑе леÑно да ==акÑенÑиÑаÑе вÑÑÑ Ñ Ð²Ð°Ð¶ÐµÐ½ ÑекÑÑ==.\n- **WikiLinks**: ÐоддÑÑжка за [[ÐÑÑÑеÑни вÑÑзки]].\n\n---\n\n## ð Ð¢ÐµÑ Ð½Ð¾Ð»Ð¾Ð³Ð¸Ñно ÑоÑмаÑиÑане\n\`\`\`javascript\n// ЧиÑÑи и пÑоÑеÑионални кодови блокове\nfunction helloWorld() {\n console.log("ÐдÑавей Ð¾Ñ MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ API за ÑазÑабоÑÑиÑи\nÐвÑомаÑизиÑайÑе ÑабоÑÐ½Ð¸Ñ Ñи пÑоÑеÑ:\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð ÐликнеÑе вÑÑÑ Ñ **"ÐкÑпоÑÑиÑане в PDF"**, за да видиÑе магиÑÑа.`, 624 aiPrompt: "ÐÐ ÐÑиÑÑенÑ", guideTitle: "ÐнÑегÑаÑÐ¸Ñ Ð½Ð° ÐРаÑиÑÑенÑ", guideSub: "Ðезабавен пÑоÑеÑионален PDF", howItWorks: "Ðак ÑабоÑи", 625 guideDesc: "MD2PDF позволÑва на ÐРагенÑиÑе да генеÑиÑÐ°Ñ Ð¿ÑоÑеÑионални оÑÑеÑи. ÐзползвайÑе ÑиÑÑÐµÐ¼Ð½Ð¸Ñ Ð¿ÑÐ¾Ð¼Ð¿Ñ Ð¿Ð¾-долÑ, Ñака Ñе ÐРда пÑедоÑÑÐ°Ð²Ñ Ð´Ð¸ÑекÑни URL команди.", 626 copyPrompt: "ÐопиÑане на ÑиÑÑÐµÐ¼Ð½Ð¸Ñ Ð¿ÑомпÑ", benefit1: "ÐÑоÑеÑионален изглед", benefit2: "ÐовеÑиÑелноÑÑ Ð¿ÑÑво", 627 legalTitle: "СигÑÑноÑÑ Ð¸ ÑÑловиÑ", dataSecurity: "СигÑÑноÑÑ Ð½Ð° данниÑе", termsOfService: "УÑÐ»Ð¾Ð²Ð¸Ñ Ð·Ð° ползване", privacyPolicy: "ÐолиÑика за повеÑиÑелноÑÑ", 628 dataSecurityDesc: "MD2PDF е клиенÑÑко пÑиложение. ÐаÑеÑо ÑÑдÑÑжание и Ñайлове Ñе ÑÑÑ ÑанÑÐ²Ð°Ñ Ñамо в локалноÑо Ñ ÑанилиÑе на ваÑÐ¸Ñ Ð±ÑаÑзÑÑ. Ðие нÑмаме ÑÑÑвÑÑи, коиÑо да ÑÑÑ ÑанÑÐ²Ð°Ñ Ð²Ð°ÑиÑе докÑменÑи.", 629 termsDesc: "ÐзползванеÑо на md2pdf.cc Ñе ÑÑежда Ð¾Ñ Quanta Flow Inc. (Wyoming). Ðе даваме гаÑанÑии за ÑÑÑ ÑанениеÑо на данни в бÑаÑзÑÑа.", 630 privacyDesc: "ÐовеÑиÑелноÑÑ Ð½Ð° пÑÑво мÑÑÑо: ÐокÑменÑиÑе Ñе обÑабоÑÐ²Ð°Ñ Ð»Ð¾ÐºÐ°Ð»Ð½Ð¾. Ðзползваме минимално пÑоÑледÑване за аналиÑиÑни Ñели.", 631 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 632 terms: "УÑловиÑ", privacy: "ÐовеÑиÑелноÑÑ", security: "СигÑÑноÑÑ", 633 themeLight: "СвеÑла", themeDark: "ТÑмна", themeSepia: "СепиÑ", themeSolarized: "Solarized", themeOcean: "Ðкеан", 634 viewEditor: "ÐÑлен ÑедакÑоÑ", viewSplit: "Разделен изглед", viewPreview: "ÐÑеглед", 635 modalCancel: "ÐÑказ", modalConfirm: "ÐоÑвÑÑждаване", newItemName: "ÐÑведеÑе име", 636 renameTitle: "ÐÑеименÑване", newFileTitle: "Ðов Ñайл", newFolderTitle: "Ðова папка", 637 exporting: "ÐкÑпоÑÑиÑане...", exportSuccess: "ÐкÑпоÑÑиÑанеÑо е ÑÑпеÑно!", exportFail: "ÐÑеÑка пÑи екÑпоÑÑиÑане. СÑдÑÑжаниеÑо може да е ÑвÑÑде голÑмо.", 638 pdfStyle: "СÑил на PDF", styleStandard: "СÑандаÑÑен (GitHub)", styleAcademic: "ÐкадемиÑен (Elegant)", styleModern: "ÐодеÑен (Clean)", styleDefault: "СиÑÑемно по подÑазбиÑане", 639 styleDefaultSimple: "Ðо подÑ.", styleStandardSimple: "СÑандаÑÑ", styleAcademicSimple: "ÐкадемиÑен", styleModernSimple: "ÐодеÑен", 640 styleUpdated: "СÑилÑÑ Ð¿Ð¾ подÑазбиÑане е акÑÑализиÑан", 641 syncStart: "СÑаÑÑиÑане на ÑÐ¸Ð½Ñ ÑонизаÑиÑ...", syncSuccess: "ÐÑиÑки Ñайлове Ñа ÑÐ¸Ð½Ñ ÑонизиÑани Ñ Google Drive!", syncFail: "Ð¡Ð¸Ð½Ñ ÑонизаÑиÑ
641Ñа Ñе пÑовали: ", syncReauth: "ÐÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ð° е повÑоÑна оÑоÑизаÑиÑ. ÐокоÑнеÑе оÑново \u201EÐ¡Ð¸Ð½Ñ ÑонизаÑиÑ\u201C, за да ÑвÑÑжеÑе Google Drive.", 642 installMsg: "ÐнÑÑалиÑайÑе MD2PDF за по-добÑо изживÑване", installBtn: "ÐнÑÑалиÑай Ñега", installSuccess: "ÐÑиложениеÑо е инÑÑалиÑано ÑÑпеÑно!", 643 chromeInstallMsg: "ÐнÑÑалиÑайÑе MD2PDF каÑо деÑкÑоп пÑиложение", chromeInstallBtn: "ÐнÑÑалиÑай пÑиложениеÑо", 644 iosInstallMsg: "ÐокоÑнеÑе бÑÑона за ÑподелÑне и Ñлед Ñова 'ÐÑм наÑÐ°Ð»Ð½Ð¸Ñ ÐµÐºÑан'", 645 copy: "ÐопиÑай", copyJson: "ÐопиÑай JSON", copied: "ÐопиÑано в клипбоÑда", 646 language: "Ðзик", theme: "Тема", copyContent: "ÐопиÑай ÑÑдÑÑжаниеÑо", copyEditor: "ÐопиÑай ÑедакÑоÑа", copyPreview: "ÐопиÑай пÑегледа", 647 sidebarSubTitle: "ÐвÑомаÑизиÑана докÑменÑаÑиÑ", 648 paste: "ÐоÑÑави Ð¾Ñ ÐºÐ»Ð¸Ð¿Ð±Ð¾Ñда", 649 recommended: "ÐÑепоÑÑÑиÑелно", 650 pdfSettings: "PDF наÑÑÑойки", pdfSettingsTitle: "ÐеÑÑонализаÑÐ¸Ñ Ð½Ð° PDF", pdfPageSetup: "ÐаÑÑÑойка на ÑÑÑаниÑаÑа", pdfCover: "Ðаглавна ÑÑÑаниÑа", 651 docTitle: "Ðаглавие на докÑменÑа", docSubtitle: "Ðодзаглавие / ÐвÑоÑ", pdfTOC: "СÑдÑÑжание", pdfHeaderFooter: "ÐоÑна и долна ÑаÑÑ", 652 pdfHeader: "ÐоÑен колонÑиÑÑл", pdfFooter: "Ðолен колонÑиÑÑл", pdfMacros: "ÐакÑоÑи: {date}, {title}, {pagenum}, {totalpages}", modalSave: "Ðапази и заÑвоÑи", 653 pdfLeft: "ÐÑво", pdfCenter: "ЦенÑÑÑ", pdfRight: "ÐÑÑно", 654 pdfBranding: "ÐÑоÑеÑионален Ð¼ÐµÑ Ð°Ð½Ð¸Ð·Ñм за ÐРаÑÑеÑакÑи кÑм PDF", 655 exportSettings: "ÐаÑÑÑойки за екÑпоÑÑиÑане", 656 coverShowHeader: "Ðокажи гоÑна/долна ÑаÑÑ Ð½Ð° коÑиÑаÑа", 657 disableBrowserHF: "ð¡ СÑвеÑ: ÐзклÑÑеÑе бÑаÑзÑÑÐ½Ð¸Ñ H/F", 658 printDialogTip: "ð¡ Рдиалога за пеÑаÑ, Ð¼Ð¾Ð»Ñ Ð¸Ð·ÐºÐ»ÑÑеÑе ÑÑандаÑÑниÑе Ð·Ð°Ð³Ð»Ð°Ð²Ð¸Ñ Ð½Ð° бÑаÑзÑÑа", 659 printDialogNote: "Ðабележка: ÐолÑ, изклÑÑеÑе 'Header and footers' в наÑÑÑойкиÑе за пеÑаÑ", 660 headerLogoLeft: "Ðого в гоÑнаÑа ÑаÑÑ (ÐÑво)", 661 headerLogoRight: "Ðого в гоÑнаÑа ÑаÑÑ (ÐÑÑно)", 662 watermarkText: "ТекÑÑ Ð·Ð° воден знак", 663 uploadLogo: "ÐаÑи", 664 logoUrl: "URL на лого", 665 logoAndWatermark: "Ðого и воден знак", 666 clear: "ÐзÑиÑÑи", 667 watermark: "Ðоден знак", 668 feedbackBtnTitle: "СподелеÑе ваÑеÑо мнение", 669 feedbackModalTitle: "СподелеÑе ваÑеÑо мнение ð¬", 670 feedbackModalDesc: "ÐомогнеÑе ни да подобÑим MD2PDF â ваÑеÑо мнение е важно!", 671 feedbackRatingLabel: "Ðак е ваÑеÑо изживÑване?", 672 feedbackMsgLabel: "ÐаÑеÑо мнение", 673 feedbackMsgPh: "СподелеÑе миÑлиÑе Ñи â ÑÑнкÑии, гÑеÑки, пÑедложениÑ...", 674 feedbackEmailPh: "[email protected]", 675 feedbackSubmitBtn: "ÐзпÑаÑи мнение", 676 feedbackSending: "ÐзпÑаÑане...", 677 feedbackSuccessTitle: "ÐлагодаÑÑ! ð", 678 feedbackSuccessMsg: "ÐаÑеÑо мнение е полÑÑено. Ще го пÑоÑеÑем внимаÑелно и Ñе го използваме за подобÑÑване на MD2PDF.", 679 feedbackErrorMsg: "ÐеÑÑпеÑно изпÑаÑане. ÐÑовеÑеÑе вÑÑзкаÑа и опиÑайÑе оÑново.", 680 feedbackNewsletterTitle: "ÐбониÑайÑе Ñе за бÑлеÑина", 681 feedbackNewsletterDesc: "СÑвеÑи за Markdown, акÑÑализаÑии на MD2PDF и ÑÑикове за пÑодÑкÑивноÑÑ â Ñамо на английÑки, ÑÑдко.", 682 feedbackEmailRequired: "ÐолÑ, вÑведеÑе валиден имейл адÑеÑ." 683 }, 684 'ar': { 685 seoTitle: "MD2PDF - Ù ØÙ٠٠جاÙÙ Ù Ù Markdown Ø¥ÙÙ PDF | md2pdf.cc", 686 seoDescription: "ØÙÙ Ù ØØ§Ø¯Ø«Ø§Øª Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·Ùاع٠إÙÙ Ù ÙÙØ§Øª PDF Ø§ØØªØ±Ø§ÙÙØ© ÙÙØ±Ø§Ù. ÙØ¯Ø¹Ù ØµÙØº KaTeXØ ÙØ±Ø³ÙÙ MermaidØ ÙØªÙسÙÙ Obsidian. ÙØ¹Ù Ù ÙÙÙØ§Ù عÙÙ Ø¬ÙØ§Ø²ÙØ Ø¨Ø¯Ù٠تسجÙ٠دخÙÙ.", 687 h1Title: "Ù ØÙÙ ChatGPT Ù DeepSeek Ø¥ÙÙ PDF Ø§ØØªØ±Ø§ÙÙ", 688 mermaidTitle: "دع٠رسÙÙ Mermaid", 689 aiExportTitle: "ØªØµØ¯ÙØ± Ù ØØ§Ø¯Ø«Ø§Øª Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·ÙاعÙ", 690 newFile: "Ù ÙÙ Ø¬Ø¯ÙØ¯", newFolder: "Ù Ø¬ÙØ¯ Ø¬Ø¯ÙØ¯", rename: "إعادة ØªØ³Ù ÙØ©", download: "تØÙ ÙÙ MD", delete: "ØØ°Ù", 691 syncDrive: "Ù Ø²Ø§Ù ÙØ© Ø³ØØ§Ø¨ÙØ© (اتجاÙÙÙ)", exportPdf: "ØªØµØ¯ÙØ± PDF", feedback: "تعÙÙÙØ§Øª", editorPh: "# ابدأ رØÙتÙ...", 692 confirmDelete: "ØØ°Ù ÙØ°Ø§ Ø§ÙØ¹ÙصرØ", welcomeTitle: "ØªÙØ§Ø±ÙرÙ", welcomeFile: "ترØÙب.md", 693 welcomeContent: `# MD2PDF.cc\n## **اÙÙ ØØ±Ù Ø§ÙØ§ØØªØ±Ø§ÙÙ ÙØ¹ØµØ± Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·ÙاعÙ**\n<span style="color: #7c3aed; font-weight: bold;">Ø§ÙØ®ØµÙØµÙØ© Ø£ÙÙØ§Ù ⢠100% Ù Ù Ø¬Ø§ÙØ¨ Ø§ÙØ¹Ù ÙÙ</span> ⢠PDF Ø¨Ø¬ÙØ¯Ø© ÙØ´Ø±\n\n---\n\n## ⨠اÙÙ ÙØ²Ø§Øª Ø§ÙØ±Ø¦ÙØ³ÙØ©\n\n### 1. Ø§ÙØ±ÙØ§Ø¶ÙØ§Øª ÙØ§ÙعÙÙÙ ($LaTeX$)\n٠ثاÙÙ Ù٠خرجات DeepSeek Ø£Ù GPT-4o Ø£Ù Claude:\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. Ø§ÙØ±Ø³ÙÙ Ø§ÙØ¨ÙاÙÙØ© (Mermaid)\nعرض اÙ٠خططات Ø§ÙØ§ÙØ³ÙØ§Ø¨ÙØ© عÙ٠اÙÙÙØ±:\n\`\`\`mermaid\ngraph LR\n A[AI Chat] --> B{MD2PDF}\n B -->|Parse| C[PDF Report]\n B -->|Style| D[Beautiful Layout]\n\`\`\`\n\n### 3. Ù ÙØ²Ø§Øª Ø§ØØªØ±Ø§ÙÙØ©\n- [x] دع٠**Mermaid.js**\n- [x] **ØªÙØ¨ÙÙØ§Øª GitHub**\n- [x] **ÙÙØ§Ù Ø´ Ø£ÙØ§Ø¯ÙÙ ÙØ©**\n- [ ] **CSS ٠خصص** (ÙØ±ÙباÙ)\n\n#### ØªÙØ¨ÙÙØ§Øª بÙÙ Ø· GitHub\n> [!NOTE]\n>
693 ÙØ°Ù **Ù ÙØ§ØØ¸Ø©** ÙÙ٠عÙÙ٠ات اÙÙ ØØ§Ùدة ÙÙÙ٠اÙÙ ÙÙ Ø©.\n\n> [!TIP]\n> ÙØ°Ù **ÙØµÙØØ©** Ù٠ساعدة اÙ٠ستخد٠Ù٠عÙ٠اÙÙÙØ§Ù Ø¨Ø§ÙØ£Ø´Ùاء بشÙÙ Ø£ÙØ¶Ù أ٠أسرع.\n\n> [!WARNING]\n> ÙØ°Ø§ **ØªØØ°Ùر** ÙÙØ¥Ø¬Ø±Ø§Ø¡Ø§Øª Ø§ÙØ®Ø·Ùرة Ø£Ù Ø§ÙØØ±Ø¬Ø© اÙÙ ØØªÙ ÙØ©.\n\n> [!DANGER]\n> ÙØ°Ù ÙØªÙØ© **خطر** ÙÙØ£Ø®Ø·Ø§Ø¡ Ø§ÙØØ±Ø¬Ø© أ٠٠خاطر ÙÙØ¯Ø§Ù Ø§ÙØ¨ÙØ§ÙØ§Øª.\n\n#### ÙÙØ§Ù Ø´ Ø£ÙØ§Ø¯ÙÙ ÙØ©\nMarkdown ÙÙÙ[^1]Ø ÙÙÙ MD2PDF ÙØ¬Ø¹Ù٠أÙÙÙØ§Ù[^2].\n\n[^1]: ÙØ¯Ø¹Ù Markdown اÙÙÙØ§Ø³Ù ØªÙØ³Ù٠اÙÙØµ Ø§ÙØ£Ø³Ø§Ø³Ù.\n[^2]: ÙØ¶ÙÙ MD2PDF Ø¥ÙØªØ§Ø¬ PDF Ø§ØØªØ±Ø§ÙÙ ÙØµÙغة Ù ØªÙØ¯Ù Ø©.\n\n---\n\n## ð Ø¯Ø¹Ù Ø§ÙÙ ÙØ§ØØ¸Ø§Øª Ø§ÙØØ¯ÙØ«Ø© (Obsidian & Notion)\nÙ ØªÙØ§Ù٠٠ع ØµÙØºØ© اÙÙ ÙØ§ØØ¸Ø§Øª اÙÙ ÙØ¶ÙØ© ÙØ¯ÙÙ:\n\n- **Ø§ÙØªØ¸ÙÙÙ**: ÙÙ ÙÙÙ ==تظÙÙ٠اÙÙØµ اÙÙ ÙÙ == بسÙÙÙØ©.\n- **WikiLinks**: دع٠ÙÙ Ø· [[Ø§ÙØ±Ùابط Ø§ÙØ¯Ø§Ø®ÙÙØ©]].\n\n---\n\n## ð ØªÙØ³Ù٠تÙÙ٠أصÙÙ\n\`\`\`javascript\n// ÙØªÙ Ø¨Ø±Ù Ø¬ÙØ© ÙØ¸ÙÙØ© ÙØ§ØØªØ±Ø§ÙÙØ©\nfunction helloWorld() {\n console.log("Ù Ø±ØØ¨Ø§Ù Ù Ù MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ API ÙÙÙ Ø·ÙØ±ÙÙ\nأت٠تة Ø³ÙØ± ع٠ÙÙ:\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð Ø§ÙÙØ± عÙÙ **"ØªØµØ¯ÙØ± PDF"** ÙÙ Ø´Ø§ÙØ¯Ø© Ø§ÙØ³ØØ±.`, 694 aiPrompt: "٠ساعد Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·ÙاعÙ", guideTitle: "ØªÙØ§Ù ٠٠ساعد Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·ÙاعÙ", guideSub: "PDF Ø§ØØªØ±Ø§ÙÙ ÙÙØ±Ù", howItWorks: "ÙÙÙ ÙØ¹Ù Ù", 695 guideDesc: "ÙÙ ÙÙÙ MD2PDF ÙÙÙØ§Ø¡ Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·ÙØ§Ø¹Ù Ù Ù Ø¥ÙØ´Ø§Ø¡ ØªÙØ§Ø±Ùر Ø§ØØªØ±Ø§ÙÙØ©. Ø§Ø³ØªØ®Ø¯Ù Ù ÙØ¬Ù اÙÙØ¸Ø§Ù Ø£Ø¯ÙØ§Ù ØØªÙ ÙÙÙØ± Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·ÙØ§Ø¹Ù Ø£ÙØ§Ù ر URL ٠باشرة ÙØ´Ø±ÙØ· Ø§ÙØ¹ÙÙØ§Ù ÙØ¥Ùشاء اÙÙ Ø³ØªÙØ¯Ø§Øª عÙ٠اÙÙÙØ±.", 696 copyPrompt: "ÙØ³Ø® Ù ÙØ¬Ù اÙÙØ¸Ø§Ù ", benefit1: "ØªÙØ³ÙÙ Ø§ØØªØ±Ø§ÙÙ", benefit2: "Ø§ÙØ®ØµÙØµÙØ© Ø£ÙÙØ§Ù", 697 legalTitle: "Ø§ÙØ£Ù Ø§Ù ÙØ§ÙØ´Ø±ÙØ·", dataSecurity: "Ø£Ù Ù Ø§ÙØ¨ÙØ§ÙØ§Øª", termsOfService: "Ø´Ø±ÙØ· Ø§ÙØ®Ø¯Ù Ø©", privacyPolicy: "Ø³ÙØ§Ø³Ø© Ø§ÙØ®ØµÙØµÙØ©", 698 dataSecurityDesc: "MD2PDF Ù٠تطبÙÙ Ù Ù Ø¬Ø§ÙØ¨ Ø§ÙØ¹Ù ÙÙ. ÙØªÙ تخزÙÙ Ù ØØªÙÙ Markdown ÙÙ ÙÙØ§ØªÙ ÙÙØ· ÙÙ Ø§ÙØªØ®Ø²Ù٠اÙÙ ØÙÙ ÙÙ ØªØµÙØÙ. ÙØ§ Ù٠تÙÙ Ø®ÙØ§Ø¯Ù ØªØ®Ø²Ù Ù Ø³ØªÙØ¯Ø§ØªÙ Ø§ÙØ®Ø§ØµØ©Ø ٠٠ا ÙØ¶Ù Ù Ø£ÙØµÙ ÙØ¯Ø± Ù Ù Ø§ÙØ®ØµÙØµÙØ© ÙØ§Ùأ٠اÙ.", 699 termsDesc: "ÙØ®Ø¶Ø¹ استخدا٠md2pdf.cc ÙØ´Ø±ÙØ© Quanta Flow Inc. (ÙØ§ÙÙÙ ÙØº). ÙØ§ ÙÙØ¯Ù Ø¶Ù Ø§ÙØ§Øª ÙØ§Ø³ØªÙ رار Ø§ÙØ¨ÙØ§ÙØ§Øª Ù٠تخزÙ٠اÙÙ ØªØµÙØ.", 700 privacyDesc: "Ø§ÙØ®ØµÙØµÙØ© Ø£ÙÙØ§Ù: ØªØªÙ Ù Ø¹Ø§ÙØ¬Ø© اÙÙ Ø³ØªÙØ¯Ø§Øª Ù ØÙÙÙØ§. ÙØ³ØªØ®Ø¯Ù ØªØªØ¨Ø¹Ø§Ù ØØ¯Ù Ø§ÙØ£Ø¯ÙÙ ÙÙØªØÙÙÙØ§Øª ÙØ§Ùأغراض Ø§ÙØ¥Ø¹ÙاÙÙØ© اÙÙ Ø³ØªÙØ¨ÙÙØ©.", 701 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 702 terms: "Ø§ÙØ´Ø±ÙØ·", privacy: "Ø§ÙØ®ØµÙØµÙØ©", security: "Ø§ÙØ£Ù اÙ", 703 themeLight: "ÙØ§ØªØ", themeDark: "داÙÙ", themeSepia: "Ø³ÙØ¨Ùا", themeSolarized: "سÙÙØ§Ø±Ø§Ùزد", themeOcean: "Ù ØÙØ·", 704 viewEditor: "Ù ØØ±Ø± ÙØ§Ù Ù", viewSplit: "عرض Ù ÙÙØ³Ù ", viewPreview: "٠عاÙÙØ© ÙØ§Ù ÙØ©", 705 modalCancel: "Ø¥ÙØºØ§Ø¡", modalConfirm: "تأÙÙØ¯", newItemName: "Ø£Ø¯Ø®Ù Ø§ÙØ§Ø³Ù ", 706 renameTitle: "إعادة ØªØ³Ù ÙØ©", newFileTitle: "Ù ÙÙ Ø¬Ø¯ÙØ¯", newFolderTitle: "Ù Ø¬ÙØ¯ Ø¬Ø¯ÙØ¯", 707 exporting: "Ø¬Ø§Ø±Ù Ø§ÙØªØµØ¯Ùر...", exportSuccess: "ØªÙ Ø§ÙØªØµØ¯Ùر Ø¨ÙØ¬Ø§Ø!", exportFail: "ÙØ´Ù Ø§ÙØªØµØ¯Ùر. ÙØ¯ ÙÙÙ٠اÙÙ ØØªÙÙ ÙØ¨Ùرا٠جداÙ.", 708 pdfStyle: "ÙÙ Ø· PDF", styleStandard: "ÙÙØ§Ø³Ù (GitHub)", styleAcademic: "Ø£ÙØ§Ø¯ÙÙ Ù (Elegant)", styleModern: "ØØ¯ÙØ« (Clean)", styleDefault: "Ø§ÙØªØ±Ø§Ø¶Ù اÙÙØ¸Ø§Ù ", 709 styleDefaultSimple: "Ø§ÙØªØ±Ø§Ø¶Ù", styleStandardSimple: "ÙÙØ§Ø³Ù", styleAcademicSimple: "Ø£ÙØ§Ø¯ÙÙ Ù", styleModernSimple: "عصرÙ", 710 styleUpdated: "ØªÙ ØªØØ¯ÙØ« ÙÙ Ø· PDF Ø§ÙØ§ÙتراضÙ", 711 syncStart: "بدء اÙÙ Ø²Ø§Ù ÙØ©...", syncSuccess: "ØªÙ Ù Ø²Ø§Ù ÙØ© Ø¬Ù ÙØ¹ اÙÙ ÙÙØ§Øª ٠ع Google Drive!", syncFail: "ÙØ´Ùت اÙÙ Ø²Ø§Ù ÙØ©: ", syncReauth: "Ù Ø·ÙÙØ¨ إعادة Ø§ÙØªÙÙÙØ¶. اضغط عÙÙ Â«Ù Ø²Ø§Ù ÙØ©Â» ٠رة أخر٠ÙÙØ§ØªØµØ§Ù ب٠Google Drive.", 712 installMsg: "ثبت MD2PDF ÙØªØ¬Ø±Ø¨Ø© Ø£ÙØ¶Ù", installBtn: "ثبت Ø§ÙØ¢Ù", installSuccess: "ØªÙ ØªØ«Ø¨ÙØª Ø§ÙØªØ·Ø¨ÙÙ Ø¨ÙØ¬Ø§Ø!", 713 chromeInstallMsg: "ثبت MD2PDF ÙØªØ·Ø¨ÙÙ Ø³Ø·Ø Ù ÙØªØ¨", chromeInstallBtn: "ØªØ«Ø¨ÙØª Ø§ÙØªØ·Ø¨ÙÙ", 714 iosInstallMsg: "اضغط عÙ٠زر اÙÙ Ø´Ø§Ø±ÙØ© ث٠'Ø¥Ø¶Ø§ÙØ© Ø¥ÙÙ Ø§ÙØ´Ø§Ø´Ø© Ø§ÙØ±Ø¦ÙØ³ÙØ©'", 715 copy: "ÙØ³Ø®", copyJson: "ÙØ³Ø® JSON", copied: "ت٠اÙÙØ³Ø® Ø¥ÙÙ Ø§ÙØØ§ÙØ¸Ø©", 716 language: "اÙÙØºØ©", theme: "Ø§ÙØ³Ù Ø©", copyContent: "ÙØ³Ø® اÙÙ ØØªÙÙ", copyEditor: "ÙØ³Ø® اÙÙ ØØ±Ø±", copyPreview: "ÙØ³Ø® اÙ٠عاÙÙØ©", 717 sidebarSubTitle: "Ø§ÙØªÙØ«ÙÙ Ø§ÙØ¢ÙÙ", 718 paste: "ÙØµÙ Ù Ù Ø§ÙØØ§ÙØ¸Ø©", 719 recommended: "Ù ÙØµÙ بÙ", 720 pdfSettings: "إعدادات PDF", pdfSettingsTitle: "ØªØ®ØµÙØµ PDF", pdfPageSetup: "إعداد Ø§ÙØµÙØØ©", pdfCover: "ØµÙØØ© Ø§ÙØºÙاÙ", 721 docTitle: "عÙÙØ§Ù اÙÙ Ø³ØªÙØ¯", docSubtitle: "Ø§ÙØ¹ÙÙØ§Ù اÙÙØ±Ø¹Ù / اÙ٠ؤÙÙ", pdfTOC: "جدÙ٠اÙÙ ØØªÙÙØ§Øª", pdfHeaderFooter: "Ø§ÙØ±Ø£Ø³ ÙØ§ÙتذÙÙÙ", 722 pdfHeader: "Ø§ÙØ±Ø£Ø³", pdfFooter: "Ø§ÙØªØ°ÙÙÙ", pdfMacros: "اÙÙ Ø§ÙØ±Ù: {date}, {title}, {pagenum}, {totalpages}", modalSave: "ØÙظ ÙØ¥ØºÙاÙ", 723 pdfLeft: "ÙØ³Ø§Ø±", pdfCenter: "ÙØ³Ø·", pdfRight: "ÙÙ ÙÙ", 724 pdfBranding: "Ù ØØ±Ù Ø§ØØªØ±Ø§ÙÙ ÙØªØÙÙ٠٠خرجات Ø§ÙØ°Ùاء Ø§ÙØ§ØµØ·Ùاع٠إÙÙ PDF", 725 exportSettings: "إعدادات Ø§ÙØªØµØ¯Ùر", 726 coverShowHeader: "Ø¥Ø¸ÙØ§Ø± Ø§ÙØ±Ø£Ø³/Ø§ÙØªØ°ÙÙ٠عÙÙ Ø§ÙØºÙاÙ", 727 disableBrowserHF: "ð¡ ÙØµÙØØ©: تعطÙ٠رأس/تذÙÙ٠اÙÙ ØªØµÙØ", 728 printDialogTip: "ð¡ ÙÙ ØÙار Ø§ÙØ·Ø¨Ø§Ø¹Ø©Ø ÙØ±Ø¬Ù تعطÙÙ Ø±Ø¤ÙØ³ ÙØªØ°ÙÙÙØ§Øª اÙÙ ØªØµÙØ Ø§ÙØ§ÙØªØ±Ø§Ø¶ÙØ©", 729 printDialogNote: "Ù ÙØ§ØØ¸Ø©: ÙØ±Ø¬Ù Ø¥ÙÙØ§Ù تشغÙÙ 'Ø§ÙØ±Ø¤Ùس ÙØ§ÙتذÙÙÙØ§Øª' ÙÙ ØÙار Ø§ÙØ·Ø¨Ø§Ø¹Ø©", 730 headerLogoLeft: "شعار Ø§ÙØ±Ø£Ø³ (ÙØ³Ø§Ø±)", 731 headerLogoRight: "شعار Ø§ÙØ±Ø£Ø³ (ÙÙ ÙÙ)", 732 watermarkText: "ÙØµ Ø§ÙØ¹Ùا٠ة اÙÙ Ø§Ø¦ÙØ©", 733 uploadLogo: "Ø±ÙØ¹", 734 logoUrl: "رابط Ø§ÙØ´Ø¹Ø§Ø±", 735 logoAndWatermark: "Ø§ÙØ¹Ùا٠ة اÙÙ Ø§Ø¦ÙØ© ÙØ§Ùشعار", 736 clear: "ØØ°Ù", 737 watermark: "Ø¹ÙØ§Ù Ø© Ù Ø§Ø¦ÙØ©", 738 feedbackBtnTitle: "Ø´Ø§Ø±Ù Ù ÙØ§ØØ¸Ø§ØªÙ", 739 feedbackModalTitle: "Ø´Ø§Ø±Ù Ù ÙØ§ØØ¸Ø§ØªÙ ð¬", 740 feedbackModalDesc: "Ø³Ø§Ø¹Ø¯ÙØ§ ÙÙ ØªØØ³ÙÙ MD2PDF â Ù ÙØ§ØØ¸Ø§ØªÙ Ù ÙÙ Ø©!", 741 feedbackRatingLabel: "ÙÙÙ ÙÙ ØªØ¬Ø±Ø¨ØªÙØ", 742 feedbackMsgLabel: "Ù ÙØ§ØØ¸Ø§ØªÙ", 743 feedbackMsgPh: "شار٠أÙÙØ§Ø±Ù â Ø§ÙÙ ÙØ²Ø§Øª ÙØ§Ùأخطاء ÙØ§ÙØªØØ³ÙÙØ§Øª...", 744 feedbackEmailPh: "[email protected]", 745 feedbackSubmitBtn: "إرسا٠اÙÙ ÙØ§ØØ¸Ø§Øª", 746 feedbackSending: "Ø¬Ø§Ø±Ù Ø§ÙØ¥Ø±Ø³Ø§Ù...", 747 feedbackSuccessTitle: "Ø´ÙØ±Ø§Ù ÙÙ! ð", 748 feedbackSuccessMsg: "ØªÙ Ø§Ø³ØªÙØ§Ù Ù ÙØ§ØØ¸Ø§ØªÙ. سÙÙØ±Ø£Ùا Ø¨Ø¹ÙØ§ÙØ© ÙÙØ³ØªØ®Ø¯Ù ÙØ§ ÙØªØØ³ÙÙ MD2PDF.", 749 feedbackErrorMsg: "ÙØ´Ù Ø§ÙØ¥Ø±Ø³Ø§Ù. تØÙ٠٠٠اتصاÙÙ ÙØØ§Ù٠٠رة أخرÙ.", 750 feedbackNewsletterTitle: "اشتر٠ÙÙ ÙØ´Ø±ØªÙا Ø§ÙØ¨Ø±ÙØ¯ÙØ©", 751 feedbackNewsletterDesc: "ÙØµØ§Ø¦Ø MarkdownØ ØªØØ¯Ùثات MD2PDF ÙØÙÙ Ø§ÙØ¥ÙØªØ§Ø¬ÙØ© â Ø¨Ø§ÙØ¥ÙØ¬ÙÙØ²ÙØ© ÙÙØ·Ø ÙØ¨Ø´ÙÙ ØºÙØ± Ù ØªÙØ±Ø±.", 752 feedbackEmailRequired: "ÙØ±Ø¬Ù إدخا٠عÙÙØ§Ù Ø¨Ø±ÙØ¯ Ø¥ÙÙØªØ±ÙÙ٠صØÙØ." 753 }, 754 'it': {
755 seoTitle: "MD2PDF - Convertitore Gratuito da Markdown a PDF | md2pdf.cc", 756 seoDescription: "Trasforma istantaneamente le chat AI in PDF professionali. Supporta formule KaTeX, diagrammi Mermaid e sintassi Obsidian. 100% lato client, nessun login.", 757 h1Title: "Convertitore Professionale da ChatGPT e DeepSeek a PDF", 758 mermaidTitle: "Rendering Diagrammi Mermaid", 759 aiExportTitle: "Esportazione Chat AI in PDF", 760 newFile: "Nuovo file", newFolder: "Nuova cartella", rename: "Rinomina", download: "Scarica MD", delete: "Elimina", 761 syncDrive: "Sincronizzazione Cloud (2-vie)", exportPdf: "Esporta PDF", feedback: "Feedback", editorPh: "# Inizia il tuo viaggio...", 762 confirmDelete: "Eliminare questo elemento?", welcomeTitle: "I miei rapporti", welcomeFile: "Benvenuto.md", 763 welcomeContent: `# MD2PDF.cc\n## **Il motore professionale per l'era dell'IA**\n<span style="color: #7c3aed; font-weight: bold;">Privacy prima di tutto ⢠100% lato client</span> ⢠PDF di qualità editoriale\n\n---\n\n## ⨠Caratteristiche principali\n\n### 1. Matematica e Scienza ($LaTeX$)\nPerfetto per output DeepSeek, GPT-4o o Claude:\n$$f(x) = \\int_{-\\infty}^\\infty \\hat f(\\xi)\\,e^{2\\pi i \\xi x}\\,d\\xi$$\n\n### 2. Diagrammi (Mermaid)\nRenderizza diagrammi di flusso istantaneamente:\n\`\`\`mermaid\ngraph LR\n A[AI Chat] --> B{MD2PDF}\n B -->|Parse| C[PDF Report]\n B -->|Stile| D[Layout bellissimo]\n\`\`\`\n\n### 3. Funzioni professionali\n- [x] Supporto **Mermaid.js**\n- [x] **GitHub Callouts**\n- [x] **Note a piè di pagina accademiche**\n- [ ] **CSS personalizzato** (Presto disponibile)\n\n#### Callout in stile GitHub\n> [!NOTE]\n> Questa è una **NOTA** per informazioni neutre ma importanti.\n\n> [!TIP]\n> Questo è un **SUGGERIMENTO** per aiutare gli utenti a fare le cose meglio o più velocemente.\n\n> [!WARNING]\n> Questo è un **AVVERTIMENTO** per azioni potenzialmente pericolose.\n\n> [!DANGER]\n> Questo è un blocco di **PERICOLO** per errori critici o rischi di perdita di dati.\n\n#### Note a piè di pagina accademiche\nMarkdown è potente[^1], maar MD2PDF lo rende elegante[^2].\n\n[^1]: Markdown standard supporta la formattazione di base del testo.\n[^2]: MD2PDF aggiunge un output PDF professionale e una sintassi avanzata.\n\n---\n\n## ð Supporto per note moderne (Obsidian & Notion)\nCompatibile con la tua sintassi preferita per prendere appunti:\n\n- **Evidenziazioni**: Puoi ==evidenziare il testo importante== facilmente.\n- **WikiLinks**: Supporto per lo stile [[Link interni]].\n\n---\n\n## ð Formattazione nativa tecnologica\n\`\`\`javascript\n// Blocchi di codice puliti e professionali\nfunction helloWorld() {\n console.log("Ciao da MD2PDF!");\n}\n\`\`\`\n\n---\n\n## ð ï¸ API per sviluppatori\nAutomatizza il tuo flusso di lavoro:\n\`https://md2pdf.cc/?content={URL_ENCODED_MARKDOWN}\`\n\nð Clicca su **"Esporta PDF"** per vedere la magia.`, 764 aiPrompt: "Assistente IA", guideTitle: "Integrazione Assistente IA", guideSub: "PDF professionale istantaneo", howItWorks: "Come funziona", 765 guideDesc: "MD2PDF consente agli agenti IA di generare report professionali. Usa il prompt di sistema qui sotto in modo che l'IA fornisca comandi URL diretti per la barra degli indirizzi per generare documenti istantaneamente.", 766 copyPrompt: "Copia Prompt di sistema", benefit1: "Layout Pro", benefit2: "Privacy First", 767 legalTitle: "Sicurezza e Termini", dataSecurity: "Sicurezza dei dati", termsOfService: "Termini di servizio", privacyPolicy: "Informativa sulla privacy", 768 dataSecurityDesc: "MD2PDF è un'applicazione lato client. I tuoi contenuti Markdown e i file sono memorizzati solo nel database locale del tuo browser. Non abbiamo server che memorizzano i tuoi documenti privati, garantendo la massima privacy e sicurezza.", 769 termsDesc: "L'uso di md2pdf.cc è regolato da Quanta Flow Inc. (Wyoming). Non forniamo garanzie per la persistenza dei dati nella memoria del browser.", 770 privacyDesc: "Privacy-first: i documenti vengono elaborati localmente. Utilizziamo un tracciamento minimo per scopi analitici e pubblicitari futuri.", 771 quantaBranding: " ©2026 <a href=\"https://quantaflow.cc\" target=\"_blank\" class=\"hover:text-primary transition-colors\">Quanta Flow Inc.</a>", 772 terms: "Termini", privacy: "Privacy", security: "Sicurezza",
773 themeLight: "Luce", themeDark: "Buio", themeSepia: "Seppia", themeSolarized: "Solarized", themeOcean: "Oceano", 774 viewEditor: "Editor completo", viewSplit: "Vista divisa", viewPreview: "Anteprima completa", 775 modalCancel: "Annulla", modalConfirm: "Conferma", newItemName: "Inserisci nome", 776 renameTitle: "Rinomina", newFileTitle: "Nuovo file", newFolderTitle: "Nuova cartella", 777 exporting: "Esportazione...", exportSuccess: "Esportazione completata!", exportFail: "Esportazione fallita. Il contenuto potrebbe essere troppo grande.", 778 pdfStyle: "Stile PDF", styleStandard: "Standard (GitHub)", styleAcademic: "Accademico (Elegant)", styleModern: "Moderno (Clean)", styleDefault: "Predefinito di sistema", 779 styleDefaultSimple: "Predef.", styleStandardSimple: "Standard", styleAcademicSimple: "Accademico", styleModernSimple: "Moderno", 780 styleUpdated: "Stile PDF predefinito aggiornato", 781 syncStart: "Avvio sincronizzazione...", syncSuccess: "Tutti i file sincronizzati su Google Drive!", syncFail: "Sincronizzazione fallita: ", syncReauth: "Autorizzazione richiesta. Tocca di nuovo «Sincronizza» per connettere Google Drive.", 782 installMsg: "Installa MD2PDF per un'esperienza migliore", installBtn: "Installa ora", installSuccess: "App installata con successo!", 783 chromeInstallMsg: "Installa MD2PDF come app desktop", chromeInstallBtn: "Installa app", 784 iosInstallMsg: "Tocca il pulsante di condivisione e poi 'Aggiungi alla schermata Home'", 785 copy: "Copia", copyJson: "Copia JSON", copied: "Copiato negli appunti", 786 language: "Lingua", theme: "Tema", copyContent: "Copia contenuto", copyEditor: "Copia editor", copyPreview: "Copia anteprima", 787 sidebarSubTitle: "Documentazione Automatizzata", 788 paste: "Incolla dagli appunti", 789 recommended: "Consigliato", 790 pdfSettings: "Impostazioni PDF", pdfSettingsTitle: "Personalizzazione PDF", pdfPageSetup: "Impostazione pagina", pdfCover: "Copertina", 791 docTitle: "Titolo del documento", docSubtitle: "Sottotitolo / Autore", pdfTOC: "Indice", pdfHeaderFooter: "Intestazione e Piè di pagina", 792 pdfHeader: "Intestazione", pdfFooter: "Piè di pagina", pdfMacros: "Macro: {date}, {title}, {pagenum}, {totalpages}", modalSave: "Salva e chiudi", 793 pdfLeft: "Sinistra", pdfCenter: "Centro", pdfRight: "Destra", 794 pdfBranding: "Il motore professionale di PDF per gli artefatti dell'IA", 795 exportSettings: "Impostazioni di esportazione", 796 coverShowHeader: "Mostra intestazione/piè di pagina sulla copertina", 797 disableBrowserHF: "ð¡ Suggerimento: Disabilita H/P del browser", 798 printDialogTip: "ð¡ Nella finestra di dialogo di stampa, disabilitare le intestazioni/piè di pagina predefiniti del browser", 799 printDialogNote: "Nota: Disattivare 'Intestazioni e piè di pagina' nella finestra di stampa", 800 headerLogoLeft: "Logo intestazione (Sinistra)", 801 headerLogoRight: "Logo intestazione (Destra)", 802 watermarkText: "Testo filigrana", 803 uploadLogo: "Carica", 804 logoUrl: "URL del logo", 805 logoAndWatermark: "Logo e filigrana", 806 clear: "Elimina", 807 watermark: "Marca d'acqua", 808 feedbackBtnTitle: "Condividi il tuo feedback", 809 feedbackModalTitle: "Condividi il tuo feedback ð¬", 810 feedbackModalDesc: "Aiutaci a migliorare MD2PDF â il tuo feedback è importante!", 811 feedbackRatingLabel: "Com'è la tua esperienza?", 812 feedbackMsgLabel: "Il tuo feedback", 813 feedbackMsgPh: "Condividi i tuoi pensieri â funzionalità , bug, miglioramenti...", 814 feedbackEmailPh: "[email protected]", 815 feedbackSubmitBtn: "Invia Feedback", 816 feedbackSending: "Invio in corso...", 817 feedbackSuccessTitle: "Grazie! ð", 818 feedbackSuccessMsg: "Il tuo feedback è stato ricevuto. Lo leggeremo attentamente e lo useremo per migliorare MD2PDF.", 819 feedbackErrorMsg: "Invio non riuscito. Controlla la connessione e riprova.", 820 feedbackNewsletterTitle: "Iscriviti alla nostra newsletter", 821 feedbackNewsletterDesc: "Consigli Markdown, aggiornamenti MD2PDF & trucchi di produttività â solo in inglese, bassa frequenza.", 822 feedbackEmailRequired: "Inserisci un indirizzo email valido." 823 } 824}; 825 826// Billing/export copy is shared by the editor and standalone billing pages. 827// Keep these keys in every supported locale so paid-export UI never falls back to English. 828const billingLocaleText = { 829 tw: { exportBrandingTitle:'ç§»é¤ PDF åçè³è¨', exportBrandingFree:'å è²»å¯åºæå å« MD2PDF åçé ã', exportBrandingActive:'å·²åç¨ç¡åçè³è¨å¯åºã', exportViewPlans:'æ¥çæ¹æ¡', account:'帳æ¶', exportOneTime:'䏿¬¡æ§ä»æ¬¾ï¼ææè£ç½®å¯ç¨', exportMonthly:'å¯é¨æåæ¶ï¼è¨é±æææéå¯ç¨', exportFullPricing:'æ¥ç宿´å¹æ ¼æ¹æ¡', licenseActivated:'MD2PDF æ¬ç已卿¤è£ç½®ä¸åç¨ã' }, 830 es: { exportBrandingTitle:'Eliminar marca del PDF', exportBrandingFree:'Las exportaciones gratuitas incluyen la página de marca de MD2PDF.', exportBrandingActive:'La exp
830ortación sin marca está activa.', exportViewPlans:'Ver planes', account:'Cuenta', exportOneTime:'Pago único, todos los dispositivos', exportMonthly:'Cancela cuando quieras; activo mientras estés suscrito', exportFullPricing:'Ver precios completos', licenseActivated:'El acceso de MD2PDF se activó en este dispositivo.' }, 831 de: { exportBrandingTitle:'PDF-Branding entfernen', exportBrandingFree:'Kostenlose Exporte enthalten die MD2PDF-Markenseite.', exportBrandingActive:'Brandingfreier Export ist aktiv.', exportViewPlans:'Tarife ansehen', account:'Konto', exportOneTime:'Einmalzahlung, alle Geräte', exportMonthly:'Jederzeit kündbar; aktiv während des Abos', exportFullPricing:'Alle Preise ansehen', licenseActivated:'Der MD2PDF-Zugriff wurde auf diesem Gerät aktiviert.' }, 832 ru: { exportBrandingTitle:'УдалиÑÑ Ð±Ñендинг PDF', exportBrandingFree:'ÐеÑплаÑнÑй ÑкÑпоÑÑ Ð²ÐºÐ»ÑÑÐ°ÐµÑ ÑÑÑаниÑÑ Ð±Ñендинга MD2PDF.', exportBrandingActive:'ÐкÑпоÑÑ Ð±ÐµÐ· бÑендинга акÑивен.', exportViewPlans:'ÐоÑмоÑÑеÑÑ ÑаÑиÑÑ', account:'ÐккаÑнÑ', exportOneTime:'Ðдин плаÑÑж, вÑе ÑÑÑÑойÑÑва', exportMonthly:'ÐÑмена в лÑбое вÑемÑ; доÑÑÑпен пÑи подпиÑке', exportFullPricing:'ÐÑе ÑенÑ', licenseActivated:'ÐоÑÑÑп MD2PDF акÑивиÑован на ÑÑом ÑÑÑÑойÑÑве.' }, 833 ja: { exportBrandingTitle:'PDFã®ãã©ã³ãæ å ±ãåé¤', exportBrandingFree:'ç¡æåºåã«ã¯MD2PDFã®ãã©ã³ããã¼ã¸ãå«ã¾ãã¾ãã', exportBrandingActive:'ãã©ã³ããªãåºåãæå¹ã§ãã', exportViewPlans:'æéãè¦ã', account:'ã¢ã«ã¦ã³ã', exportOneTime:'ä¸åæãããã¹ã¦ã®ç«¯æ«', exportMonthly:'ãã¤ã§ãè§£ç´å¯è½ãè³¼èªä¸ã¯æå¹', exportFullPricing:'æéãã©ã³ãè¦ã', licenseActivated:'ãã®ç«¯æ«ã§MD2PDFã®ã¢ã¯ã»ã¹ãæå¹ã«ãªãã¾ããã' }, 834 ko: { exportBrandingTitle:'PDF ë¸ëë ì ê±°', exportBrandingFree:'ë¬´ë£ ë´ë³´ë´ê¸°ìë MD2PDF ë¸ëë íì´ì§ê° í¬í¨ë©ëë¤.', exportBrandingActive:'ë¸ëë ìë ë´ë³´ë´ê¸°ê° íì±íëììµëë¤.', exportViewPlans:'ìê¸ì 보기', account:'ê³ì ', exportOneTime:'ì¼í ê²°ì , 모ë 기기', exportMonthly:'ì¸ì ë ì·¨ì ê°ë¥; 구ë ì¤ íì±', exportFullPricing:'ì ì²´ ìê¸ì 보기', licenseActivated:'ì´ ê¸°ê¸°ìì MD2PDF ì´ì© ê¶íì´ íì±íëììµëë¤.' }, 835 bg: { exportBrandingTitle:'ÐÑÐµÐ¼Ð°Ñ Ð²Ð°Ð½Ðµ на PDF бÑандинга', exportBrandingFree:'ÐезплаÑниÑÑ ÐµÐºÑпоÑÑ Ð²ÐºÐ»ÑÑва бÑандиÑана ÑÑÑаниÑа на MD2PDF.', exportBrandingActive:'ÐкÑпоÑÑÑÑ Ð±ÐµÐ· бÑандинг е акÑивен.', exportViewPlans:'Ðиж плановеÑе', account:'ÐкаÑнÑ', exportOneTime:'ÐднокÑаÑно плаÑане, вÑиÑки ÑÑÑÑойÑÑва', exportMonthly:'ÐÑказ по вÑÑко вÑеме; акÑивен пÑи абонаменÑ', exportFullPricing:'Ðиж вÑиÑки Ñени', licenseActivated:'ÐоÑÑÑпÑÑ Ð´Ð¾ MD2PDF е акÑивиÑан на Ñова ÑÑÑÑойÑÑво.' }, 836 ar: { exportBrandingTitle:'Ø¥Ø²Ø§ÙØ© Ø¹ÙØ§Ù Ø© PDF Ø§ÙØªØ¬Ø§Ø±ÙØ©', exportBrandingFree:'ÙØªØ¶Ù Ù Ø§ÙØªØµØ¯Ùر اÙ٠جاÙÙ ØµÙØØ© Ø¹ÙØ§Ù Ø© MD2PDF.', exportBrandingActive:'Ø§ÙØªØµØ¯Ùر دÙÙ Ø¹ÙØ§Ù Ø© Ù ÙØ¹ÙÙ.', exportViewPlans:'عرض Ø§ÙØ®Ø·Ø·', account:'Ø§ÙØØ³Ø§Ø¨', exportOneTime:'Ø¯ÙØ¹Ø© ÙØ§ØØ¯Ø©Ø Ø¬Ù ÙØ¹ Ø§ÙØ£Ø¬Ùزة', exportMonthly:'Ø¥ÙØºØ§Ø¡ Ù٠أ٠ÙÙØªØ ÙØ¹Ø§Ù Ø£Ø«ÙØ§Ø¡ Ø§ÙØ§Ø´ØªØ±Ø§Ù', exportFullPricing:'عرض Ø§ÙØ£Ø³Ø¹Ø§Ø± اÙÙØ§Ù ÙØ©', licenseActivated:'ØªÙ ØªÙØ¹Ù٠اÙÙØµÙ٠إÙÙ MD2PDF عÙÙ ÙØ°Ø§ Ø§ÙØ¬Ùاز.' }, 837 it: { exportBrandingTitle:'Rimuovi branding PDF', exportBrandingFree:'Le esportazioni gratuite includono la pagina brand MD2PDF.', exportBrandingActive:'Lâesportazione senza branding è attiva.', exportViewPlans:'Vedi piani', account:'Account', exportOneTime:'Pagamento unico, tutti i dispositivi', exportMonthly:'Annulla quando vuoi; attivo durante lâabbonamento', exportFullPricing:'Vedi prezzi completi', licenseActivated:'Lâaccesso a MD2PDF è stato attivato su questo dispositivo.' } 838}; 839Object.entries(billingLocaleText).forEach(([locale, strings]) => Object.assign(langMap[locale], strings)); 840 841const getLang = () => { 842 const params = new URLSearchParams(window.location.search); 843 const urlLang = params.get('lang'); 844 if (urlLang && langMap[urlLang]) { 845 localStorage.setItem('MD2PDF_LANG', urlLang); 846 return urlLang; 847 } 848 const saved = localStorage.getItem('MD2PDF_LANG'); 849 if (saved) return saved; 850 const l = navigator.language || navigator.userLanguage; 851 if (l.startsWith('zh-TW') || l.startsWith('zh-HK')) return 'tw'; 852 if (l.startsWith('zh')) return 'zh'; 853 if (l.startsWith('es')) return 'es'; 854 if (l.startsWith('de')) return 'de'; 855 if (l.startsWith('ru')) return 'ru'; 856 if (l.startsWith('ja')) return 'ja'; 857 if (l.startsWith('ko')) return 'ko'; 858 if (l.startsWith('bg')) return 'bg'; 859 if (l.startsWith('ar')) return 'ar'; 860 if (l.startsWith('it')) return 'it'; 861 return 'en'; 862}; 863 864/** 865 * Updates the canonical tag based on the current URL and language. 866 * Ensures that tracking parameters (UTM, etc.) are stripped. 867 */ 868const updateCanonicalTag = () => {
869 let canonical = document.querySelector('link[rel="canonical"]'); 870 if (!canonical) { 871 canonical = document.createElement('link'); 872 canonical.setAttribute('rel', 'canonical'); 873 document.head.appendChild(canonical); 874 } 875 876 try { 877 const url = new URL(window.location.href); 878 const lang = url.searchParams.get('lang'); 879 // Ensure pathname ends with a slash if it's a directory 880 let pathname = url.pathname; 881 if (!pathname.endsWith('/') && !pathname.split('/').pop().includes('.')) { 882 pathname += '/'; 883 } 884 const cleanUrl = url.origin + pathname + (lang ? `?lang=${lang}` : ''); 885 canonical.setAttribute('href', cleanUrl); 886 } catch (e) { 887 console.error('Failed to update canonical tag:', e); 888 } 889}; 890 891const translateUI = () => { 892 try { 893 const lang = getLang(); 894 const dict = langMap[lang] || langMap['en']; 895 896 updateCanonicalTag(); 897 898 // è®¾ç½®é¡µé¢æ¹å (RTL æ¯æ) 899 if (lang === 'ar') { 900 document.documentElement.setAttribute('dir', 'rtl'); 901 document.body.classList.add('rtl-mode'); 902 } else { 903 document.documentElement.setAttribute('dir', 'ltr'); 904 document.body.classList.remove('rtl-mode'); 905 } 906 907 document.querySelectorAll('[data-i18n]').forEach(el => { 908 const key = el.getAttribute('data-i18n'); 909 if (dict[key]) el.innerHTML = dict[key]; 910 }); 911 document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { 912 const key = el.getAttribute('data-i18n-placeholder'); 913 if (dict[key]) el.placeholder = dict[key]; 914 }); 915 document.querySelectorAll('[data-i18n-title]').forEach(el => { 916 const key = el.getAttribute('data-i18n-title'); 917 if (dict[key]) el.title = dict[key]; 918 }); 919 920 // Dynamic Meta Tag Update 921 // Only update title for main page (not landing pages with specific paths) 922 if (dict.seoTitle && window.location.pathname === '/') { 923 document.title = dict.seoTitle; 924 const ogTitle = document.querySelector('meta[property="og:title"]'); 925 if (ogTitle) ogTitle.setAttribute('content', dict.seoTitle); 926 } 927 if (dict.seoDescription) { 928 const metaDesc = document.querySelector('meta[name="description"]'); 929 if (metaDesc) metaDesc.setAttribute('content', dict.seoDescription); 930 const ogDesc = document.querySelector('meta[property="og:description"]'); 931 if (ogDesc) ogDesc.setAttribute('content', dict.seoDescription); 932 933 // Structured Data update 934 const schemaScript = document.querySelector('script[type="application/ld+json"]'); 935 if (schemaScript) { 936 try { 937 const schemaData = JSON.parse(schemaScript.innerHTML); 938 schemaData.description = dict.seoDescription; 939 schemaScript.innerHTML = JSON.stringify(schemaData, null, 2); 940 } catch (e) { 941 console.error("Schema.org update error:", e); 942 } 943 } 944 } 945 946 uiActions.updatePromptTemplate(); 947 } catch (err) { 948 console.error("UI Translation Error:", err); 949 } 950}; 951 952/** 953 * UI ACTIONS 954 */ 955const uiActions = { 956 toggleLangMenu(e, btn) { 957 e.stopPropagation(); 958 if (window.innerWidth <= 768) { 959 const currentLang = getLang(); 960 const dict = langMap[currentLang] || langMap['en']; 961 const items = Array.from(document.querySelectorAll('#langMenu .context-item')).map(item => { 962 const isSelected = item.getAttribute('onclick')?.includes(`'${currentLang}'`); 963 return `<div class="p-4 border-b border-border active:bg-accent flex items-center justify-between text-base font-medium ${isSelected ? 'text-primary' : ''}" onclick="uiActions.closeDrawer(); ${item.getAttribute('onclick')}"> 964 <div class="flex items-center gap-3">${item.innerHTML}</div> 965 ${isSelected ? '<i class="fas fa-check-circle"></i>' : ''} 966 </div>`; 967 }).join(''); 968 this.showDrawer(dict.language || 'Language', `<div>${items}</div>`, null, false); 969 return; 970 } 971 const menu = document.getElementById('langMenu'); 972 const isHidden = menu.classList.contains('hidden'); 973 document.querySelectorAll('.context-menu').forEach(m => { 974 m.classList.add('hidden'); 975 m.style.display = 'none'; 976 });
977 if (isHidden) { 978 menu.classList.remove('hidden'); 979 menu.style.display = 'flex'; 980 const rect = btn.getBoundingClientRect(); 981 menu.style.top = `${rect.bottom + 8}px`; 982 menu.style.left = `${rect.left}px`; 983 984 const close = () => { menu.classList.add('hidden'); menu.style.display = 'none'; document.removeEventListener('click', close); }; 985 setTimeout(() => document.addEventListener('click', close), 10); 986 } 987 }, 988 openModal(id) { 989 const m = document.getElementById(id); 990 if (m) { 991 m.classList.remove('hidden'); 992 m.style.setProperty('display', 'flex', 'important'); 993 } 994 }, 995 closeModal(id) { 996 const m = document.getElementById(id); 997 if (m) { 998 m.classList.add('hidden'); 999 m.style.setProperty('display', 'none', 'important'); 1000 } 1001 }, 1002 handleLogoUpload(input, targetId) { 1003 if (input.files && input.files[0]) { 1004 const reader = new FileReader(); 1005 reader.onload = (e) => { 1006 document.getElementById(targetId).value = e.target.result; 1007 this.updateLogoPreview(targetId); 1008 }; 1009 reader.readAsDataURL(input.files[0]); 1010 } 1011 }, 1012 updateLogoPreview(id) { 1013 const input = document.getElementById(id); 1014 if (!input) return; 1015 1016 const val = input.value; 1017 const container = document.getElementById(id + '-preview-container'); 1018 const previewImg = document.getElementById(id + '-preview'); 1019 1020 // è·å对åºçææ¬è¾å ¥æ¡ ID (default-logo-left -> default-header-left, export-logo-right -> export-header-right) 1021 let textFieldId = ''; 1022 if (id.includes('logo-left')) textFieldId = id.replace('logo-left', 'header-left'); 1023 else if (id.includes('logo-right')) textFieldId = id.replace('logo-right', 'header-right'); 1024 1025 const textField = textFieldId ? document.getElementById(textFieldId) : null; 1026 const hasLogo = val && val.trim().length > 0; 1027 1028 if (container && previewImg) { 1029 if (hasLogo && (val.startsWith('data:image') || val.startsWith('http'))) { 1030 previewImg.src = val; 1031 container.classList.remove('hidden'); 1032 if (val.startsWith('data:image')) { 1033 input.classList.add('hidden'); 1034 } else { 1035 input.classList.remove('hidden'); 1036 } 1037 } else { 1038 container.classList.add('hidden'); 1039 input.classList.remove('hidden'); 1040 } 1041 } 1042 1043 // æ 论æ¯å¦è½é¢è§ï¼åªè¦æå¼å¨Logoæ¡éï¼å°±ç¦ç¨ææ¬æ¡ 1044 if (textField) { 1045 if (hasLogo) { 1046 textField.disabled = true; 1047 textField.style.opacity = '0.5'; 1048 textField.style.cursor = 'not-allowed'; 1049 textField.title = 'Logo enabled - Header text for this position is disabled'; 1050 } else { 1051 textField.disabled = false; 1052 textField.style.opacity = '1'; 1053 textField.style.cursor = 'text'; 1054 textField.title = ''; 1055 } 1056 } 1057 }, 1058 clearLogo(id) { 1059 document.getElementById(id).value = ''; 1060 this.updateLogoPreview(id); 1061 }, 1062 openPdfSettings() { 1063 const defaults = JSON.parse(localStorage.getItem('MD2PDF_DEFAULT_HEADER_FOOTER') || '{}'); 1064 const ids = ['default-subtitle', 'default-header-left', 'default-header-center', 'default-header-right', 'default-footer-left', 'default-footer-center', 'default-footer-right', 'default-logo-left', 'default-logo-right', 'default-watermark']; 1065 ids.forEach(id => { 1066 const el = document.getElementById(id); 1067 if (!el) return; 1068 el.value = defaults[id] || ''; 1069 }); 1070 1071 ['default-logo-left', 'default-logo-right'].forEach(id => this.updateLogoPreview(id)); 1072 1073 // å è½½å°é¢é¡µç页è设置 1074 const coverShowHeaderEl = document.getElementById('default-cover-show-header'); 1075 if (coverShowHeaderEl) { 1076 coverShowHeaderEl.checked = defaults['default-cover-show-header'] || false; 1077 } 1078 1079 this.openModal('pdfSettingsModal'); 1080 }, 1081 saveDefaultHeaderFooter() { 1082 const defaults = {}; 1083 const ids = ['default-subtitle', 'default-header-left', 'default-header-center', 'default-header-right', 'default-footer-left', 'default-footer-center', 'default-footer-right', 'default-logo-left', 'default-logo-right', 'default-watermark']; 1084 ids.forEach(id => { 1085 const el = document.getElementById(id); 1086 if (!el) return; 1087 defaults[id] = el.value; 1088 }); 1089 1090 // ä¿åå°é¢é¡µç页è设置 1091 const coverShowHeaderEl = document.getElementById('default-cover-show-header'); 1092 if (coverShowHeaderEl) { 1093 defaults['default-cover-show-header'] = coverShowHeaderEl.checked; 1094 } 1095 1096 localStorage.setItem('MD2PDF_DEFAULT_HEADER_FOOTER', JSON.stringify(defaults)); 1097 this.closeModal('pdfSettingsModal'); 1098 const lang = getLang(); 1099 const dict = langMap[lang] || langMap['en']; 1100 this.showToast(dict.styleUpdated || "Default settings saved", "success"); 1101 }, 1102 openExportSettings() { 1103 // Get current file 1104 const file = db.nodes.find(n => n.id === db.activeId); 1105 if (!file) return; 1106
1107 // Load file's export settings or defaults 1108 const exportSettings = file.exportSettings || {}; 1109 const defaults = JSON.parse(localStorage.getItem('MD2PDF_DEFAULT_HEADER_FOOTER') || '{}'); 1110 1111 // Set style (radio buttons) 1112 const style = exportSettings.style || db.pdfStyle || 'default'; 1113 document.querySelectorAll('input[name="export-style"]').forEach(radio => { 1114 radio.checked = (radio.value === style); 1115 }); 1116 1117 // Set cover page 1118 const useCover = exportSettings['export-use-cover'] || false; 1119 const useCoverEl = document.getElementById('export-use-cover'); 1120 const coverOptionsEl = document.getElementById('export-cover-options'); 1121 const titleEl = document.getElementById('export-title'); 1122 const subtitleEl = document.getElementById('export-subtitle'); 1123 if (useCoverEl) useCoverEl.checked = useCover; 1124 if (coverOptionsEl) coverOptionsEl.classList.toggle('hidden', !useCover); 1125 if (titleEl) titleEl.value = exportSettings['export-title'] || file.name || ''; 1126 if (subtitleEl) subtitleEl.value = exportSettings['export-subtitle'] !== undefined ? exportSettings['export-subtitle'] : (defaults['default-subtitle'] || ''); 1127 // document.getElementById('export-cover-show-header').checked = exportSettings['export-cover-show-header'] !== undefined ? exportSettings['export-cover-show-header'] : (defaults['default-cover-show-header'] || false); 1128 1129 // Set TOC 1130 const useTocEl = document.getElementById('export-use-toc'); 1131 if (useTocEl) useTocEl.checked = exportSettings['export-use-toc'] || false; 1132 1133 // Set header/footer (use file settings if exist, otherwise use defaults) 1134 ['header-left', 'header-center', 'header-right', 'footer-left', 'footer-center', 'footer-right', 'logo-left', 'logo-right', 'watermark'].forEach(key => { 1135 const el = document.getElementById('export-' + key); 1136 if (el) { 1137 el.value = exportSettings['export-' + key] !== undefined ? exportSettings['export-' + key] : (defaults['default-' + key] || ''); 1138 } 1139 }); 1140 1141 ['export-logo-left', 'export-logo-right'].forEach(id => this.updateLogoPreview(id)); 1142 1143 this.openModal('exportSettingsModal'); 1144 }, 1145 saveExportSettings() { 1146 const file = db.nodes.find(n => n.id === db.activeId); 1147 if (!file) return; 1148 1149 // Get selected style 1150 const selectedStyle = document.querySelector('input[name="export-style"]:checked'); 1151 const style = selectedStyle ? selectedStyle.value : 'default'; 1152 1153 // Update global default style 1154 db.pdfStyle = style; 1155 1156 // Collect all export settings 1157 const exportSettings = { 1158 style: style, 1159 'export-use-cover': document.getElementById('export-use-cover').checked, 1160 'export-title': document.getElementById('export-title').value, 1161 'export-subtitle': document.getElementById('export-subtitle').value, 1162 // 'export-cover-show-header': document.getElementById('export-cover-show-header').checked, 1163 'export-use-toc': document.getElementById('export-use-toc').checked, 1164 'export-header-left': document.getElementById('export-header-left').value, 1165 'export-header-center': document.getElementById('export-header-center').value, 1166 'export-header-right': document.getElementById('export-header-right').value, 1167 'export-footer-left': document.getElementById('export-footer-left').value, 1168 'export-footer-center': document.getElementById('export-footer-center').value, 1169 'export-footer-right': document.getElementById('export-footer-right').value, 1170 'export-logo-left': document.getElementById('export-logo-left').value, 1171 'export-logo-right': document.getElementById('export-logo-right').value, 1172 'export-watermark': document.getElementById('export-watermark').value 1173 }; 1174 1175 // Save to file's exportSettings 1176 file.exportSettings = exportSettings; 1177 file.updatedAt = Date.now(); 1178 save(); 1179 1180 this.closeModal('exportSettingsModal'); 1181 }, 1182 savePdfSettings() { 1183 // This function is deprecated, keeping for backward compatibility 1184 this.saveDefaultHeaderFooter(); 1185 }, 1186 toggleThemeMenu(e, btn) { 1187 e.stopPropagation(); 1188 if (window.innerWidth <= 768) { 1189 const currentTheme = document.body.getAttribute('data-theme') || 'light'; 1190 const lang = getLang(); 1191 const dict = langMap[lang] || langMap['en']; 1192 const items = Array.from(document.querySelectorAll('#themeMenu .context-item')).map(item => { 1193 const isSelected = item.getAttribute('onclick')?.includes(`'${currentTheme}'`); 1194 return `<div class="p-4 border-b border-border active:bg-accent flex items-center justify-between text-base font-medium ${isSelected ? 'text-primary' : ''}" onclick="uiActions.closeDrawer(); ${item.getAttribute('onclick')}"> 1195 <div class="flex items-center gap-3">${item.innerHTML}</div> 1196 ${isSelected ? '<i class="fas fa-check-circle"></i>' : ''} 1197 </div>`; 1198 }).join(''); 1199 this.showDrawer(dict.theme || 'Theme / 主é¢', `<div>${items}</div>`, null, false); 1200 return; 1201 } 1202 const menu = document.getElementById('themeMenu'); 1203 const isHidden = menu.classList.contains('hidden'); 1204 document.querySelectorAll('.context-menu').forEach(m => { 1205 m.classList.add('hidden'); 1206 m.style.display = 'none'; 1207 });
1208 if (isHidden) { 1209 menu.classList.remove('hidden'); 1210 menu.style.display = 'flex'; 1211 const rect = btn.getBoundingClientRect(); 1212 menu.style.top = `${rect.bottom + 8}px`; 1213 menu.style.left = `${rect.left}px`; 1214 1215 const close = () => { menu.classList.add('hidden'); menu.style.display = 'none'; document.removeEventListener('click', close); }; 1216 setTimeout(() => document.addEventListener('click', close), 10); 1217 } 1218 }, 1219 togglePdfStyleMenu(e, btn) { 1220 // This function is deprecated - kept for backward compatibility 1221 // Now we use per-file export settings instead 1222 e.stopPropagation(); 1223 this.openExportSettings(); 1224 }, 1225 toggleCopyMenu(e, btn) { 1226 e.stopPropagation(); 1227 if (window.innerWidth <= 768) { 1228 const lang = getLang(); 1229 const dict = langMap[lang] || langMap['en']; 1230 const items = [ 1231 { label: dict.copyEditor || 'Copy Editor', icon: 'fa-file-alt', action: 'uiActions.copyEditor()' }, 1232 { label: dict.copyPreview || 'Copy Preview', icon: 'fa-eye', action: 'uiActions.copyPreview()' }, 1233 { label: dict.paste || 'Paste from Clipboard', icon: 'fa-paste', action: 'uiActions.pasteFromClipboard()' } 1234 ].map(item => 1235 `<div class="p-4 border-b border-border active:bg-accent flex items-center gap-3 text-base font-medium" onclick="event.stopPropagation(); ${item.action}; uiActions.closeDrawer();"> 1236 <i class="fas ${item.icon} text-primary"></i> ${item.label} 1237 </div>` 1238 ).join(''); 1239 this.showDrawer(dict.copyContent || 'Copy Content', `<div>${items}</div>`, null, false); 1240 return; 1241 } 1242 const menu = document.getElementById('copyMenu'); 1243 const isHidden = menu.classList.contains('hidden'); 1244 document.querySelectorAll('.context-menu').forEach(m => { 1245 m.classList.add('hidden'); 1246 m.style.display = 'none'; 1247 }); 1248 if (isHidden) { 1249 menu.classList.remove('hidden'); 1250 menu.style.display = 'flex'; 1251 const rect = btn.getBoundingClientRect(); 1252 menu.style.top = `${rect.bottom + 8}px`; 1253 menu.style.left = `${rect.left}px`; // Align to left of button 1254 1255 const close = () => { menu.classList.add('hidden'); menu.style.display = 'none'; document.removeEventListener('click', close); }; 1256 setTimeout(() => document.addEventListener('click', close), 10); 1257 } 1258 }, 1259 setLang(lang) { 1260 try { 1261 const oldLang = getLang(); 1262 1263 // å¦æç®æ è¯è¨åå½åè¯è¨ä¸è´ï¼ç´æ¥è¿å 1264 if (oldLang === lang) { 1265 const menu = document.getElementById('langMenu'); 1266 if (menu) { 1267 menu.classList.add('hidden'); 1268 menu.style.display = 'none'; 1269 } 1270 return; 1271 } 1272 1273 // 卿´æ° localStorage ä¹åï¼å è·åæ§çåå ¸ 1274 const oldDict = langMap[oldLang] || langMap['en']; 1275 const newDict = langMap[lang] || langMap['en']; 1276 1277 // æ´æ°è¯è¨è®¾ç½® 1278 localStorage.setItem('MD2PDF_LANG', lang); 1279 translateUI(); 1280 1281 // åæ¥æ´æ°é»è®¤çæçæ¬¢è¿ææ¡£å 容 1282 db.nodes.forEach(node => { 1283 // æ£æ¥æ¯å¦æ¯ä»»ä½ä¸ç§è¯è¨å®ä¹çæ¬¢è¿æä»¶ 1284 let isWelcomeFile = false; 1285 let isWelcomeFolder = false; 1286 for (const l in langMap) { 1287 if (node.name === langMap[l].welcomeFile) isWelcomeFile = true; 1288 if (node.name === langMap[l].welcomeTitle) isWelcomeFolder = true; 1289 } 1290 1291 if (isWelcomeFolder) { 1292 node.name = newDict.welcomeTitle; 1293 } else if (isWelcomeFile) { 1294 node.name = newDict.welcomeFile; 1295 // ç±äºç¨æ·å¯è½å¾®è°è¿å å®¹ï¼æä»¬éç¨â模ç³å¹é âæç´æ¥è¦çï¼å ä¸ºè¿æ¯æ¬¢è¿é¡µï¼ 1296 // è¿éæä»¬ä¿æé»è¾ï¼å¦æå 容没æä¹åï¼å°±æ´æ°å® 1297 node.content = newDict.welcomeContent; 1298 } 1299 }); 1300 1301 save(); // ç¡®ä¿æ´æ¹è¢«ä¿å 1302 renderTree(); 1303 1304 // 妿å½åæå¼çæä»¶æ£æ¯æ¬¢è¿ææ¡£ï¼åå·æ°ç¼è¾å¨å 容 1305 if (db.activeId) { 1306 const activeNode = db.nodes.find(n => n.id === db.activeId); 1307 if (activeNode) { 1308 document.getElementById('editor').value = activeNode.content || ''; 1309 document.getElementById('filenameDisplay').value = activeNode.name; 1310 } 1311 } 1312 1313 updatePreview(); 1314
1315 // æ´æ°é¢å å±ï¼å¦æå½åéä¸çæ¯é»è®¤æä»¶ï¼åç§°å¯è½åäºï¼ 1316 if (db.activeId) updateBreadcrumb(db.activeId); 1317 1318 const menu = document.getElementById('langMenu'); 1319 if (menu) { 1320 menu.classList.add('hidden'); 1321 menu.style.display = 'none'; 1322 } 1323 } catch (err) { 1324 console.error("SetLang Error:", err); 1325 } 1326 }, 1327 setTheme(theme) { 1328 // GA4 Tracking 1329 trackEvent('md_theme_change', { theme_name: theme }); 1330 1331 document.body.setAttribute('data-theme', theme); 1332 localStorage.setItem('MD2PDF_THEME', theme); 1333 const menu = document.getElementById('themeMenu'); 1334 menu.classList.add('hidden'); 1335 menu.style.display = 'none'; 1336 }, 1337 setPdfStyle(style) { 1338 // This function is deprecated - kept for backward compatibility 1339 // Now we use per-file export settings instead 1340 const file = db.nodes.find(n => n.id === db.activeId); 1341 if (!file) return; 1342 1343 if (!file.exportSettings) file.exportSettings = {}; 1344 file.exportSettings.style = style; 1345 file.updatedAt = Date.now(); 1346 save(); 1347 1348 const lang = getLang(); 1349 const dict = langMap[lang] || langMap['en']; 1350 uiActions.showToast(dict.styleUpdated || "Style Updated", "success"); 1351 }, 1352 updatePdfStyleUI() { 1353 // This function is deprecated - kept for backward compatibility 1354 // UI updates are now handled in openExportSettings 1355 },
1356 setView(mode) { 1357 const ws = document.getElementById('workspace'); 1358 ws.className = `workspace-container relative mode-${mode}`; 1359 1360 // Update UI state 1361 ['editor', 'split', 'preview'].forEach(m => { 1362 const btn = document.getElementById(`view-${m}`); 1363 1364 if (btn) { 1365 btn.classList.remove('bg-background', 'shadow-sm'); 1366 if (m === mode) btn.classList.add('bg-background', 'shadow-sm'); 1367 } 1368 }); 1369 1370 // Update Mobile Single Toggle Icon 1371 const mobileToggleIconHeader = document.getElementById('mobile-toggle-icon-header'); 1372 if (mobileToggleIconHeader) { 1373 mobileToggleIconHeader.className = mode === 'editor' ? 'fas fa-eye text-xs' : 'fas fa-pen text-xs'; 1374 } 1375 1376 if (mode !== 'editor') updatePreview(); 1377 }, 1378 toggleMobileView() { 1379 const ws = document.getElementById('workspace'); 1380 const isEditor = ws.classList.contains('mode-editor'); 1381 this.setView(isEditor ? 'preview' : 'editor'); 1382 }, 1383 toggleSidebar() { 1384 const sidebar = document.getElementById('sidebar'); 1385 const overlay = document.getElementById('sidebar-overlay'); 1386 if (window.innerWidth <= 768) { 1387 const isOpen = sidebar.classList.toggle('open'); 1388 if (overlay) { 1389 if (isOpen) overlay.classList.remove('hidden'); 1390 else overlay.classList.add('hidden'); 1391 } 1392 } else { 1393 sidebar.classList.toggle('closed'); 1394 } 1395 this.updateLogoVisibility(); 1396 setTimeout(() => window.dispatchEvent(new Event('resize')), 300); 1397 }, 1398 updateLogoVisibility() { 1399 const sidebar = document.getElementById('sidebar'); 1400 const logoCollapsed = document.getElementById('logo-collapsed'); 1401 if (!logoCollapsed) return; 1402 1403 if (window.innerWidth <= 768) { 1404 logoCollapsed.classList.remove('hidden'); 1405 logoCollapsed.classList.add('flex'); 1406 } else { 1407 if (sidebar.classList.contains('closed')) { 1408 logoCollapsed.classList.remove('hidden'); 1409 logoCollapsed.classList.add('flex'); 1410 } else { 1411 logoCollapsed.classList.add('hidden'); 1412 logoCollapsed.classList.remove('flex'); 1413 } 1414 } 1415 }, 1416 toggleGuide() { 1417 const m = document.getElementById('guideModal'); 1418 const isHidden = m.classList.contains('hidden'); 1419 m.classList.toggle('hidden'); 1420 m.style.display = isHidden ? 'flex' : 'none'; 1421 if (isHidden) { 1422 this.switchGuideTab('link'); 1423 this.updatePromptTemplate(); 1424 } 1425 }, 1426 switchGuideTab(tab) { 1427 const linkBtn = document.getElementById('tabLink'); 1428 const actionBtn = document.getElementById('tabAction'); 1429 const linkTab = document.getElementById('guideTabLink'); 1430 const actionTab = document.getElementById('guideTabAction'); 1431 1432 if (tab === 'link') { 1433 linkBtn.className = "px-3 py-1.5 text-[11px] font-bold border-b-2 border-primary text-foreground"; 1434 actionBtn.className = "px-3 py-1.5 text-[11px] font-medium text-muted-foreground hover:text-foreground"; 1435 linkTab.classList.remove('hidden'); 1436 actionTab.classList.add('hidden'); 1437 } else { 1438 actionBtn.className = "px-3 py-1.5 text-[11px] font-bold border-b-2 border-primary text-foreground"; 1439 linkBtn.className = "px-3 py-1.5 text-[11px] font-medium text-muted-foreground hover:text-foreground"; 1440 actionTab.classList.remove('hidden'); 1441 linkTab.classList.add('hidden'); 1442 } 1443 }, 1444 updatePromptTemplate() { 1445 const lang = getLang(); 1446 const isZH = lang === 'zh' || lang === 'tw'; 1447 1448 const promptText = isZH ? 1449 `ä½ ç°å¨æ¯ä¸åä¸ä¸çææ¡£ç¼åä¸å®¶ã请å¡å¿ ä¿æåç使ç¨çè¯è¨ï¼å¦ææ¯ä¸æè¯·ä¿æä¸æï¼ã 1450çææ¥ååï¼è¯·æ ¹æ®å 容é¿åº¦éæ©åéç URL æ ¼å¼ï¼æ¾å¨ä»£ç åä¸æç¤ºæå¤å¶æå¼ï¼ 1451 1452è¦æ±ï¼ 14531. 龿¥æ ¼å¼éæ©ï¼ 1454- **æ¹æ¡ A (æ¨è)**ï¼è¥ Markdown é¿åº¦è¾çï¼çº¦ 1500 å符å ï¼ï¼è¯·ä½¿ç¨ Base64 ç¼ç ï¼ 1455 https://md2pdf.cc/?title=[æ é¢]&b64=[Base64å 容]&mode=export 1456 *(注æï¼Base64 éå å°å符串转为 UTF-8 åèæµåè¿è¡ç¼ç )* 1457
1458- **æ¹æ¡ B (é¿ææ¬)**ï¼è¥å 容è¾é¿ï¼è¯·ä½¿ç¨ä¸¤æ¬¡ URL ç¼ç ï¼ 1459 https://md2pdf.cc/?title=[æ é¢]&content=[äºæ¬¡ç¼ç å 容]&mode=export 1460 *(注æï¼å¿ é¡»å¯¹å ¨æè¿è¡ **两次** 宿´ç URL ç¼ç ï¼ä»¥ä¿æ¤ #, &, ? ç符å·)* 1461 14622. å ±åè¦æ±ï¼ 1463- [æ é¢] ç»ä¸ä½¿ç¨æ å URL ç¼ç ã 1464- 龿¥å¿ é¡»å å« '&mode=export'ã 1465- è¥å å®¹è¶ è¿ 4000 å符ï¼è¯·åç¥é¾æ¥å¯è½å¤±æï¼å»ºè®®æå¨å¤å¶å å®¹å° md2pdf.ccã` : 1466 `You are a professional documentation expert. You MUST maintain the language used in the response. 1467After generating the report, provide a plain text link in a code block based on content length: 1468 1469Requirements: 14701. URL Format Selection: 1471- **Option A (Highly Recommended)**: If content is short (<1500 chars), use Base64: 1472 https://md2pdf.cc/?title=[Title]&b64=[Base64Content]&mode=export 1473 *(Note: Convert string to UTF-8 bytes before Base64 encoding)* 1474 1475- **Option B (Long Content)**: If content is long, use Double URL Encoding: 1476 https://md2pdf.cc/?title=[Title]&content=[DoubleEncodedContent]&mode=export 1477 *(Note: Perform **Double URL Encoding** on the whole body to protect #, &, and ?)* 1478 14792. General Rules: 1480- [Title] MUST be Standard URL encoded. 1481- Link MUST include '&mode=export'. 1482- If content exceeds 4000 chars, warn the user and suggest manual copy-paste.`; 1483 1484 const openApiJson = { 1485 "openapi": "3.1.0", 1486 "info": { "title": "MD2PDF Export", "version": "1.0.0" }, 1487 "servers": [{ "url": "https://md2pdf.cc" }], 1488 "paths": { 1489 "/": { 1490 "get": { 1491 "operationId": "exportPdf", 1492 "summary": "Convert text to professional PDF", 1493 "parameters": [ 1494 { "name": "title", "in": "query", "required": true, "schema": { "type": "string" } }, 1495 { "name": "content", "in": "query", "required": true, "schema": { "type": "string" } }, 1496 { "name": "mode", "in": "query", "schema": { "type": "string", "default": "export" } } 1497 ] 1498 } 1499 } 1500 } 1501 }; 1502 1503 const pt = document.getElementById('promptTemplate'); 1504 const at = document.getElementById('actionTemplate'); 1505 if (pt) pt.innerText = promptText; 1506 if (at) at.innerText = JSON.stringify(openApiJson, null, 2); 1507 }, 1508 copyPrompt(type) { 1509 const id = type === 'action' ? 'actionTemplate' : 'promptTemplate'; 1510 const el = document.getElementById(id); 1511 if (!el) return; 1512 const text = el.innerText; 1513 navigator.clipboard.writeText(text).then(() => { 1514 const lang = getLang(); 1515 const dict = langMap[lang] || langMap['en']; 1516 this.showToast(dict.copied || "Copied to clipboard", "success"); 1517 }); 1518 }, 1519 showPrompt(title, defaultValue, callback) { 1520 if (window.innerWidth <= 768) { 1521 this.showDrawer(title, `<input id="drawerInput" type="text" class="w-full h-12 px-4 rounded-xl border border-input bg-muted/30 text-base outline-none focus:ring-2 focus:ring-primary/50" value="${defaultValue}">`, (db) => { 1522 const val = db.querySelector('#drawerInput').value.trim(); 1523 callback(val); 1524 }); 1525 setTimeout(() => document.getElementById('drawerInput').focus(), 300); 1526 return; 1527 } 1528 const input = document.getElementById('promptInput'); 1529 const confirmBtn = document.getElementById('promptConfirmBtn'); 1530 1531 document.getElementById('promptTitle').innerText = title; 1532 input.value = defaultValue; 1533 this.openModal('promptModal'); 1534 input.focus(); 1535 input.select(); 1536 1537 const onConfirm = () => { 1538 const val = input.value.trim(); 1539 this.closeModal('promptModal'); 1540 callback(val); 1541 }; 1542 1543 confirmBtn.onclick = onConfirm; 1544 input.onkeydown = (e) => { 1545 if (e.key === 'Enter') onConfirm(); 1546 if (e.key === 'Escape') this.closeModal('promptModal'); 1547 }; 1548 }, 1549 showConfirm(title, message, callback) { 1550 if (window.innerWidth <= 768) { 1551 this.showDrawer(title, `<p class="text-sm text-muted-foreground">${message}</p>`, () => { 1552 callback(); 1553 }); 1554 return; 1555 } 1556 const confirmBtn = document.getElementById('confirmConfirmBtn'); 1557 document.getElementById('confirmTitle').innerText = title; 1558 document.getElementById('confirmMessage').innerText = message; 1559 1560 this.openModal('confirmModal'); 1561 1562 confirmBtn.onclick = () => { 1563 this.closeModal('confirmModal'); 1564 callback(); 1565 }; 1566 }, 1567 showDrawer(title, content, onConfirm, showFooter = true) { 1568 const d = document.getElementById('drawer'); 1569 if (this._drawerTimeout) { 1570 clearTimeout(this._drawerTimeout); 1571 this._drawerTimeout = null; 1572 } 1573 1574 const dt = document.getElementById('drawerTitle'); 1575 const db = document.getElementById('drawerBody'); 1576 const df = d.querySelector('.drawer-footer'); 1577 const dcb = document.getElementById('drawerConfirmBtn'); 1578 1579 dt.innerText = title; 1580 db.innerHTML = content; 1581 df.style.display = showFooter ? 'flex' : 'none'; 1582 d.classList.remove('hidden'); 1583 d.style.display = 'flex'; 1584 d.classList.remove('closing'); 1585 1586 // Translate dynamic content 1587 translateUI(); 1588 1589 dcb.onclick = () => { 1590 const result = onConfirm(db); 1591 if (result !== false) this.closeDrawer(); 1592 }; 1593 }, 1594 showNodeActions(id) { 1595 const node = db.nodes.find(n => n.id === id); 1596 if (!node) return; 1597 const lang = getLang(); 1598 const dict = langMap[lang] || langMap['en']; 1599 1600 nodeActions.currentId = id; 1601 1602 let items = ''; 1603 if (node.type === 'folder') { 1604 items = ` 1605 <div class="p-4 border-b border-border active:bg-accent flex items-center gap-3 text-base font-medium" onclick="event.stopPropagation(); nodeActions.create('file')"> 1606 <i class="fa-solid fa-file-circle-plus w-6 text-center"></i> ${dict.newFile} 1607 </div> 1608 <div class="p-4 border-b border-border active:bg-accent flex items-center gap-3 text-base font-medium" onclick="event.stopPropagation(); nodeActions.create('folder')"> 1609 <i class="fa-solid fa-folder-plus w-6 text-center"></i> ${dict.newFolder} 1610 </div> 1611 `; 1612 } else { 1613 items = ` 1614 <div class="p-4 border-b border-border active:bg-accent flex items-center gap-3 text-base font-medium" onclick="event.stopPropagation(); nodeActions.download(); uiActions.closeDrawer();"> 1615 <i class="fa-solid fa-download w-6 text-center"></i> ${dict.download} 1616 </div> 1617 <div class="p-4 border-b border-border active:bg-accent flex items-center gap-3 text-base font-medium" onclick="event.stopPropagation(); if(db.activeId !== '${id}') openFile('${id}'); exportHandler.performPdfExport(); uiActions.closeDrawer();"> 1618 <i class="fa-solid fa-file-pdf w-6 text-center text-primary"></i> ${dict.exportPdf} 1619 </div> 1620 `; 1621 } 1622 items += ` 1623 <div class="p-4 border-b border-border active:bg-accent flex items-center gap-3 text-base font-medium" onclick="event.stopPropagation(); nodeActions.rename()"> 1624 <i class="fa-solid fa-pen-to-square w-6 text-center"></i> ${dict.rename} 1625 </div> 1626 <div class="p-4 border-b border-border active:bg-accent flex items-center gap-3 text-base font-medium text-destructive" onclick="event.stopPropagation(); nodeActions.delete()">
1627 <i class="fa-solid fa-trash w-6 text-center"></i> ${dict.delete} 1628 </div> 1629 `; 1630 1631 this.showDrawer(node.name, `<div>${items}</div>`, null, false); 1632 }, 1633 closeDrawer() { 1634 const d = document.getElementById('drawer'); 1635 if (!d) return; 1636 d.classList.add('closing'); 1637 if (this._drawerTimeout) clearTimeout(this._drawerTimeout); 1638 this._drawerTimeout = setTimeout(() => { 1639 d.classList.add('hidden'); 1640 d.style.display = 'none'; 1641 d.classList.remove('closing'); 1642 this._drawerTimeout = null; 1643 }, 300); 1644 }, 1645 showAlert(title, message) { 1646 if (window.innerWidth <= 768) { 1647 this.showDrawer(title, `<p class="text-sm text-muted-foreground">${message}</p>`, () => { }); 1648 return; 1649 } 1650 document.getElementById('alertTitle').innerText = title; 1651 document.getElementById('alertMessage').innerText = message; 1652 this.openModal('alertModal'); 1653 }, 1654 showToast(message, type = 'info') { 1655 const container = document.getElementById('toastContainer'); 1656 if (!container) return; 1657 1658 const toast = document.createElement('div'); 1659 const bgColor = type === 'error' ? 'bg-destructive' : (type === 'success' ? 'bg-green-600' : 'bg-primary'); 1660 const textColor = 'text-white'; 1661 1662 toast.className = `toast-item ${bgColor} ${textColor} px-4 py-3 rounded-lg flex items-center shadow-2xl space-x-3 pointer-events-auto transition-all`; 1663 1664 const icon = type === 'error' ? 'fa-circle-exclamation' : (type === 'success' ? 'fa-circle-check' : 'fa-info-circle'); 1665 toast.innerHTML = ` 1666 <i class="fas ${icon} text-lg shrink-0"></i> 1667 <span class="text-sm font-medium leading-tight">${message}</span> 1668 `; 1669 1670 container.appendChild(toast); 1671 1672 setTimeout(() => { 1673 toast.classList.add('exiting'); 1674 setTimeout(() => toast.remove(), 200); 1675 }, 3000); 1676 }, 1677 showInstallToast(message, btnText, onInstall) { 1678 const container = document.getElementById('toastContainer'); 1679 if (!container) return; 1680 1681 const toast = document.createElement('div'); 1682 toast.className = `toast-item bg-primary text-white px-4 py-3 rounded-xl flex items-center justify-between shadow-2xl space-x-4 pointer-events-auto transition-all border border-white/10`; 1683 1684 toast.innerHTML = ` 1685 <div class="flex items-center space-x-3"> 1686 <i class="fas fa-download text-lg shrink-0"></i> 1687 <span class="text-sm font-bold leading-tight">${message}</span> 1688 </div> 1689 <div class="flex items-center gap-2"> 1690 <button class="bg-white text-primary px-3 py-1.5 rounded-lg text-xs font-black hover:bg-white/90 transition-colors uppercase tracking-wider"> 1691 ${btnText} 1692 </button> 1693 <button class="close-btn text-white/60 hover:text-white transition-colors p-1"> 1694 <i class="fas fa-times"></i> 1695 </button> 1696 </div> 1697 `; 1698 1699 const btn = toast.querySelector('button:not(.close-btn)'); 1700 btn.onclick = () => { 1701 onInstall(); 1702 toast.remove(); 1703 }; 1704 1705 const closeBtn = toast.querySelector('.close-btn'); 1706 closeBtn.onclick = () => { 1707 localStorage.setItem('MD2PDF_INSTALL_DISMISSED', 'true'); 1708 toast.remove(); 1709 }; 1710 1711 container.appendChild(toast); 1712 }, 1713 async copyToClipboard(text) { 1714 try { 1715 if (navigator.clipboard && window.isSecureContext) { 1716 await navigator.clipboard.writeText(text); 1717 } else { 1718 // Fallback for older browsers or non-secure contexts 1719 const textArea = document.createElement('textarea'); 1720 textArea.value = text; 1721 textArea.style.position = 'fixed'; 1722 textArea.style.left = '-999999px'; 1723 textArea.style.top = '-999999px'; 1724 document.body.appendChild(textArea); 1725 textArea.focus(); 1726 textArea.select(); 1727 document.execCommand('copy'); 1728 document.body.removeChild(textArea); 1729 } 1730 const lang = getLang(); 1731 const dict = langMap[lang] || langMap['en']; 1732 this.showToast(dict.copied || "Copied to clipboard", "success"); 1733 } catch (err) { 1734 console.error('Failed to copy text: ', err); 1735 this.showToast("Failed to copy", "error"); 1736 } 1737 }, 1738 copyEditor() { 1739 const editor = document.getElementById('editor'); 1740 if (editor) { 1741 this.copyToClipboard(editor.value); 1742 } 1743 }, 1744 copyPreview() { 1745 const preview = document.getElementById('preview-content'); 1746 if (preview) { 1747 // Get text content from the preview, removing HTML tags 1748 const text = preview.textContent || preview.innerText || ''; 1749 this.copyToClipboard(text); 1750 } 1751 }, 1752 async pasteFromClipboard() { 1753 try { 1754 const text = await navigator.clipboard.readText(); 1755 const editor = document.getElementById('editor'); 1756 if (editor && text) { 1757 // GA4 Tracking 1758 trackEvent('md_clipboard_paste'); 1759 1760 // Insert at cursor 1761 const start = editor.selectionStart; 1762 const end = editor.selectionEnd; 1763 const val = editor.value; 1764 editor.value = val.substring(0, start) + text + val.substring(end); 1765 editor.focus(); 1766 editor.setSelectionRange(start + text.length, start + text.length); 1767 updatePreview(); 1768 save(); 1769 } 1770 } catch (err) { 1771 console.error('Failed to read clipboard: ', err); 1772 // Non-blocking fallback 1773 } 1774 } 1775}; 1776 1777/** 1778 * CONFIG & GLOBAL STATE 1779 */ 1780const CONFIG = { 1781 CLIENT_ID: '161561844059-n4uhfihna0k9vih6qdb888n587fuc1fr.apps.googleusercontent.com', 1782 STORAGE_KEY: 'MD2PDF_STORAGE_V1', 1783 WELCOME_VERSION: '1773326925' // Unix timestamp or unique version 1784}; 1785 1786let db = { 1787 nodes: [], 1788 deletedNodes: [], // Track deleted nodes for sync: { id, remoteId, type } 1789 activeId: null, 1790 expanded: new Set(), 1791 pdfStyle: 'default' 1792}; 1793 1794/** 1795 * INITIALIZATION & CORE FUNCTIONS 1796 */ 1797function initDefaultDocs() { 1798 const lang = getLang(); 1799 const dict = langMap[lang] || langMap['en']; 1800 const folderId = createId(); 1801 const fileId = createId(); 1802 const now = Date.now(); 1803 db.nodes = [ 1804 { id: folderId, type: 'folder', name: dict.welcomeTitle, parentId: null, order: 0, updatedAt: now }, 1805 { id: fileId, type: 'file', name: dict.welcomeFile, parentId: folderId, content: dict.welcomeContent, order: 0, updatedAt: now } 1806 ]; 1807 db.activeId = fileId; 1808 db.expanded = new Set([folderId]); 1809 save(); 1810} 1811 1812function checkWelcomeUpdate() { 1813 const lang = getLang(); 1814 const dict = langMap[lang] || langMap['en']; 1815 const savedVersion = localStorage.getItem('MD2PDF_WELCOME_VERSION'); 1816 1817 // If version mismatched, update it. 1818 if (savedVersion !== CONFIG.WELCOME_VERSION) { 1819 let updated = false;
1820 db.nodes.forEach(node => { 1821 let isWelcomeFile = false; 1822 for (const l in langMap) { 1823 if (node.name === langMap[l].welcomeFile) { 1824 isWelcomeFile = true; 1825 break; 1826 } 1827 } 1828 if (isWelcomeFile) { 1829 node.content = dict.welcomeContent; 1830 updated = true; 1831 } 1832 }); 1833 if (updated) { 1834 save(); 1835 } 1836 } 1837 localStorage.setItem('MD2PDF_WELCOME_VERSION', CONFIG.WELCOME_VERSION); 1838} 1839 1840function normalizeOrders(parentId) { 1841 const siblings = db.nodes.filter(n => n.parentId === parentId) 1842 .sort((a, b) => (a.order || 0) - (b.order || 0) || (a.type === 'folder' ? -1 : 1)); 1843 siblings.forEach((n, i) => n.order = i); 1844} 1845 1846function renderTree() { 1847 const container = document.getElementById('treeContainer'); 1848 if (!container) return; 1849 container.innerHTML = ''; 1850 1851 const renderNodes = (parentId, depth) => { 1852 const children = db.nodes.filter(n => n.parentId === parentId) 1853 .sort((a, b) => (a.order || 0) - (b.order || 0) || (a.type === 'folder' ? -1 : 1)); 1854 1855 children.forEach(node => { 1856 const el = document.createElement('div'); 1857 el.className = `tree-node ${node.id === db.activeId ? 'active' : ''}`; 1858 el.style.paddingLeft = `${depth * 16 + 12}px`; 1859 el.draggable = true; 1860 1861 const isMobile = window.innerWidth <= 768; 1862 1863 el.innerHTML = ` 1864 <i class="fa-solid ${node.type === 'folder' ? (db.expanded.has(node.id) ? 'fa-folder-open' : 'fa-folder') : 'fa-file-lines'} mr-2 opacity-60 text-[12px]"></i> 1865 <span class="truncate text-xs flex-1">${node.name}</span> 1866 <div class="tree-actions"> 1867 ${isMobile ? ` 1868 <button class="w-7 h-7 flex items-center justify-center rounded-md hover:bg-accent/50 active:bg-accent transition-colors" onclick="event.stopPropagation(); uiActions.showNodeActions('${node.id}')"> 1869 <i class="fa-solid fa-ellipsis-vertical text-[12px] opacity-70"></i> 1870 </button> 1871 ` : ` 1872 ${node.type === 'folder' ? ` 1873 <button class="action-btn" onclick="event.stopPropagation(); nodeActions.currentId='${node.id}'; nodeActions.create('file')" title="${langMap[getLang()].newFile}"><i class="fa-solid fa-file-circle-plus text-[10px]"></i></button> 1874 <button class="action-btn" onclick="event.stopPropagation(); nodeActions.currentId='${node.id}'; nodeActions.create('folder')" title="${langMap[getLang()].newFolder}"><i class="fa-solid fa-folder-plus text-[10px]"></i></button> 1875 ` : ` 1876 <button class="action-btn text-primary" onclick="event.stopPropagation(); nodeActions.currentId='${node.id}'; nodeActions.download()" title="${langMap[getLang()].download}"><i class="fa-solid fa-download text-[10px]"></i></button> 1877 <button class="action-btn text-primary" onclick="event.stopPropagation(); if(db.activeId !== '${node.id}') openFile('${node.id}'); exportHandler.performPdfExport()" title="${langMap[getLang()].exportPdf}"><i class="fa-solid fa-file-pdf text-[10px]"></i></button> 1878 `} 1879 <button class="action-btn" onclick="event.stopPropagation(); nodeActions.currentId='${node.id}'; nodeActions.rename()" title="${langMap[getLang()].rename}"><i class="fa-solid fa-pen-to-square text-[10px]"></i></button> 1880 <button class="action-btn text-destructive" onclick="event.stopPropagation(); nodeActions.currentId='${node.id}'; nodeActions.delete()" title="${langMap[getLang()].delete}"><i class="fa-solid fa-trash text-[10px]"></i></button> 1881 `} 1882 </div> 1883 `; 1884 1885 el.onclick = (e) => { 1886 e.stopPropagation(); 1887 if (node.type === 'folder') { 1888 if (db.expanded.has(node.id)) db.expanded.delete(node.id); 1889 else db.expanded.add(node.id); 1890 // On mobile, also make it active to show selected state in drawer if needed 1891 if (isMobile) db.activeId = node.id; 1892 renderTree(); 1893 } else { 1894 openFile(node.id); 1895 } 1896 }; 1897 1898 // Drag and Drop 1899 el.ondragstart = (e) => { 1900 e.dataTransfer.setData('text/plain', node.id); 1901 e.dataTransfer.effectAllowed = 'move'; 1902 }; 1903 1904 el.ondragover = (e) => { 1905 e.preventDefault(); 1906 const rect = el.getBoundingClientRect(); 1907 const relY = e.clientY - rect.top; 1908 1909 el.classList.remove('drag-over', 'drag-over-top', 'drag-over-bottom'); 1910 1911 if (node.type === 'folder') { 1912 if (relY < rect.height * 0.2) el.classList.add('drag-over-top'); 1913 else if (relY > rect.height * 0.8) el.classList.add('drag-over-bottom'); 1914 else el.classList.add('drag-over'); 1915 } else { 1916 if (relY < rect.height * 0.5) el.classList.add('drag-over-top'); 1917 else el.classList.add('drag-over-bottom'); 1918 } 1919 }; 1920 1921 el.ondragleave = () => { 1922 el.classList.remove('drag-over', 'drag-over-top', 'drag-over-bottom'); 1923 }; 1924 1925 el.ondrop = (e) => { 1926 e.preventDefault(); 1927 el.classList.remove('drag-over', 'drag-over-top', 'drag-over-bottom'); 1928 const draggedId = e.dataTransfer.getData('text/plain'); 1929 if (draggedId === node.id) return; 1930 1931 const rect = el.getBoundingClientRect(); 1932 const relY = e.clientY - rect.top; 1933 const draggedNode = db.nodes.find(n => n.id === draggedId); 1934 if (!draggedNode) return; 1935 1936 let targetParentId = node.parentId; 1937 let newOrder = 0; 1938 1939 if (node.type === 'folder' && relY >= rect.height * 0.2 && relY <= rect.height * 0.8) { 1940 // Drop INTO folder 1941 targetParentId = node.id; 1942 const siblings = db.nodes.filter(n => n.parentId === targetParentId); 1943 newOrder = siblings.length > 0 ? Math.max(...siblings.map(s => s.order || 0)) + 1 : 0; 1944 } else { 1945 // Drop BEFORE or AFTER 1946 if (relY < (node.type === 'folder' ? rect.height * 0.2 : rect.height * 0.5)) { 1947 newOrder = (node.order || 0) - 0.5; 1948 } else { 1949 newOrder = (node.order || 0) + 0.5; 1950 } 1951 } 1952 1953 if (isDescendant(draggedId, targetParentId)) { 1954 uiActions.showToast("Cannot move folder into itself", "error"); 1955 return; 1956 } 1957 1958 draggedNode.parentId = targetParentId; 1959 draggedNode.order = newOrder; 1960 1961 normalizeOrders(targetParentId); 1962 1963 if (targetParentId) db.expanded.add(targetParentId); 1964 save(); 1965 renderTree(); 1966 updateBreadcrumb(db.activeId); 1967 }; 1968 1969 container.appendChild(el); 1970 if (node.type === 'folder' && db.expanded.has(node.id)) { 1971 renderNodes(node.id, depth + 1); 1972 } 1973 }); 1974 }; 1975 1976 renderNodes(null, 0); 1977 1978 // Allow dropping on the root container 1979 container.ondragover = (e) => e.preventDefault(); 1980 container.ondrop = (e) => { 1981 if (e.target === container) { 1982 const draggedId = e.dataTransfer.getData('text/plain'); 1983 const draggedNode = db.nodes.find(n => n.id === draggedId); 1984 if (draggedNode && draggedNode.parentId !== null) { 1985 draggedNode.parentId = null; 1986 draggedNode.order = db.nodes.filter(n => n.parentId === null).length; 1987 normalizeOrders(null); 1988 save(); 1989 renderTree(); 1990 updateBreadcrumb(db.activeId); 1991 } 1992 } 1993 }; 1994} 1995 1996function isDescendant(parentIdx, childId) { 1997 if (!childId) return false;
1998 let curr = db.nodes.find(n => n.id === childId); 1999 while (curr) { 2000 if (curr.parentId === parentIdx) return true; 2001 curr = db.nodes.find(n => n.id === curr.parentId); 2002 } 2003 return false; 2004} 2005 2006function openFile(id) { 2007 db.activeId = id; 2008 const file = db.nodes.find(n => n.id === id); 2009 if (!file) return; 2010 const editor = document.getElementById('editor'); 2011 editor.value = file.content || ''; 2012 document.getElementById('filenameDisplay').value = file.name; 2013 updateBreadcrumb(id); 2014 renderTree(); 2015 save(); 2016 updatePreview(); 2017 2018 if (window.innerWidth <= 768) { 2019 const sidebar = document.getElementById('sidebar'); 2020 const overlay = document.getElementById('sidebar-overlay'); 2021 if (sidebar) sidebar.classList.remove('open'); 2022 if (overlay) overlay.classList.add('hidden'); 2023 } 2024} 2025 2026const nodeActions = { 2027 currentId: null, 2028 create(type) { 2029 const lang = getLang(); 2030 const dict = langMap[lang] || langMap['en']; 2031 const title = type === 'file' ? dict.newFileTitle : dict.newFolderTitle; 2032 const defaultName = type === 'file' ? 'NewNote.md' : (dict.newFolder || 'New Folder'); 2033 2034 uiActions.showPrompt(title, defaultName, (name) => { 2035 if (!name) return; 2036 const parent = db.nodes.find(n => n.id === this.currentId); 2037 const parentId = parent ? (parent.type === 'folder' ? parent.id : parent.parentId) : null; 2038 const siblings = db.nodes.filter(n => n.parentId === parentId); 2039 2040 const newNode = { 2041 id: createId(), 2042 type, 2043 name, 2044 parentId, 2045 content: type === 'file' ? '' : undefined, 2046 order: siblings.length, 2047 updatedAt: Date.now() 2048 }; 2049 2050 db.nodes.push(newNode); 2051 if (parentId) db.expanded.add(parentId); 2052 if (type === 'file') openFile(newNode.id); 2053 renderTree(); 2054 save(); 2055 }); 2056 }, 2057 delete() { 2058 const lang = getLang(); 2059 const dict = langMap[lang] || langMap['en']; 2060 2061 uiActions.showConfirm(dict.delete || "Delete", langMap[lang].confirmDelete, () => { 2062 const idsToDelete = new Set(); 2063 const nodesToDelete = []; 2064 const collectIds = (id) => { 2065 const node = db.nodes.find(n => n.id === id); 2066 if (node) { 2067 idsToDelete.add(id); 2068 nodesToDelete.push(node); 2069 } 2070 db.nodes.filter(n => n.parentId === id).forEach(child => collectIds(child.id)); 2071 }; 2072 collectIds(this.currentId); 2073 2074 // Add to deletedNodes tracking if they have remoteId 2075 nodesToDelete.forEach(node => { 2076 if (node.remoteId) { 2077 db.deletedNodes.push({ 2078 id: node.id, 2079 remoteId: node.remoteId, 2080 type: node.type 2081 }); 2082 } 2083 }); 2084 2085 db.nodes = db.nodes.filter(n => !idsToDelete.has(n.id)); 2086 if (idsToDelete.has(db.activeId)) { 2087 db.activeId = null; 2088 const editor = document.getElementById('editor'); 2089 if (editor) editor.value = ''; 2090 const filenameDisplay = document.getElementById('filenameDisplay'); 2091 if (filenameDisplay) filenameDisplay.value = ''; 2092 updatePreview(); 2093 } 2094 renderTree(); 2095 save(); 2096 }); 2097 }, 2098 rename() { 2099 const node = db.nodes.find(n => n.id === this.currentId); 2100 const lang = getLang(); 2101 const dict = langMap[lang] || langMap['en']; 2102 2103 uiActions.showPrompt(dict.renameTitle, node.name, (newName) => { 2104 if (newName && newName !== node.name) { 2105 node.name = newName; 2106 node.updatedAt = Date.now(); 2107 renderTree(); 2108 save(); 2109 if (node.id === db.activeId) { 2110 document.getElementById('filenameDisplay').value = node.name; 2111 } 2112 } 2113 }); 2114 }, 2115 download() { 2116 const node = db.nodes.find(n => n.id === this.currentId); 2117 if (node.type !== 'file') return; 2118 const blob = new Blob([node.content], { type: 'text/markdown' }); 2119 const url = URL.createObjectURL(blob); 2120 const a = document.createElement('a'); 2121 a.href = url; a.download = node.name; a.click(); 2122 } 2123}; 2124 2125/** 2126 * URL PARAMS (GROWTH ENGINE) 2127 */ 2128function handleUrlParams() { 2129 const params = new URLSearchParams(window.location.search); 2130 const titleParam = params.get('title'); 2131 const contentParam = params.get('content'); 2132 const b64 = params.get('b64'); 2133 const mode = params.get('mode'); 2134 2135 // 鲿£ç URL è§£ç é»è¾ï¼é对 Double Encoding 设计 2136 const robustDecode = (str) => { 2137 if (!str) return ""; 2138 let current = str; 2139 2140 // å°è¯æå¤è§£ç 3 次ï¼å¤çå¯è½åå¨ç Double/Triple Encoding 2141 for (let i = 0; i < 3; i++) { 2142 if (!current.includes('%')) break; 2143 try { 2144 // æ åè§£ç 2145 const next = decodeURIComponent(current); 2146 if (next === current) break; 2147 current = next; 2148 } catch (e) { 2149 // 妿æ åè§£ç å éæ³å符ï¼AI å¹»è§ï¼æ¥éï¼å°è¯åèä¿®å¤ 2150 try { 2151 const bytes = []; 2152 for (let j = 0; j < current.length; j++) { 2153 if (current[j] === '%' && /^[0-9A-Fa-f]{2}$/.test(current.substring(j + 1, j + 3))) { 2154 bytes.push(parseInt(current.substring(j + 1, j + 3), 16)); 2155 j += 2; 2156 } else { 2157 // è·³è¿ AI 误å çéæ³ç¾åå·ææ¯ä¿çåæ 2158 if (current[j] === '%' && j + 1 < current.length && current.charCodeAt(j + 1) > 127) continue; 2159 const charBytes = new TextEncoder().encode(current[j]);
2160 charBytes.forEach(b => bytes.push(b)); 2161 } 2162 } 2163 current = new TextDecoder('utf-8', { fatal: false }).decode(new Uint8Array(bytes)); 2164 } catch (err) { 2165 break; 2166 } 2167 } 2168 } 2169 return current; 2170 }; 2171 2172 if (titleParam || contentParam || b64) { 2173 try { 2174 let title = robustDecode(titleParam) || 'Untitled'; 2175 let content = ''; 2176 2177 if (b64) { 2178 // b64 ä¿æåæ · 2179 try { 2180 const binString = window.atob(b64.replace(/ /g, '+')); 2181 const bytes = new Uint8Array(binString.length); 2182 for (let i = 0; i < binString.length; i++) bytes[i] = binString.charCodeAt(i); 2183 content = new TextDecoder().decode(bytes); 2184 } catch (e) { 2185 try { 2186 content = decodeURIComponent(escape(window.atob(b64.replace(/ /g, '+')))); 2187 } catch (e2) { 2188 content = "Error decoding Base64 content."; 2189 } 2190 } 2191 } else { 2192 content = robustDecode(contentParam || ''); 2193 } 2194 2195 if (!content && !b64) return; 2196 2197 const newNode = { 2198 id: createId(), 2199 type: 'file', 2200 name: title.endsWith('.md') ? title : title + ".md", 2201 parentId: null, 2202 content: content, 2203 updatedAt: Date.now() 2204 }; 2205 db.nodes.push(newNode); 2206 openFile(newNode.id); 2207 2208 if (mode === 'export') { 2209 const lang = getLang(); 2210 const dict = langMap[lang] || langMap['en']; 2211 uiActions.showToast(dict.exporting || "Exporting PDF...", "info"); 2212 2213 setTimeout(() => { 2214 exportHandler.performPdfExport(); 2215 }, 1200); 2216 } 2217 2218 window.history.replaceState({}, document.title, "/"); 2219 save(); // ç¡®ä¿æä¹ åå¯¼å ¥çå 容 2220 return true; 2221 } catch (err) { 2222 console.error("URL Params Parse Error:", err); 2223 return false; 2224 } 2225 } 2226 return false; 2227} 2228 2229/** 2230 * LICENSE / BILLING STATE 2231 * The server is the source of truth. localStorage is only a short offline cache. 2232 */ 2233const licenseManager = { 2234 cacheKey: 'MD2PDF_LICENSE_CACHE_V1', 2235 cacheTtlMs: 5 * 60 * 1000, 2236 offlineGraceMs: 7 * 24 * 60 * 60 * 1000, 2237 state: null, 2238 refreshPromise: null, 2239 2240 copy() { 2241 const localized = langMap[getLang()] || langMap.en; 2242 return getLang() === 'zh' 2243 ? { upgrade: 'ç§»é¤ PDF åçä¿¡æ¯', statusFree: 'å è´¹çï¼æåä¸é¡µå å« MD2PDF åçä¿¡æ¯', statusPro: 'åçä¿¡æ¯å·²ç§»é¤', lifetime: 'æ°¸ä¹ ç · $19.9', monthly: 'æåº¦ç · $1.9/æ', oneTime: '䏿¬¡æ§ä»æ¬¾ï¼ææè®¾å¤å¯ç¨', monthlyDesc: 'å¯éæ¶åæ¶ï¼è®¢é æææå å¯ç¨', fullPricing: 'æ¥ç宿´ä»·æ ¼æ¹æ¡', restore: 'æ¢å¤è´ä¹°', manage: '管ç订é ', account: 'è´¦æ·', close: 'å ³é', email: 'è´ä¹°æ¶ä½¿ç¨çé®ç®±', linkSent: 'è¯·æ£æ¥é®ç®±å¹¶ç¹å»æ¿æ´»é¾æ¥ã', checkoutError: 'ææ¶æ æ³æå¼æ¯ä»é¡µé¢ï¼è¯·ç¨åéè¯ã', linkError: 'æ¿æ´»é¾æ¥åé失败ï¼è¯·ç¨åéè¯ã', title: 'ç§»é¤ PDF åçä¿¡æ¯', desc: 'å 费导åºä»å¯ä½¿ç¨ãä»è´¹ååªä¼å½±åæ°ç PDF 导åºã', licenseActivated: 'MD2PDF æç已卿¤è®¾å¤ä¸æ¿æ´»ã' } 2244 : { upgrade: 'Remove PDF branding', statusFree: 'Free: MD2PDF branding is included on the last page', statusPro: 'PDF branding is removed', lifetime: 'Lifetime · $19.9', monthly: 'Monthly · $1.9/mo', oneTime: 'One-time payment, all devices', monthlyDesc: 'Cancel anytime; active while subscribed', fullPricing: 'View full pricing', restore: 'Restore purchase', manage: 'Manage subscription', account: 'Account', close: 'Close', email: 'Email used for purchase', linkSent: 'Check your email for the activation link.', checkoutError: 'Unable to open checkout. Please try again.', linkError: 'Unable to send the activation link.', title: 'Remove PDF branding', desc: 'The free export remains available. Payment affects new PDF exports only.', licenseActivated: localized.licenseActivated || 'MD2PDF access activated on this device.' }; 2245 }, 2246 2247 readCache() { try { return JSON.parse(localStorage.getItem(this.cacheKey) || 'null'); } catch { return null; } },
2248 writeCache(state) { try { localStorage.setItem(this.cacheKey, JSON.stringify(state)); } catch { /* private browsing */ } }, 2249 2250 async api(path, options = {}) { 2251 const controller = new AbortController(); 2252 const timeout = setTimeout(() => controller.abort(), options.timeout || 6000); 2253 try { 2254 const response = await fetch(path, { credentials: 'include', ...options, signal: controller.signal, headers: { 'Content-Type': 'application/json', ...(options.headers || {}) } }); 2255 const data = await response.json().catch(() => ({})); 2256 if (!response.ok) throw new Error(data.error || `Request failed (${response.status})`); 2257 return data; 2258 } finally { clearTimeout(timeout); } 2259 }, 2260 2261 async refresh(force = false) { 2262 const now = Date.now(); 2263 if (!force && this.state?.checkedAt && now - new Date(this.state.checkedAt).getTime() < this.cacheTtlMs) return this.state; 2264 if (this.refreshPromise) return this.refreshPromise; 2265 this.refreshPromise = this.api('/api/license/status') 2266 .then(data => { this.state = data; this.writeCache(data); this.updateUi(); return data; }) 2267 .catch(error => { 2268 const cached = this.state || this.readCache(); 2269 if (cached?.canRemoveBranding && cached.checkedAt && now - new Date(cached.checkedAt).getTime() <= this.offlineGraceMs) { 2270 this.state = { ...cached, offline: true }; this.updateUi(); return this.state; 2271 } 2272 console.debug('[License] status unavailable:', error.message); 2273 this.state = { authenticated: false, canRemoveBranding: false, entitlement: 'branding_required', checkedAt: new Date().toISOString() }; this.updateUi(); return this.state; 2274 }) 2275 .finally(() => { this.refreshPromise = null; }); 2276 return this.refreshPromise; 2277 }, 2278 2279 async canRemoveBranding() { return (await this.refresh())?.canRemoveBranding === true; }, 2280 2281 async startCheckout(plan) { 2282 try { 2283 const result = await this.api('/api/billing/create-checkout', { method: 'POST', body: JSON.stringify({ plan }) }); 2284 if (result.url) window.location.href = result.url; 2285 } catch { uiActions.showToast(this.copy().checkoutError, 'error'); } 2286 }, 2287 2288 async requestMagicLink() { 2289 const input = document.getElementById('licenseEmailInput'); 2290 const message = document.getElementById('licenseModalMessage'); 2291 const email = input?.value.trim(); 2292 if (!email) return; 2293 try { 2294 await this.api('/api/auth/request-link', { method: 'POST', body: JSON.stringify({ email }) }); 2295 if (message) { message.textContent = this.copy().linkSent; message.className = 'mt-2 text-xs text-green-600'; } 2296 } catch { 2297 if (message) { message.textContent = this.copy().linkError; message.className = 'mt-2 text-xs text-red-600'; } 2298 } 2299 }, 2300 2301 async openPortal() { 2302 try { 2303 const result = await this.api('/api/billing/portal', { method: 'POST', body: '{}' }); 2304 if (result.url) window.location.href = result.url; 2305 } catch { uiActions.showToast(this.copy().checkoutError, 'error'); } 2306 }, 2307 2308 closeModal() { document.getElementById('licenseUpgradeModal')?.remove(); }, 2309 2310 openUpgrade() { 2311 this.closeModal(); 2312 const c = this.copy(); 2313 const modal = document.createElement('div'); 2314 modal.id = 'licenseUpgradeModal'; 2315 modal.className = 'fixed inset-0 z-[10000] flex items-start sm:items-center justify-center overflow-y-auto bg-black/40 p-3 sm:p-4'; 2316 modal.innerHTML = `<div class="my-3 sm:my-6 w-full max-w-md rounded-2xl border border-border bg-card p-4 sm:p-5 shadow-2xl" style="max-height:calc(100dvh - 1.5rem);overflow-y:auto;"> 2317 <div class="flex items-start justify-between gap-3"><div><h2 class="text-lg font-bold">${c.title}</h2><p class="mt-1 text-xs text-muted-foreground">${c.desc}</p></div><button type="button" class="h-7 w-7 rounded-md hover:bg-accent" onclick="licenseManager.closeModal()">Ã</button></div> 2318 <div class="mt-5 grid gap-3"><button type="button" class="rounded-xl border-2 border-primary bg-primary/5 p-4 text-left hover:bg-primary/10" onclick="licenseManager.startCheckout('lifetime')"><span class="block text-sm font-bold">${c.lifetime}</span><span class="mt-1 block text-xs text-muted-foreground">${c.oneTime}</span></button><button type="button" class="rounded-xl border border-border p-4 text-left hover:bg-accent" onclick="licenseManager.startCheckout('monthly')"><span class="block text-sm font-bold">${c.monthly}</span><span class="mt-1 block text-xs text-muted-foreground">${c.monthlyDesc}</span></button></div> 2319 <div class="mt-5 border-t border-border pt-4"><label class="block text-xs font-semibold mb-1.5">${c.email}</label><div class="flex flex-col gap-2 sm:flex-row"><input id="licenseEmailInput" type="email" class="min-w-0 flex-1 h-10 rounded-lg border border-input bg-background px-3 text-xs" placeholder="[email protected]"><button type="button" class="h-10 w-full shrink-0 rounded-lg bg-secondary px-3 text-xs font-bold sm:w-auto" onclick="licenseManager.requestMagicLink()">${c.restore}</button></div><p id="licenseModalMessage" class="mt-2 text-xs text-muted-foreground"></p></div> 2320 <div class="mt-4 flex items-center justify-between gap-3"><a href="/pricing/" class="text-xs text-primary hover:underline">${c.fullPricing}</a><button type="button" class="text-xs text-muted-foreground hover:text-foreground" onclick="licenseManager.closeModal()">${c.close}</button></div> 2321 </div>`; 2322 modal.addEventListener('click', event => { if (event.target === modal) this.closeModal(); }); 2323 document.body.appendChild(modal); 2324 }, 2325 2326 updateUi() { 2327 const statusEl = document.getElementById('licenseStatusText'); 2328 const exportButton = document.getElementById('exportBillingButton'); 2329 const accountLink = document.getElementById('exportBillingAccountLink'); 2330 const exportStatus = document.getElementById('exportBillingStatus'); 2331 const exportTitle = document.getElementById('exportBillingTitle'); 2332 const c = this.copy(); 2333 if (statusEl) statusEl.textContent = this.state?.canRemoveBranding ? c.statusPro : c.statusFree; 2334 if (exportTitle) exportTitle.textContent = this.state?.canRemoveBranding ? c.statusPro : c.title; 2335 if (exportStatus) exportStatus.textContent = this.state?.canRemoveBranding ? c.statusPro : c.statusFree; 2336 if (exportButton) { 2337 if (this.state?.canRemoveBranding) { 2338 if (this.state?.canManageBilling) { 2339 exportButton.hidden = false;
2340 exportButton.textContent = c.manage; 2341 exportButton.onclick = () => this.openPortal(); 2342 } else { 2343 // The static Account link beside this button already provides account access. 2344 exportButton.hidden = true; 2345 } 2346 } else { 2347 exportButton.hidden = false; 2348 exportButton.textContent = c.upgrade; 2349 exportButton.onclick = () => this.openUpgrade(); 2350 } 2351 } 2352 if (accountLink) accountLink.hidden = false; 2353 }, 2354 2355 mount() { 2356 this.updateUi(); 2357 this.refresh(); 2358 const params = new URLSearchParams(window.location.search); 2359 if (params.get('billing') === 'success') { 2360 setTimeout(() => { uiActions.showToast('Payment received. Enter your payment email to activate this device.', 'success'); this.openUpgrade(); }, 300); 2361 } else if (params.get('license') === 'activated') { 2362 this.refresh(true); 2363 uiActions.showToast(this.copy().licenseActivated, 'success'); 2364 } 2365 } 2366}; 2367 2368window.licenseManager = licenseManager; 2369 2370/** 2371 * PDF EXPORT (THE CORE) 2372 */ 2373const exportHandler = { 2374 async performPdfExport() { 2375 if (this.isExporting) return; 2376 const file = db.nodes.find(n => n.id === db.activeId); 2377 if (!file) return; 2378 2379 // GA4 Tracking 2380 trackEvent('md_export_pdf', { 2381 theme_used: document.body.getAttribute('data-theme') || 'light', 2382 content_length: file.content ? file.content.length : 0 2383 }); 2384 2385 // å¼ºè¡æ´æ°ä¸æ¬¡é¢è§ï¼ç¡®ä¿è·åçæ¯ææ°å 容ï¼å°¤å ¶æ¯ç§»å¨ç«¯å¤äºç¼è¾æ¨¡å¼æ¶ 2386 updatePreview(); 2387 2388 const previewEl = document.getElementById('preview-content'); 2389 if (!previewEl) return; 2390 2391 const lang = getLang(); 2392 const dict = langMap[lang] || langMap['en']; 2393 const canRemoveBranding = await licenseManager.canRemoveBranding(); 2394 2395 this.isExporting = true; 2396 document.body.classList.add('is-exporting'); 2397 2398 const exportBtn = document.querySelector('[onclick*="performPdfExport"]'); 2399 const originalBtnHtml = exportBtn ? exportBtn.innerHTML : ''; 2400 if (exportBtn) { 2401 exportBtn.style.opacity = '0.5'; 2402 exportBtn.innerHTML = `<i class="fas fa-spinner fa-spin sm:mr-2"></i><span class="hidden sm:inline">${dict.exporting}</span>`; 2403 } 2404 2405 // 1. è·åæä»¶ç导åºè®¾ç½®ï¼å¦ææ²¡æå使ç¨é»è®¤å¼ï¼ 2406 const exportSettings = file.exportSettings || {}; 2407 const style = exportSettings.style || db.pdfStyle || 'default'; 2408 let styleCss = ''; 2409 2410 if (style === 'academic') { 2411 styleCss = ` 2412 .pdf-main-container { 2413 padding: 0 !important; 2414 font-family: "STSong", "SimSun", "Garamond", "Georgia", serif !important; 2415 line-height: 1.8 !important; 2416 text-align: justify; 2417 } 2418 .markdown-body { font-family: inherit !important; font-size: 15px !important; color: #111 !important; } 2419 .markdown-body h1, .markdown-body h2 { border-bottom: none !important; text-align: center; margin-top: 1.5em !important; font-weight: 700; } 2420 .markdown-body blockquote { border-left: 3px solid #ccc !important; font-style: italic; color: #444 !important; background: transparent !important; } 2421 .markdown-body code { background: #f5f5f5 !important; } 2422 `; 2423 } else if (style === 'modern') { 2424 styleCss = ` 2425 .pdf-main-container { 2426 padding: 0 !important; 2427 font-family: "Inter", -apple-system, system-ui, sans-serif !important; 2428 line-height: 1.6 !important; 2429 color: #1a1a1a !important; 2430 } 2431 .markdown-body { font-family: inherit !important; font-size: 16px !important; } 2432 .markdown-body h1 { color: #000; font-weight: 800; letter-spacing: -0.02em; border: none !important; font-size: 2.25em !important; } 2433 .markdown-body h2 { border: none !important; font-weight: 700; margin-top: 2em !important; } 2434 .markdown-body pre { background: #f8f9fa !important; border: 1px solid #e9ecef !important; border-radius: 12px !important; padding: 20px !important; } 2435 .markdown-body code { color: #7c3aed !important; background: rgba(124, 58, 237, 0.05) !important; border-radius: 4px; } 2436 .markdown-body blockquote { border: none !important; padding: 20px !important; background: #f1f5f9 !important; border-radius: 12px !important; color: #475569 !important; } 2437 `; 2438 } else if (style === 'standard') { 2439 styleCss = ` 2440 .pdf-main-container { 2441 padding: 0 !important; 2442 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif !important; 2443 line-height: 1.6 !important; 2444 } 2445 .markdown-body { font-family: inherit !important; font-size: 16px !important; } 2446 .markdown-body h1 { border-bottom: 1px solid #eaecef
2446!important; padding-bottom: 0.3em !important; font-weight: 600; } 2447 .markdown-body h2 { border-bottom: 1px solid #eaecef !important; padding-bottom: 0.3em !important; font-weight: 600; } 2448 .markdown-body pre { background-color: #f6f8fa !important; border-radius: 6px !important; padding: 16px !important; } 2449 `; 2450 } else { 2451 // "Default" - Exactly match the preview's look 2452 styleCss = ` 2453 .pdf-main-container { 2454 padding: 0 !important; 2455 font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Amiri", "Tahoma", "Arial", sans-serif !important; 2456 line-height: 1.75 !important; 2457 } 2458 .markdown-body { font-family: inherit !important; font-size: 16px !important; color: #24292e !important; } 2459 .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { 2460 font-family: inherit !important; 2461 font-weight: 600 !important; 2462 line-height: 1.25 !important; 2463 margin-top: 1.5em !important; 2464 margin-bottom: 1rem !important; 2465 } 2466 .markdown-body h1 { font-size: 2em !important; padding-bottom: 0.3em !important;
2466 border-bottom: 1px solid #eaecef !important; } 2467 .markdown-body h2 { font-size: 1.5em !important; padding-bottom: 0.3em !important; border-bottom: 1px solid #eaecef !important; } 2468 .markdown-body pre { 2469 background-color: #f6f8fa !important; 2470 border-radius: 8px !important; 2471 padding: 16px !important; 2472 border: 1px solid #e1e4e8 !important; 2473 } 2474 .markdown-body blockquote { 2475 padding: 0 1em !important; 2476 color: #6a737d !important; 2477 border-left: 0.25em solid #dfe2e5 !important; 2478 background: transparent !important; 2479 } 2480 `; 2481 } 2482 2483 // 2. è·å PDF 设置并å夿³¨å ¥å 容 2484 // ä¼å ä½¿ç¨æä»¶çº§å«ç导åºè®¾ç½®ï¼å¦ææ²¡æå使ç¨é»è®¤ç页ç页è设置 2485 const dateStr = new Date().toLocaleDateString(lang === 'zh' ? 'zh-CN' : (lang === 'tw' ? 'zh-TW' : 'en-US'), { 2486 year: 'numeric', 2487 month: 'long', 2488 day: 'numeric' 2489 }); 2490 2491 const replaceMacros = (str) => { 2492 if (!str) return ''; 2493 return str 2494 .replace(/{date}/g, dateStr) 2495 .replace(/{title}/g, file.name || 'document') 2496 // ç¡®ä¿é¡µç å ç´ æå 容ï¼ä»¥ä¾¿è®¡æ°å¨å¨æäºæµè§å¨æ¨¡å¼ä¸çæ 2497 .replace(/{pagenum}/g, '<span class="page-number"> </span>') 2498 .replace(/{totalpages}/g, '<span class="page-count"></span>'); 2499 }; 2500 2501 const hLeft = replaceMacros(exportSettings['export-header-left']); 2502 const hCenter = replaceMacros(exportSettings['export-header-center']); 2503 const hRight = replaceMacros(exportSettings['export-header-right']); 2504 const fLeft = replaceMacros(exportSettings['export-footer-left']); 2505 const fCenter = replaceMacros(exportSettings['export-footer-center']); 2506 const fRight = replaceMacros(exportSettings['export-footer-right']); 2507 2508 const logoLeft = exportSettings['export-logo-left'] || ''; 2509 const logoRight = exportSettings['export-logo-right'] || ''; 2510 const watermarkText = exportSettings['export-watermark'] || ''; 2511 2512 // æ£æ¥æ¯å¦çç设置äºé¡µçæé¡µèå 容 (å æ¬æåå Logo) 2513 const hasHeader = !!(exportSettings['export-header-left'] || exportSettings['export-header-center'] || exportSettings['export-header-right'] || logoLeft || logoRight); 2514 const hasFooter = !!(exportSettings['export-footer-left'] || exportSettings['export-footer-center'] || exportSettings['export-footer-right']); 2515 2516 const container = document.createElement('div'); 2517 container.id = 'pdf-export-wrapper'; 2518 if (lang === 'ar') { 2519 container.setAttribute('dir', 'rtl'); 2520 container.classList.add('rtl-mode'); 2521 } 2522 Object.assign(container.style, { 2523 // å®ä½å°è§å£åç¹ï¼ç¡®ä¿ iOS Safari ä¸ä¼å å ç´ è¶ åºæ¸²æèå´èè·³è¿å¸å±è®¡ç®ã 2524 // ä¸ä½¿ç¨ zIndex:'-1'ï¼å èæ ·å¼ä¼å 级é«äº @media print è§åï¼ä¼å¯¼è´æå°æ¶ 2525 // 容å¨è¢«æ¸²æå¨é¡µé¢ç½è²èæ¯ä¹åï¼è¾åºç©ºç½ PDFï¼ã 2526 // clip-path:inset(100%) å¨å±å¹ä¸å®å ¨åªè£å ç´ ä½¿å ¶ä¸å¯è§ï¼ä¸ä¸å½±åå¸å±æµéï¼ 2527 // @media print ä¸å°è¢« clip-path:none è¦çã 2528 position: 'fixed', 2529 top: '0', 2530 left: '0', 2531 width: '210mm', 2532 pointerEvents: 'none', 2533 background: 'white', 2534 clipPath: 'inset(100%)', 2535 visibility: 'hidden' 2536 }); 2537 2538 // 3. å éå 容并å¤çå°é¢é¡µä¸ç®å½ 2539 const clone = previewEl.cloneNode(true); 2540 clone.querySelectorAll('.copy-btn, button, .action-btn').forEach(el => el.remove()); 2541 2542 let coverHtml = ''; 2543 // ç§»é¤ export-cover-show-header 设置ï¼å¼ºå¶å°é¢ä½¿ç¨æ£å¸¸é»è¾ï¼ä¸å使ç¨é®ç½©å± 2544 if (exportSettings['export-use-cover']) { 2545 coverHtml = ` 2546 <div class="pdf-cover-page" style="height: 980px; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; background: transparent; position: relative;"> 2547 <h1 style="font-size: 3.5rem; margin-bottom: 1rem; border: none !important;">${exportSettings['export-title'] || file.name}</h1> 2548 <h2 style="font-size: 1.5rem; color: #64748b; font-weight: normal; margin-top: 0; border: none !important;">${exportSettings['export-subtitle'] || ''}</h2> 2549 </div> 2550 `; 2551 } 2552 2553 let tocHtml = ''; 2554 if (exportSettings['export-use-toc']) { 2555 const headings = Array.from(clone.querySelectorAll('h1, h2, h3')); 2556 if (headings.length > 0) { 2557 const tocItems = headings.map((h, i) => { 2558 const id = `toc-anchor-${i}`; 2559 h.setAttribute('id', id); 2560 const level = parseInt(h.tagName[1]); 2561 const indentSide = lang === 'ar' ? 'margin-right' : 'margin-left'; 2562 const paddingSide = lang === 'ar' ? 'padding-left' : 'padding-right'; 2563 return `<a href="#${id}" style="text-decoration: none; color: inherit; display: block; ${indentSide}: ${(level - 1) * 20}px; margin-bottom: 12px; font-size: 14px;"> 2564 <div style="display: flex; justify-content: space-between; border-bottom: 1px dotted #e2e8f0; padding-bottom: 4px;">
2565 <span style="background: white; ${paddingSide}: 8px;">${h.innerText}</span> 2566 </div> 2567 </a>`; 2568 }).join(''); 2569 2570 const tocMap = { 2571 'en': 'Table of Contents', 2572 'zh': 'ç®å½', 2573 'tw': 'ç®é', 2574 'es': 'Ãndice', 2575 'de': 'Inhaltsverzeichnis', 2576 'ja': 'ç®æ¬¡', 2577 'ko': '목차', 2578 'ru': 'СодеÑжание', 2579 'bg': 'СÑдÑÑжание', 2580 'it': 'Indice', 2581 'ar': 'جدÙ٠اÙÙ ØØªÙÙØ§Øª' 2582 }; 2583 const tocTitle = tocMap[lang] || tocMap['en']; 2584 2585 tocHtml = ` 2586 <div class="pdf-toc-page" style="padding: 40px 0; min-height: 980px; background: transparent; position: relative; direction: ${lang === 'ar' ? 'rtl' : 'ltr'};"> 2587 <h1 style="text-align: center; margin-bottom: 3rem; border: none !important; font-size: 2rem;">${tocTitle}</h1> 2588 <div style="max-width: 600px; margin: 0 auto;">${tocItems}</div> 2589 </div> 2590 `; 2591 } 2592 } 2593 2594 const styleTag = document.createElement('style'); 2595 styleTag.textContent = ` 2596 #pdf-export-wrapper .pdf-main-container { 2597 width: 210mm !important; 2598 background-color: #ffffff !important; 2599 color: #000000 !important; 2600 position: relative !important; 2601 box-sizing: border-box !important; 2602 font-size: 16px !important; 2603 padding: 40px !important; 2604 -webkit-font-smoothing: antialiased; 2605 } 2606 ${styleCss} 2607 2608 @media print { 2609 html, body { 2610 height: auto !important; 2611 overflow: visible !important; 2612 margin: 0 !important; 2613 padding: 0 !important; 2614 } 2615 2616 /* æ¾å¼åå§å页ç 计æ°ï¼é¿å æµè§å¨é»è®¤å¼ä¸º0 */ 2617 body { 2618 counter-reset: page 0; 2619 } 2620 2621 @page { 2622 margin: 0; /* 强å¶ä¸º0ï¼ç±åºç¨å 鍿§å¶é¡µè¾¹è·ï¼è§£å³å页溢åºé®é¢ */ 2623 size: A4; 2624 counter-increment: page; 2625 } 2626 2627 /* é JS å页模å¼ä¸ç容å¨è¾¹è·è¡¥å */ 2628 #pdf-export-wrapper:not(.js-paginate) .pdf-main-container { 2629 margin: 0 auto !important; 2630 padding: ${hasHeader ? '23mm' : '15mm'} 15mm ${hasFooter ? '23mm' : '15mm'} 15mm !important; 2631 width: 210mm !important; 2632 box-sizing: border-box !important; 2633 } 2634 2635 /* èªå®ä¹é¡µçé¡µèæ ·å¼ - è¿äºæ¯åºç¨å 设置çï¼ä¸åæµè§å¨æå°å¯¹è¯æ¡å½±å */ 2636 .pdf-header { 2637 position: fixed; 2638 top: 6mm; 2639 left: 0; 2640 right: 0; 2641 height: 12mm; 2642 display: ${hasHeader ? 'flex' : 'none'} !important; 2643 justify-content: space-between; 2644 align-items: center; 2645 font-size: 10px; 2646 color: #334155; 2647 padding: 0 15mm; 2648 border-bottom: 0.5px solid #f1f5f9; 2649 z-index: 99999 !important; 2650 background: white !important; 2651 -webkit-print-color-adjust: exact !important; 2652 print-color-adjust: exact !important; 2653 overflow: hidden; 2654 box-sizing: border-box !important; 2655 } 2656 .pdf-footer { 2657 position: fixed; 2658 bottom: 6mm; 2659 left: 0; 2660 right: 0; 2661 height: 12mm; 2662 display: ${hasFooter ? 'flex' : 'none'} !important; 2663 justify-content: space-between; 2664 align-items: center; 2665 font-size: 10px; 2666 color: #334155; 2667 padding: 0 15mm; 2668 border-top: 0.5px solid #f1f5f9; 2669 z-index: 99999 !important; 2670 background: white !important; 2671 -webkit-print-color-adjust: exact !important; 2672 print-color-adjust: exact !important; 2673 } 2674 2675 /* JS å页æ¶ï¼ä½¿ç¨æ¯é¡µç»å¯¹å®ä½ç页ç/页è */ 2676 #pdf-export-wrapper.js-paginate .pdf-header, 2677 #pdf-export-wrapper.js-paginate .pdf-footer { 2678 display: none !important; 2679 } 2680 #pdf-export-wrapper.js-paginate { 2681 --footer-offset: 0; 2682 } 2683 #pdf-export-wrapper.js-paginate .pdf-page-header { 2684 position: absolute; 2685 left: 10mm; 2686 right: 10mm; 2687 height: 12mm; 2688 display: ${hasHeader ? 'flex' : 'none'} !important; 2689 justify-content: space-between; 2690 align-items: center; 2691 font-size: 10px; 2692 color: #334155; 2693 padding: 0; 2694 background: white !important; 2695 -webkit-print-color-adjust: exact !important; 2696 print-color-adjust: exact !important; 2697 box-sizing: border-box !important; 2698 z-index: 99998 !important; 2699 top: 6mm; 2700 border-bottom: 0.5px solid #f1f5f9; 2701 } 2702 #pdf-export-wrapper.js-paginate .pdf-page-footer { 2703 position: absolute; 2704 left: 10mm; 2705 right: 10mm; 2706 height: 12mm; 2707 display: ${hasFooter ? 'flex' : 'none'} !important; 2708 justify-content: space-between; 2709 align-items: center; 2710 font-size: 10px; 2711 color: #334155; 2712 padding: 0; 2713 background: white !important; 2714 -webkit-print-color-adjust: exact !important; 2715 print-color-adjust: exact !important; 2716 box-sizing: border-box !important; 2717 z-index: 99998 !important; 2718 bottom: 6mm; 2719 border-top: 0.5px solid #f1f5f9; 2720 transform: translateY(var(--footer-offset)); 2721 } 2722 #pdf-export-wrapper.js-paginate .pdf-page-inner { 2723 z-index: 1 !important; 2724 top: ${hasHeader ? '23mm' : '15mm'} !important; 2725 bottom: ${hasFooter ? '23mm' : '15mm'} !important; 2726 } 2727 2728 .pdf-header-logo { 2729 max-height: 11mm !important; 2730 max-width: 80mm !important; 2731 width: auto !important; 2732 height: auto !important; 2733 object-fit: contain !important; 2734 display: block !important; 2735 flex-shrink: 0 !important; 2736 margin: 0; 2737 padding: 0; 2738 } 2739 2740 .pdf-watermark { 2741 position: fixed; 2742 top: 50%; 2743 left: 50%; 2744 transform: translate(-50%, -50%) rotate(-30deg); 2745 font-size: 60pt; 2746 color: rgba(150, 150, 150, 0.1); 2747 z-index: 10 !important; 2748 pointer-events: none; 2749 white-space: nowrap; 2750 font-weight: bold; 2751 text-transform: uppercase; 2752 display: flex !important; 2753 align-items: center; 2754 justify-content: center; 2755 width: 100%; 2756 height: 100%; 2757 -webkit-print-color-adjust: exact !important; 2758 print-color-adjust: exact !important; 2759 } 2760 2761 /* é¡µç æ¾ç¤ºï¼ä½¿ç¨CSSå ç½®çpage counterï¼å®ä¼èªå¨è·è¸ªå½åæå°é¡µç */ 2762 .page-number::after { 2763 content: counter(page); 2764 } 2765 2766 /* ç§»é¤åä½ç pdf-main-container 强å¶è¾¹è·ï¼ç±ä¸é¢çç¹å®è§åå¤ç */ 2767 .pdf-cover-page { 2768 margin: 0 !important; 2769 padding: 0 !important; 2770 page-break-after: always; 2771 } 2772 .pdf-toc-page { 2773 margin: 0 !important; 2774 padding: 0 !important; 2775 page-break-before: always; 2776 } 2777 } 2778 2779 #pdf-export-wrapper .pdf-footer-branding { 2780 margin-top: 50px !important; 2781 padding-top: 20px !important; 2782 border-top: 1px solid #f1f5f9 !important; 2783 text-align: center !important; 2784 font-size: 12px !important; 2785 color: #94a3b8 !important; 2786 page-break-inside: avoid; 2787 } 2788 2789 #pdf-export-wrapper.js-paginate .pdf-main-container { 2790 padding: 0 !important; 2791 margin-top: 0 !important; 2792 margin-bottom: 0 !important; 2793 } 2794 #pdf-export-wrapper.js-paginate .pdf-pages { 2795 position: relative; 2796 width: 210mm; 2797 margin: 0 auto; 2798 } 2799 #pdf-export-wrapper.js-paginate .pdf-page-sheet { 2800 position: relative; 2801 width: 210mm; 2802 height: 296.8mm; /* Reduced slightly to prevent extra blank pages in some browsers */ 2803 page-break-after: always; 2804 break-after: page; 2805 background: white; 2806 box-sizing: border-box; 2807 overflow: hidden; 2808 } 2809 #pdf-export-wrapper.js-paginate .pdf-page-sheet:last-child { 2810 page-break-after: avoid !important; 2811 break-after: avoid !important; 2812 } 2813 #pdf-export-wrapper.js-paginate .pdf-page-inner { 2814 position: absolute; 2815 top: ${hasHeader ? '23mm' : '15mm'}; 2816 left: 10mm; 2817 right: 10mm; 2818 bottom: ${hasFooter ? '23mm' : '15mm'}; 2819 padding: 0; 2820 box-sizing: border-box; 2821 z-index: 1; 2822 } 2823 #pdf-export-wrapper.js-paginate .pdf-page-content { 2824 position: relative; 2825 width: 100%; 2826 height: 100%; 2827 overflow: hidden; 2828 box-sizing: border-box; 2829 } 2830 #pdf-export-wrapper.js-paginate .pdf-page-watermark { 2831 position: absolute; 2832 inset: 0; 2833 transform: rotate(-30deg); 2834 font-size: 60pt; 2835 color: rgba(150, 150, 150, 0.1); 2836 z-index: 5 !important; 2837 pointer-events: none; 2838 white-space: nowrap; 2839 font-weight: bold; 2840 text-transform: uppercase; 2841 display: flex !important; 2842 align-items: center; 2843 justify-content: center; 2844 -webkit-print-color-adjust: exact !important; 2845 print-color-adjust: exact !important; 2846 } 2847 #pdf-export-wrapper.js-paginate .pdf-cover-page, 2848 #pdf-export-wrapper.js-paginate .pdf-toc-page { 2849 page-break-before: auto !important; 2850 page-break-after: auto !important; 2851 break-before: auto !important; 2852 break-after: auto !important; 2853 } 2854 2855 /* å 容å è£ ï¼ç¡®ä¿ä¸è¢«æªæ */ 2856 #pdf-export-wrapper .pdf-content { 2857 position: relative; 2858 z-index: 2; 2859 overflow: visible !important; /* å ³é®ï¼ç¡®ä¿æå°æ¶ä¸æªæ */ 2860 } 2861 2862 /* ä¿®æ£ markdown-body çæ½å¨æªæé®é¢ */ 2863 .markdown-body { 2864 overflow: visible !important; 2865 background: transparent !important; 2866 } 2867 2868 /* å页ä¼å */ 2869 #pdf-export-wrapper .markdown-body p { orphans: 3; widows: 3; } 2870 #pdf-export-wrapper .markdown-body h1, #pdf-export-wrapper .markdown-body h2, #pdf-export-wrapper .markdown-body h3 { break-after: avoid-page; page-break-after: avoid; } 2871 2872 #pdf-export-wrapper h1, #pdf-export-wrapper h2, #pdf-export-wrapper h3, #pdf-export-wrapper h4, #pdf-export-wrapper h5, #pdf-export-wrapper h6 { page-break-after: avoid !important; break-after: avoid !important; } 2873 #pdf-export-wrapper pre, #pdf-export-wrapper blockquote, #pdf-export-wrapper img, #pdf-export-wrapper table, #pdf-export-wrapper .mermaid, #pdf-export-wrapper .admonition, #pdf-export-wrapper .katex-display { 2874 page-break-inside: avoid !important; 2875 break-inside: avoid !important; 2876 margin-bottom: 1.5em; /* 廿 !important 以å 许 JS å¨æè°æ´ç¼©æ¾åçé´è· */ 2877 } 2878 /* å¼ºå¶ Mermaid å KaTeX æ£å¸¸æ¸²æ */ 2879 #pdf-export-wrapper .katex-mathml, #pdf-export-wrapper .katex-error { display: none !important; } 2880 #pdf-export-wrapper .mermaid { 2881 page-break-inside: avoid !important; 2882 break-inside: avoid !important; 2883 margin: 1.5em 0; /* åä¸ï¼å»æ !important */ 2884 width: 100% !important; 2885 text-align: center !important; 2886 transition: none !important; /* é¿å 缩æ¾å¨ç» */ 2887 } 2888 #pdf-export-wrapper .mermaid svg { 2889 max-width: 100% !important; 2890 max-height: 23cm !important; /* éå¶é«åº¦ä»¥éåºå页 A4ï¼é²æ¢æº¢åºæå è¿å¤§å¯¼è´çå页å¼å¸¸ */ 2891 height: auto !important; 2892 width: auto !important; /* å è®¸æ ¹æ®é«åº¦çæ¯ç¼©å° */ 2893 display: block !important; 2894 margin: 0 auto !important; 2895 object-fit: contain !important; 2896 } 2897 #pdf-export-wrapper pre, #pdf-export-wrapper code { 2898 white-space: pre-wrap !important; 2899 word-break: break-all !important; 2900 } 2901 #pdf-export-wrapper img { 2902 max-width: 100% !important; 2903 height: auto !important; 2904 object-fit: contain !important; 2905 } 2906 2907 /* RTL Support for PDF */ 2908 #pdf-export-wrapper.rtl-mode { 2909 text-align: right !important; 2910 direction: rtl !important; 2911 } 2912 #pdf-export-wrapper.rtl-mode .pdf-pages, 2913 #pdf-export-wrapper.rtl-mode .pdf-page-sheet { 2914 direction: rtl !important; 2915 } 2916 #pdf-export-wrapper.rtl-mode .mermaid, 2917 #pdf-export-wrapper.rtl-mode pre, 2918 #pdf-export-wrapper.rtl-mode code, 2919 #pdf-export-wrapper.rtl-mode .katex-display { 2920 direction: ltr !important; 2921 text-align: left !important; 2922 } 2923 #pdf-export-wrapper.rtl-mode .mermaid svg { 2924 margin: 0 auto !important; 2925 } 2926 #pdf-export-wrapper.rtl-mode ul, 2927 #pdf-export-wrapper.rtl-mode ol { 2928 padding-right: 2em !important; 2929 padding-left: 0 !important; 2930 margin-left: 0 !important; 2931 } 2932 #pdf-export-wrapper.rtl-mode .task-list-item { 2933 list-style-type: none !important; 2934 padding-right: 0 !important; 2935 } 2936 #pdf-export-wrapper.rtl-mode .task-list-item-checkbox { 2937 margin: 0 -1.6em 0.25em 0.5em !important; 2938 } 2939 #pdf-export-wrapper.rtl-mode .markdown-body blockquote { 2940 border-left: none !important; 2941 border-right: 0.25em solid #dfe2e5 !
2941important; 2942 padding: 0 1em 0 0 !important; 2943 } 2944 #pdf-export-wrapper.rtl-mode .pdf-header, 2945 #pdf-export-wrapper.rtl-mode .pdf-footer, 2946 #pdf-export-wrapper.rtl-mode .pdf-page-header, 2947 #pdf-export-wrapper.rtl-mode .pdf-page-footer { 2948 direction: rtl !important; 2949 } 2950 /* ä¿®æ£ RTL 模å¼ä¸å¯è½åºç°çæ°´å¹³æº¢åº */ 2951 #pdf-export-wrapper.rtl-mode .pdf-page-inner { 2952 left: auto !important; 2953 right: 10mm !important; 2954 width: 190mm !important; 2955 } 2956 #pdf-export-wrapper.rtl-mode .pdf-content { 2957 width: 100% !important; 2958 } 2959 2960 * { box-sizing: border-box !important; } 2961 `; 2962 2963 container.appendChild(styleTag); 2964 2965 // æé 页ç页è DOM (åç¦»åº main-container 以é¿å æ ·å¼å¹²æ°) 2966 const headerHtml = ` 2967 <div class="pdf-header"> 2968 <div style="flex: 1; height: 100%; display: flex; align-items: center; gap: 8px; justify-content: flex-start; overflow: hidden;"> 2969 ${logoLeft ? `<img src="${logoLeft}" class="pdf-header-logo">` : `<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${hLeft}</span>`} 2970 </div> 2971 <div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden;"> 2972 <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${hCenter}</span> 2973 </div> 2974 <div style="flex: 1; height: 100%; display: flex; align-items: center; gap: 8px; justify-content: flex-end; overflow: hidden;"> 2975 ${logoRight ? `<img src="${logoRight}" class="pdf-header-logo">` : `<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${hRight}</span>`} 2976 </div> 2977 </div>`; 2978 2979 const footerHtml = ` 2980 <div class="pdf-footer"> 2981 <div style="flex: 1; display: flex; align-items: center; justify-content: flex-start; overflow: hidden;"> 2982 <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${fLeft}</span> 2983 </div> 2984 <div style="flex: 1; display: flex; align-items: center; justify-content: center; overflow: hidden;"> 2985 <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${fCenter}</span> 2986 </div> 2987 <div style="flex: 1; display: flex; align-items: center; justify-content: flex-end; overflow: hidden;"> 2988 <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${fRight}</span> 2989 </div> 2990 </div>`; 2991 2992 // æå ¥æ°´å° (Fixed) 2993 if (watermarkText) { 2994 container.insertAdjacentHTML('beforeend', `<div class="pdf-watermark">${watermarkText}</div>`); 2995 } 2996 2997 // æå ¥é¡µç (Fixed) 2998 if (hasHeader) { 2999 container.insertAdjacentHTML('beforeend', headerHtml); 3000 } 3001 3002 // æå ¥é¡µè (Fixed) - å¿ é¡»æ¾å¨ content wrapper ä¹åï¼ä»¥ç¡®ä¿å¨å¤é¡µææ¡£ä¸çæ£ç¡®æ¸²æå±çº§åä½ç½® 3003 if (hasFooter) { 3004 container.insertAdjacentHTML('beforeend', footerHtml); 3005 } 3006 3007 const wrapper = document.createElement('div'); 3008 wrapper.className = 'pdf-main-container'; 3009 wrapper.innerHTML = ` 3010 ${coverHtml} 3011 ${tocHtml} 3012 <div class="pdf-content markdown-body">${clone.innerHTML}</div> 3013 ${canRemoveBranding ? '' : `<div class="pdf-footer-branding"> 3014 ${dict.pdfBranding || 'The Professional AI Artifacts to PDF Engine'} - <a href="https://md2pdf.cc" target="_blank" style="color: #7c3aed; text-decoration: none; font-weight: 600; margin-left: 4px;">md2pdf.cc</a> 3015 </div>`} 3016 `; 3017 container.appendChild(wrapper); 3018 3019 // æå ¥é¡µè (Moved above) 3020 // container.insertAdjacentHTML('beforeend', footerHtml); 3021 3022 document.body.appendChild(container); 3023 3024 // ç»äºæµè§å¨ä¸å°æ®µæ¶é´è¿è¡åæ¥å¸å±è®¡ç®ï¼é²æ¢ Mermaid æ¸²ææ¶åæ è®¡ç®åºé (NaN) 3025 await new Promise(resolve => setTimeout(resolve, 100)); 3026 3027 // 为导åºå®¹å¨å ç Mermaid å¾è¡¨è¿è¡æ¸²æ 3028 if (window.mermaid) { 3029 try { 3030 const mermaidNodes = container.querySelectorAll('.mermaid'); 3031 if (mermaidNodes.length > 0) { 3032 // 对äºå¯¼åºï¼æä»¬éè¦ç¡®ä¿å®ç«å³æ¸²æ 3033 await mermaid.run({ 3034 nodes: mermaidNodes, 3035 }); 3036 3037 // æºè½åé¡µç®æ³ï¼é²æ¢ Mermaid å¾è¡¨é æè¿å¤§ç页é¢çç½ 3038 // é»è¾ï¼å¦æå¾è¡¨ä¼è¢«æ¨å°ä¸ä¸é¡µï¼ä¸å½å页å©ä½ç©ºé´ > 1/3ï¼å缩å°å¾è¡¨ä½¿å ¶çå¨å½å页 3039 const PAGE_HEIGHT_PX = 980; // A4 约 260mm çåç´ å¼
3040 mermaidNodes.forEach(node => { 3041 const rect = node.getBoundingClientRect(); 3042 const wrapperRect = container.getBoundingClientRect(); 3043 const topRelativeToWrapper = rect.top - wrapperRect.top; 3044 3045 const pageIndex = Math.floor(topRelativeToWrapper / PAGE_HEIGHT_PX); 3046 const pageBottom = (pageIndex + 1) * PAGE_HEIGHT_PX; 3047 const remainingSpace = pageBottom - topRelativeToWrapper; 3048 const elementHeight = rect.height; 3049 3050 // 妿å ç´ é«åº¦å¤§äºå©ä½ç©ºé´ï¼å³ä¼è¢«æ¨å°ä¸ä¸é¡µï¼ 3051 // ä¸å½åå©ä½ç©ºé´è¶ è¿é¡µé¢é«åº¦ç 1/3 3052 if (elementHeight > remainingSpace && remainingSpace > (PAGE_HEIGHT_PX / 3)) { 3053 // ä¼åï¼æ£æ¥åç»å ç´ ï¼å¦æç¼©æ¾æ¯ä¾å¨å¯æ¥åèå´å (>=0.45)ï¼å°½éæåç»å 容乿䏿¥ 3054 let extraHeightNeeded = 0; 3055 const nextEl = node.nextElementSibling; 3056 if (nextEl) { 3057 // å³ä½¿ä¸æ¯æ é¢ï¼å¦æåç»å ç´ è¾å°ï¼ä¹å°è¯ä¸èµ·æä¸æ¥ 3058 extraHeightNeeded = Math.min(nextEl.offsetHeight, 100) + 20; 3059 } 3060 3061 // éæ°è®¡ç®ç¼©æ¾æ¯ä¾ï¼å°è¯çåºç©ºé´ç»åç»å ç´ 3062 let scale = (remainingSpace - 40 - extraHeightNeeded) / elementHeight; 3063 3064 // å¦æä¸ºäºå®¹çº³åç»å 容导è´ç¼©æ¾å¤ªä¸¥éï¼åä¼å ä¿è¯å¾è¡¨æ¬èº«å¤§å°ï¼ä» å缩å¾è¡¨å°æ¬é¡µæ«å°¾ 3065 if (scale < 0.45) { 3066 scale = (remainingSpace - 30) / elementHeight; 3067 } 3068 3069 if (scale > 0.4) { // ç¼©æ¾æ¯ä¾åºçº¿ 3070 node.style.transform = `scale(${scale})`; 3071 node.style.transformOrigin = 'top center'; 3072 // å ³é®ï¼ä¿®æ¹å®é å¸å±é«åº¦ï¼è®©å页弿æç¥å°ç¼©æ¾åç尺寸 3073 // 使ç¨ç¨å¾®å¤§ä¸ç¹çå移补å¿ï¼ç¡®ä¿å 容紧å 3074 node.style.height = `${elementHeight * scale}px`; 3075 3076 // 强å¶å 许å¨è¯¥å ç´ ä½ç½®è¿è¡åé¡µè®¡ç® 3077 node.style.setProperty('break-inside', 'auto', 'important'); 3078 node.style.setProperty('page-break-inside', 'auto', 'important'); 3079 3080 node.style.marginBottom = '5px'; 3081 node.style.marginTop = '5px'; 3082 node.style.overflow = 'visible'; 3083 } 3084 } else if (elementHeight <= remainingSpace) { 3085 // å¦ææ¬æ¥å°±è½æ¾ä¸ï¼ç¡®ä¿æ¢å¤é»è®¤ç avoid é»è¾ 3086 node.style.transform = ''; 3087 node.style.height = 'auto'; 3088 node.style.setProperty('break-inside', 'avoid', 'important'); 3089 node.style.setProperty('page-break-inside', 'avoid', 'important'); 3090 node.style.marginTop = ''; 3091 node.style.marginBottom = ''; 3092 } 3093 }); 3094 } 3095 } catch (err) { 3096 console.error("Mermaid export render error:", err); 3097 } 3098 } 3099 3100 try { 3101 // ç»ç§»å¨ç«¯çåºæ´å¤çæ¸²ææ¶é´ 3102 const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 3103 await new Promise(resolve => setTimeout(resolve, isMobile ? 2500 : 1500)); 3104 3105 const mmToPx = (mm) => { 3106 const probe = document.createElement('div'); 3107 probe.style.height = `${mm}mm`; 3108 probe.style.width = '1mm'; 3109 probe.style.position = 'absolute'; 3110 probe.style.visibility = 'hidden'; 3111 document.body.appendChild(probe); 3112 const px = probe.getBoundingClientRect().height; 3113 probe.remove(); 3114 return px; 3115 }; 3116 3117 const getPageMetrics = () => { 3118 const pageProbe = document.createElement('div'); 3119 pageProbe.style.width = '210mm'; 3120 pageProbe.style.height = '297mm'; 3121 pageProbe.style.position = 'absolute'; 3122 pageProbe.style.visibility = 'hidden'; 3123 document.body.appendChild(pageProbe); 3124 3125 const rect = pageProbe.getBoundingClientRect(); 3126 pageProbe.remove(); 3127 3128 const pageHeight = rect.height || mmToPx(297); 3129 const headerFooterHeight = pageHeight * (12 / 297); 3130 const contentHeight = pageHeight * (270 / 297); 3131 3132 return { 3133 pageHeight, 3134 headerFooterHeight, 3135 contentHeight 3136 }; 3137 }; 3138 3139 // è®¡ç®æ»é¡µæ° 3140 // A4 页é¢é«åº¦çº¦ä¸º 297mmï¼åå»ä¸ä¸è¾¹è· (10mm + 17mm)ï¼å 容åºåé«åº¦çº¦ä¸º 270mm 3141 // 转æ¢ä¸ºåç´ ï¼ä½¿ç¨ A4 页颿¢éè·åç宿¸²ææ¯ä¾ 3142 const pageMetrics = getPageMetrics(); 3143 const PAGE_MM = { 3144 width: 210, 3145 height: 296.8, /* Changed from 297 for printing safety */ 3146 marginTop: 10, 3147 marginBottom: 0, 3148 marginLeft: 10, 3149 marginRight: 10, 3150 headerFooter: 12, 3151 gap: 3 3152 }; 3153 const CONTENT_GAP_PX = mmToPx(PAGE_MM.gap); 3154 const MARGIN_TOP_PX = mmToPx(PAGE_MM.marginTop); 3155 const MARGIN_RIGHT_PX = mmToPx(PAGE_MM.marginRight); 3156 const MARGIN_BOTTOM_PX = mmToPx(PAGE_MM.marginBottom); 3157 const MARGIN_LEFT_PX = mmToPx(PAGE_MM.marginLeft); 3158 const PAGE_HEIGHT_PX = pageMetrics.pageHeight; 3159 const HEADER_FOOTER_HEIGHT_PX = pageMetrics.headerFooterHeight; 3160 3161 // å¨æè®¡ç®å 容åºåé«åº¦ï¼å¦ææ²¡æé¡µçæé¡µèï¼åçåºæ´å°ç©ºé´ 3162 const pt_px = hasHeader ? mmToPx(23) : mmToPx(15); 3163 const pb_px = hasFooter ? mmToPx(23) : mmToPx(15); 3164 3165 const PAGE_CONTENT_HEIGHT = Math.max( 3166 1, 3167 PAGE_HEIGHT_PX - pt_px - pb_px 3168 ); 3169 const containerHeight = container.offsetHeight; 3170 let totalPages = Math.max(1, Math.ceil(containerHeight / PAGE_CONTENT_HEIGHT)); 3171 3172 // æ¿æ¢ææçæ»é¡µæ°å ä½ç¬¦ 3173 const pageCountElements = container.querySelectorAll('.page-count');
3174 pageCountElements.forEach(el => { 3175 el.textContent = totalPages.toString(); 3176 }); 3177 3178 const pageMacroSource = [ 3179 exportSettings['export-header-left'], 3180 exportSettings['export-header-center'], 3181 exportSettings['export-header-right'], 3182 exportSettings['export-footer-left'], 3183 exportSettings['export-footer-center'], 3184 exportSettings['export-footer-right'] 3185 ].join(' '); 3186 3187 const hasPageMacros = /\{pagenum\}|\{totalpages\}/.test(pageMacroSource); 3188 3189 // å§ç»å¼å¯ JS å页模å¼ä»¥ç¡®ä¿å¸å±ä¸è´æ§ï¼é²æ¢å 容ä¸é¡µç页èéå ï¼å¹¶å¯ç¨é²æªæé»è¾ 3190 const useJsPaginate = true; 3191 3192 const replacePageMacros = (str, pageNum, pageTotal) => { 3193 if (!str) return ''; 3194 return str 3195 .replace(/{date}/g, dateStr) 3196 .replace(/{title}/g, file.name || 'document') 3197 .replace(/{pagenum}/g, String(pageNum)) 3198 .replace(/{totalpages}/g, String(pageTotal)); 3199 }; 3200 3201 if (useJsPaginate) { 3202 container.classList.add('js-paginate'); 3203 3204 const wrapperEl = container.querySelector('.pdf-main-container'); 3205 const contentEl = wrapperEl ? wrapperEl.querySelector('.pdf-content') : null; 3206 const brandingEl = wrapperEl ? wrapperEl.querySelector('.pdf-footer-branding') : null; 3207 const coverEl = wrapperEl ? wrapperEl.querySelector('.pdf-cover-page') : null; 3208 const tocEl = wrapperEl ? wrapperEl.querySelector('.pdf-toc-page') : null; 3209 const pagesEl = document.createElement('div'); 3210 pagesEl.className = 'pdf-pages'; 3211 3212 if (contentEl && wrapperEl) { 3213 const createNodeFromHtml = (html) => { 3214 if (!html) return null; 3215 const wrapper = document.createElement('div'); 3216 wrapper.innerHTML = html; 3217 return wrapper.firstElementChild || null; 3218 }; 3219 3220 // æå"æ´ä½é«åº¦å·²è¶ è¿å页å¯ç¨é«åº¦"çè¡¨æ ¼ï¼ 3221 // å ¨å± CSS 对 table å¼ºå¶ page-break-inside: avoid !importantï¼é²æ¢è¡¨æ ¼å é¨éææè¡ï¼ï¼ 3222 // ä½å½è¡¨æ ¼æ¬èº«ï¼å¦ 7.1 åæ®µè§èè¡¨ï¼æ¸²æé«åº¦å°±è¶ è¿æ´é¡µå 容åºåæ¶ï¼æµè§å¨åçæå°å页 3223 // æ æ³å¨âä¸å¯æåâåâæ¾ä¸ä¸ä¸æ´é¡µâä¹é´æ¾å°è§£ï¼ä¼å¯¼è´è¯¥è¡¨æ ¼è¢«ç´æ¥ä¸¢å¼ãè¾åºç©ºç½é¡µã 3224 // è¿é卿µéé¶æ®µæè¡æå为å¤ä¸ªæ´å°çè¡¨æ ¼ï¼æ¯ä¸ªé½å¤å¶è¡¨å¤´ï¼ï¼äº¤ç±ä¸æ¹éèç¹å页é»è¾ 3225 // æ£å¸¸å°ææ¯ä¸å°æ®µåé å°åéç页é¢ã 3226 const splitOversizedTable = (table, hostEl, maxHeight) => { 3227 if (!table || table.tagName !== 'TABLE') return [table]; 3228 if (table.offsetHeight <= maxHeight - 4) return [table]; 3229 3230 const thead = table.querySelector(':scope > thead'); 3231 const tbody = table.querySelector(':scope > tbody'); 3232 if (!tbody || tbody.children.length <= 1) return [table]; 3233 3234 const scratch = document.createElement('div'); 3235 scratch.style.position = 'absolute'; 3236 scratch.style.visibility = 'hidden'; 3237 scratch.style.pointerEvents = 'none'; 3238 scratch.style.width = `${table.offsetWidth || table.getBoundingClientRect().width}px`; 3239 hostEl.appendChild(scratch); 3240 3241 const makeShell = () => { 3242 const t = table.cloneNode(false); 3243 if (thead) t.appendChild(thead.cloneNode(true)); 3244 const tb = document.createElement('tbody'); 3245 t.appendChild(tb); 3246 return { t, tb }; 3247 }; 3248 3249 const rows = Array.from(tbody.children); 3250 const chunks = []; 3251 let shell = makeShell(); 3252 scratch.innerHTML = ''; 3253 scratch.appendChild(shell.t); 3254 3255 rows.forEach(row => { 3256 shell.tb.appendChild(row.cloneNode(true)); 3257 if (scratch.offsetHeight > maxHeight - 4 && shell.tb.children.length > 1) { 3258 const overflowRow = shell.tb.lastElementChild; 3259 shell.tb.removeChild(overflowRow); 3260 chunks.push(shell.t); 3261 shell = makeShell(); 3262 scratch.innerHTML = ''; 3263 scratch.appendChild(shell.t); 3264 shell.tb.appendChild(overflowRow); 3265 } 3266 }); 3267 chunks.push(shell.t); 3268 scratch.remove(); 3269 3270 // è¥æåæªçæï¼ä¾å¦åè¡æ¬èº«å°±è¶ è¿æ´é¡µé«åº¦ï¼ï¼åé为åè¡¨æ ¼ï¼äº¤ç±åç»å页é»è¾å åºå¤ç 3271 return chunks.length > 1 ? chunks : [table]; 3272 }; 3273 3274 const flowNodes = [];
3275 const coverNode = coverEl || createNodeFromHtml(coverHtml); 3276 const tocNode = tocEl || createNodeFromHtml(tocHtml); 3277 if (coverNode) flowNodes.push(coverNode); 3278 if (tocNode) flowNodes.push(tocNode); 3279 flowNodes.push(...Array.from(contentEl.childNodes).filter(node => { 3280 if (node.nodeType === Node.TEXT_NODE) { 3281 return node.textContent && node.textContent.trim().length > 0; 3282 } 3283 return true; 3284 }).flatMap(node => { 3285 if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'TABLE') { 3286 return splitOversizedTable(node, contentEl, PAGE_CONTENT_HEIGHT); 3287 } 3288 return [node]; 3289 })); 3290 if (brandingEl) flowNodes.push(brandingEl); 3291 3292 contentEl.remove(); 3293 if (brandingEl && brandingEl.parentNode === wrapperEl) brandingEl.remove(); 3294 if (coverEl && coverEl.parentNode === wrapperEl) coverEl.remove(); 3295 if (tocEl && tocEl.parentNode === wrapperEl) tocEl.remove(); 3296 3297 wrapperEl.appendChild(pagesEl); 3298 3299 const isFullPageNode = (node) => { 3300 if (!node || node.nodeType !== Node.ELEMENT_NODE) return false; 3301 return node.classList.contains('pdf-cover-page') || node.classList.contains('pdf-toc-page'); 3302 }; 3303 3304 const createPage = (options = {}) => { 3305 const allowOverflow = options.allowOverflow || false; 3306 const page = document.createElement('div'); 3307 page.className = 'pdf-page-sheet'; 3308 page.style.width = `${PAGE_MM.width}mm`; 3309 page.style.height = `${PAGE_MM.height}mm`; 3310 3311 const inner = document.createElement('div'); 3312 inner.className = 'pdf-page-inner'; 3313 3314 const content = document.createElement('div'); 3315 content.className = 'pdf-page-content markdown-body'; 3316 content.style.position = 'absolute'; 3317 content.style.top = '0'; 3318 content.style.left = '0'; 3319 content.style.right = '0'; 3320 content.style.bottom = '0'; 3321 content.style.width = '100%'; 3322 content.style.height = '100%'; 3323 content.style.padding = '0'; 3324 content.style.margin = '0'; 3325 content.style.boxSizing = 'border-box'; 3326 content.style.overflow = allowOverflow ? 'visible' : 'hidden'; 3327 content.style.overflowX = 'hidden'; 3328 content.style.direction = 'inherit'; 3329 content.style.textAlign = 'inherit'; 3330 3331 inner.appendChild(content); 3332 page.appendChild(inner); 3333 pagesEl.appendChild(page); 3334 return { page, inner, content }; 3335 }; 3336 3337 let current = null; 3338 flowNodes.forEach(node => { 3339 if (isFullPageNode(node)) { 3340 const fullPage = createPage({ allowOverflow: true }); 3341 fullPage.content.appendChild(node); 3342 current = null; 3343 return; 3344 } 3345 3346 if (!current) current = createPage(); 3347 3348 current.content.appendChild(node); 3349 // å½ clientHeight 为 0 æ¶ï¼ç§»å¨ç«¯ iOS Safari å¯è½å 渲æä¼åä¸è®¡ç®é«åº¦ï¼ï¼ 3350 // 使ç¨é¢å æµé好ç PAGE_CONTENT_HEIGHT ä½ä¸ºå åºï¼é¿å å页é»è¾å¤±æå¯¼è´ç¼ºé¡µã 3351 const effectiveClientHeight = current.content.clientHeight > 10 3352 ? current.content.clientHeight 3353 : PAGE_CONTENT_HEIGHT; 3354 if (current.content.scrollHeight > effectiveClientHeight + 1) { 3355 current.content.removeChild(node); 3356 if (current.content.childNodes.length === 0) { 3357 current.content.appendChild(node); 3358 } else { 3359 current = createPage(); 3360 current.content.appendChild(node); 3361 } 3362 } 3363 }); 3364 3365 totalPages = pagesEl.children.length || totalPages; 3366 3367 const buildHeaderHtml = (pageNum) => { 3368 const left = replacePageMacros(exportSettings['export-header-left'], pageNum, totalPages); 3369 const center = replacePageMacros(exportSettings['export-header-center'], pageNum, totalPages); 3370 const right = replacePageMacros(exportSettings['export-header-right'], pageNum, totalPages); 3371 return ` 3372 <div class="pdf-page-header"> 3373 <div style="flex: 1; height: 100%; display: flex; align-items: center; gap: 8px; justify-content: flex-start; overflow: hidden;"> 3374 ${logoLeft ? `<img src="${logoLeft}" class="pdf-header-logo">` : `<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${left}</span>`} 3375 </div> 3376 <div style="flex: 1; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden;">
3377 <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${center}</span> 3378 </div> 3379 <div style="flex: 1; height: 100%; display: flex; align-items: center; gap: 8px; justify-content: flex-end; overflow: hidden;"> 3380 ${logoRight ? `<img src="${logoRight}" class="pdf-header-logo">` : `<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${right}</span>`} 3381 </div> 3382 </div>`; 3383 }; 3384 3385 const buildFooterHtml = (pageNum) => { 3386 const left = replacePageMacros(exportSettings['export-footer-left'], pageNum, totalPages); 3387 const center = replacePageMacros(exportSettings['export-footer-center'], pageNum, totalPages); 3388 const right = replacePageMacros(exportSettings['export-footer-right'], pageNum, totalPages); 3389 return ` 3390 <div class="pdf-page-footer"> 3391 <div style="flex: 1; display: flex; align-items: center; justify-content: flex-start; overflow: hidden;"> 3392 <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${left}</span> 3393 </div> 3394 <div style="flex: 1; display: flex; align-items: center; justify-content: center; overflow: hidden;"> 3395 <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${center}</span> 3396 </div> 3397 <div style="flex: 1; display: flex; align-items: center; justify-content: flex-end; overflow: hidden;"> 3398 <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${right}</span> 3399 </div> 3400 </div>`; 3401 }; 3402 3403 Array.from(pagesEl.children).forEach((page, index) => { 3404 const pageNum = index + 1; 3405 const pageInner = page.querySelector('.pdf-page-inner'); 3406 if (watermarkText) { 3407 const watermark = document.createElement('div'); 3408 watermark.className = 'pdf-page-watermark'; 3409 watermark.textContent = watermarkText; 3410 page.appendChild(watermark); 3411 } 3412 3413 if (hasHeader) { 3414 const header = document.createElement('div'); 3415 header.innerHTML = buildHeaderHtml(pageNum); 3416 const headerNode = header.firstElementChild; 3417 page.appendChild(headerNode); 3418 } 3419 3420 if (hasFooter) { 3421 const footer = document.createElement('div'); 3422 footer.innerHTML = buildFooterHtml(pageNum); 3423 const footerNode = footer.firstElementChild; 3424 page.appendChild(footerNode); 3425 } 3426 }); 3427 } 3428 } 3429 3430 pageCountElements.forEach(el => { 3431 el.textContent = totalPages.toString(); 3432 }); 3433 3434 const pageMarginCss = '0'; // Always 0, controlled by internal padding 3435 3436 const printStyle = document.createElement('style'); 3437 printStyle.id = 'pdf-print-style'; 3438 3439 // å¨æè¾¹è·ä¸æ¾ç¤ºé»è¾ 3440 const pt_css = hasHeader ? "23mm" : "15mm"; 3441 const pb_css = hasFooter ? "23mm" : "15mm"; 3442 const displayHeader = hasHeader ? "flex" : "none"; 3443 const displayFooter = hasFooter ? "flex" : "none"; 3444 3445 printStyle.textContent = ` 3446 @media print { 3447 html, body { 3448 height: auto !important; 3449 overflow: visible !important; 3450 margin: 0 !important; /* Reset browser default */ 3451 padding: 0 !important; 3452 } 3453 @page { 3454 margin: ${pageMarginCss}; 3455 size: A4; 3456 counter-increment: page; 3457 } 3458 3459 /* Explicitly reset page counter to avoid 0 on first page */ 3460 body { 3461 counter-reset: page 0; 3462 } 3463 3464 /* Hide everything else */ 3465 body > *:not(#pdf-export-wrapper) { 3466 display: none !important; 3467 } 3468 3469 /* Wrapper configuration */ 3470 #pdf-export-wrapper { 3471 position: absolute !important; 3472 top: 0 !important; 3473 left: 0 !important; 3474 right: 0 !important; 3475 width: 100% !important; 3476 margin: 0 !important; 3477 padding: 0 !important; 3478 display: block !important; 3479 visibility: visible !important; 3480 clip-path: none !important; 3481 overflow: visible !important; 3482 transform: none !important; 3483 z-index: 9999 !important; 3484 } 3485 3486 #pdf-export-wrapper.js-paginate { 3487 width: 210mm !important; 3488 margin: 0 auto !important; 3489 --footer-offset: 0; 3490 } 3491 3492 /* Main Content Container */ 3493 #pdf-export-wrapper .pdf-main-container { 3494 width: 100% !important; 3495 box-shadow: none !important; 3496 position: relative !important; 3497 transform: none !important; 3498 z-index: 10; 3499 margin-top: 0 !important; 3500 margin-bottom: 0 !important; 3501 padding: ${pt_css} 15mm ${pb_css} 15mm !important; /* Visual spacing for non-JS paginate case */ 3502 box-sizing: border-box !important; 3503 } 3504 3505 /* Header & Footer: FIXED POSITION IS KEY */ 3506 #pdf-export-wrapper .pdf-header { 3507 display: ${displayHeader} !important; 3508 opacity: 1 !important; 3509 visibility: visible !important; 3510 position: fixed !important; /* Must be fixed */ 3511 z-index: 200 !important; /* Topmost */ 3512 background-color: white !important; 3513 color: #334155 !important; 3514 left: 0 !important; 3515 right: 0 !important; 3516 height: 12mm !important; 3517 padding: 0 15mm !important; 3518 box-sizing: border-box !important; 3519 top: 6mm !important; 3520 border-bottom: 0.5px solid #e2e8f0; 3521 } 3522 3523 #pdf-export-wrapper .pdf-footer { 3524 display: ${displayFooter} !important; 3525 opacity: 1 !important; 3526 visibility: visible !important; 3527 position: fixed !important; /* Must be fixed */ 3528 z-index: 200 !important; /* Topmost */ 3529 background-color: white !important; 3530 color: #334155 !important; 3531 left: 0 !important; 3532 right: 0 !important; 3533 height: 12mm !important; 3534 padding: 0 15mm !important; 3535 box-sizing: border-box !important; 3536 bottom: 6mm !important; 3537 border-top: 0.5px solid #e2e8f0; 3538 } 3539 3540 /* JS å页æ¶éèåºå®é¡µç页èï¼æ¾ç¤ºæ¯é¡µå®ä½ç页ç页è */ 3541 #pdf-export-wrapper.js-paginate .pdf-header, 3542 #pdf-export-wrapper.js-paginate .pdf-footer { 3543 display: none !important; 3544 } 3545 #pdf-export-wrapper.js-paginate .pdf-page-header { 3546 display: ${displayHeader} !important; 3547 position: absolute !important; 3548 left: 10mm !important; 3549 right: 10mm !important; 3550 height: 12mm !important; 3551 padding: 0 !important; 3552 box-sizing: border-box !important; 3553 background-color: white !important; 3554 z-index: 200 !important; 3555 top: 6mm !important; 3556 transform: none !important; 3557 border-bottom: 0.5px solid #e2e8f0; 3558 } 3559 #pdf-export-wrapper.js-paginate .pdf-page-footer { 3560 display: ${displayFooter} !important; 3561 position: absolute !important; 3562 left: 10mm !important; 3563 right: 10mm !important; 3564 height: 12mm !important; 3565 padding: 0 !important; 3566 box-sizing: border-box !important; 3567 background-color: white !important; 3568 z-index: 200 !important; 3569 bottom: 6mm !important; 3570 border-top: 0.5px solid #e2e8f0; 3571 transform: translateY(var(--footer-offset)); 3572 } 3573 #pdf-export-wrapper.js-paginate .pdf-page-inner { 3574 z-index: 1 !important; 3575 top: ${pt_css} !important; 3576 bottom: ${pb_css} !important; 3577 } 3578 3579 /* Override margins for JS pagination to prevent blank pages */ 3580 #pdf-export-wrapper.js-paginate .pdf-main-container { 3581 margin-top: 0 !important; 3582 margin-bottom: 0 !important; 3583 padding: 0 !important; 3584 } 3585
3586 /* Page Number Counter */ 3587 .page-number::after { 3588 content: counter(page); 3589 } 3590 `; 3591 document.head.appendChild(printStyle); 3592 3593 const originalTitle = document.title; 3594 document.title = file.name || 'document'; 3595 3596 // æ¾ç¤ºæå°æç¤º 3597 const hasCustomHeader = exportSettings['export-header-left'] || exportSettings['export-header-center'] || exportSettings['export-header-right']; 3598 const hasCustomFooter = exportSettings['export-footer-left'] || exportSettings['export-footer-center'] || exportSettings['export-footer-right']; 3599 if (hasCustomHeader || hasCustomFooter) { 3600 // å建ä¸ä¸ªä¸´æ¶æç¤ºï¼å¨æå°å¯¹è¯æ¡æå¼åèªå¨åºç° 3601 setTimeout(() => { 3602 const toastContainer = document.getElementById('toast-container'); 3603 if (toastContainer) { 3604 const toast = document.createElement('div'); 3605 toast.className = 'toast toast-info'; 3606 toast.innerHTML = `<i class="fas fa-info-circle"></i><span>${dict.printDialogNote || 'Please disable browser headers/footers in print dialog'}</span>`; 3607 toastContainer.appendChild(toast); 3608 setTimeout(() => toast.classList.add('show'), 10); 3609 setTimeout(() => { 3610 toast.classList.remove('show'); 3611 setTimeout(() => toast.remove(), 300); 3612 }, 5000); 3613 } 3614 }, 500); 3615 } 3616 3617 window.print(); 3618 3619 // æ¸ çé»è¾ï¼æ¢å¤æ é¢ãç§»é¤æå°æ ·å¼åå®¹å¨ 3620 let cleanupDone = false; 3621 const cleanupExport = () => { 3622 if (cleanupDone) return; 3623 cleanupDone = true; 3624 window.removeEventListener('afterprint', cleanupExport); 3625 document.removeEventListener('visibilitychange', onVisibilityChange); 3626 document.title = originalTitle; 3627 const ps = document.getElementById('pdf-print-style'); 3628 if (ps) ps.remove(); 3629 if (container.parentNode) document.body.removeChild(container); 3630 document.body.classList.remove('is-exporting'); 3631 if (exportBtn) { 3632 exportBtn.style.opacity = '1'; 3633 exportBtn.innerHTML = originalBtnHtml; 3634 } 3635 this.isExporting = false; 3636 }; 3637 3638 // iOS Safari ç afterprint 卿å°é¢æ¿å¼¹åºæ¶ç«å³è§¦åï¼èéç¨æ·ä¿ååï¼ï¼ 3639 // è¥æ¤æ¶æ¢å¤ document.titleï¼PDF æä»¶åä¼ååç½é¡µæ é¢ã 3640 // ä¿®å¤ï¼ç§»å¨ç«¯æ¹ç¨ visibilitychangeï¼æå°é¢æ¿å¼¹åºæ¶é¡µé¢å hiddenï¼ 3641 // ç¨æ·å ³é颿¿ï¼ä¿å/åæ¶ï¼å页é¢å visibleï¼æ¤æ¶ PDF å·²è½çï¼åæ¢å¤æ é¢ã 3642 const onVisibilityChange = () => { 3643 if (document.visibilityState === 'visible') { 3644 document.removeEventListener('visibilitychange', onVisibilityChange); 3645 setTimeout(cleanupExport, 300); 3646 } 3647 }; 3648 3649 if (isMobile) { 3650 // ç§»å¨ç«¯ï¼ç页é¢éæ°å¯è§åæ¸ çï¼æå°é¢æ¿å ³éåï¼ 3651 document.addEventListener('visibilitychange', onVisibilityChange); 3652 // å åºï¼30s åå¼ºå¶æ¸ çï¼visibilitychange ä¸è§¦åçæç«¯æ åµï¼ 3653 setTimeout(cleanupExport, 30000); 3654 } else { 3655 // æ¡é¢ç«¯ï¼afterprint å¯é å°å¨æå°å®æå触å 3656 window.addEventListener('afterprint', cleanupExport); 3657 setTimeout(cleanupExport, 3000); 3658 } 3659 3660 } catch (err) { 3661 console.error("PDF Export Fail", err); 3662 uiActions.showToast(dict.exportFail, 'error'); 3663 this.isExporting = false; 3664 if (container.parentNode) document.body.removeChild(container); 3665 document.body.classList.remove('is-exporting'); 3666 } 3667 },
3668 // ä¸ºä¿æååå ¼å®¹ï¼ä¿ç exportPDF å«å 3669 async exportPDF() { 3670 return this.performPdfExport(); 3671 } 3672}; 3673 3674 3675/** 3676 * FEEDBACK HANDLER 3677 */ 3678const feedbackHandler = { 3679 selectedRating: 0, 3680 3681 openModal() { 3682 this.selectedRating = 0; 3683 // Reset form 3684 const msgEl = document.getElementById('feedbackMessage'); 3685 const emailEl = document.getElementById('feedbackEmail'); 3686 const subscribeEl = document.getElementById('feedbackSubscribe'); 3687 const subRow = document.getElementById('feedbackSubscribeRow'); 3688 const errEl = document.getElementById('feedbackError'); 3689 const formBody = document.getElementById('feedbackFormBody'); 3690 const successBody = document.getElementById('feedbackSuccessBody'); 3691 const footer = document.getElementById('feedbackFooter'); 3692 3693 if (msgEl) msgEl.value = ''; 3694 if (emailEl) emailEl.value = ''; 3695 if (subscribeEl) subscribeEl.checked = true; 3696 if (subRow) subRow.classList.remove('hidden'); 3697 if (errEl) errEl.classList.add('hidden'); 3698 if (formBody) formBody.classList.remove('hidden'); 3699 if (successBody) successBody.classList.add('hidden'); 3700 // Always restore footer to original buttons (submit may have replaced it with just OK) 3701 if (footer) { 3702 const dict = langMap[getLang()] || langMap['en']; 3703 footer.innerHTML = ` 3704 <button onclick="feedbackHandler.closeModal()" class="h-8 px-3 rounded-md text-xs font-medium border border-input bg-background hover:bg-accent">${dict.modalCancel || 'Cancel'}</button> 3705 <button onclick="feedbackHandler.submit()" id="feedbackSubmitBtn" class="h-8 px-4 rounded-md text-xs font-bold bg-primary text-primary-foreground hover:opacity-90 active:scale-95 shadow-lg shadow-primary/20 min-w-[100px]"> 3706 <span>${dict.feedbackSubmitBtn || 'Send Feedback'}</span> 3707 </button>`; 3708 } 3709 3710 // Reset emoji rating buttons 3711 document.querySelectorAll('.feedback-emoji-btn').forEach(btn => btn.classList.remove('selected')); 3712 3713 uiActions.openModal('feedbackModal'); 3714 }, 3715 3716 closeModal() { 3717 uiActions.closeModal('feedbackModal'); 3718 }, 3719 3720 setRating(value) { 3721 this.selectedRating = value; 3722 document.querySelectorAll('.feedback-emoji-btn').forEach(btn => { 3723 btn.classList.toggle('selected', parseInt(btn.dataset.rating) === value); 3724 }); 3725 }, 3726 3727 onEmailInput() { 3728 // Email is now required; subscribe row is always visible 3729 }, 3730 3731 submit() { 3732 const dict = langMap[getLang()] || langMap['en']; 3733 const msgEl = document.getElementById('feedbackMessage'); 3734 const emailEl = document.getElementById('feedbackEmail'); 3735 const subscribeEl = document.getElementById('feedbackSubscribe'); 3736 const errEl = document.getElementById('feedbackError'); 3737 const submitBtn = document.getElementById('feedbackSubmitBtn'); 3738 const formBody = document.getElementById('feedbackFormBody'); 3739 const successBody = document.getElementById('feedbackSuccessBody'); 3740 const footer = document.getElementById('feedbackFooter'); 3741 3742 const message = msgEl ? msgEl.value.trim() : ''; 3743 if (message.length < 3) { 3744 if (errEl) { 3745 errEl.textContent = dict.feedbackErrorMsg || 'Please enter your feedback.'; 3746 errEl.classList.remove('hidden'); 3747 } 3748 if (msgEl) msgEl.focus(); 3749 return; 3750 } 3751 3752 const emailVal = emailEl ? emailEl.value.trim() : ''; 3753 if (!emailVal || !emailVal.includes('@')) { 3754 if (errEl) { 3755 errEl.textContent = dict.feedbackEmailRequired || 'Please enter a valid email address.'; 3756 errEl.classList.remove('hidden'); 3757 } 3758 if (emailEl) emailEl.focus(); 3759 return; 3760 } 3761 3762 if (errEl) errEl.classList.add('hidden'); 3763 3764 const payload = { 3765 rating: this.selectedRating || null, 3766 message, 3767 email: emailVal, 3768 subscribe: subscribeEl ? subscribeEl.checked : false, 3769 }; 3770 3771 // Optimistic UI: show success immediately without waiting for network 3772 if (formBody) formBody.classList.add('hidden'); 3773 if (successBody) successBody.classList.remove('hidden'); 3774 if (footer) footer.innerHTML = `<button onclick="feedbackHandler.closeModal()" class="h-8 px-6 rounded-md text-xs font-bold bg-primary text-primary-foreground hover:opacity-90 active:scale-95">OK</button>`; 3775 3776 trackEvent('feedback_submitted', { rating: payload.rating, has_email: !!payload.email, subscribe: payload.subscribe }); 3777 3778 // Fire-and-forget â send in background, no need to block UX 3779 fetch('/api/feedback', { 3780 method: 'POST', 3781 headers: { 'Content-Type': 'application/json' }, 3782 body: JSON.stringify(payload), 3783 }).catch(err => console.error('Feedback submit error:', err)); 3784 }, 3785}; 3786 3787// Expose to global scope for inline onclick handlers 3788window.feedbackHandler = feedbackHandler; 3789 3790/** 3791 * GOOGLE DRIVE SYNC 3792 */ 3793const googleSync = { 3794 client: null, 3795 accessToken: null, 3796 tokenExpiry: 0, 3797 isSyncing: false, // ç¨äºéç¥ SW æ´æ°é»è¾ï¼åæ¥è¿è¡ä¸ï¼æç¼é¡µé¢å·æ° 3798 init(opt = null) { 3799 // 1. å åä¸ token 仿æï¼ç´æ¥æ§è¡åæ¥ 3800 if (this.accessToken && Date.now() < this.tokenExpiry) { 3801 this.startSync(); 3802 return; 3803 } 3804 3805 // 2. é²å¾¡æ§æ£æ¥ï¼GSI åºå°æªå è½½å®æï¼ç§»å¨ç«¯å¼±ç½ç¯å¢ä¸å¯è½åºç°ï¼ 3806 if (typeof google === 'undefined' || !google.accounts || !google.accounts.oauth2) { 3807 const lang = getLang(); 3808 const dict = langMap[lang] || langMap['en']; 3809 uiActions.showToast((dict.syncFail || 'Sync failed: ') + 'Google Sign-In library n
3809ot loaded yet, please try again.', 'error'); 3810 return; 3811 } 3812 3813 // 3. æ£æµæ¯å¦ä¸ºç§»å¨è®¾å¤ï¼iOS Safari / Android WebView çï¼ 3814 // ç§»å¨ç«¯æµè§å¨å¯¹å¼¹çªæä¸¥æ ¼ç"ç¨æ·æå¿ä¸ä¸æ"éå¶ï¼ 3815 // åªæå¨ç¨æ·æå¿ï¼tap/clickï¼ç忥è°ç¨æ 䏿å 许æå¼å¼¹çªï¼ 3816 // 弿¥åè°ï¼å¦ immediate_failedï¼ä¸è§¦åå¼¹çªä¼è¢«ç´æ¥æ¦æªã 3817 const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 3818 3819 // 4. åå§å OAuth2 Token Clientï¼åªåå§å䏿¬¡ï¼åç»å¤ç¨ï¼ 3820 if (!this.client) { 3821 this.client = google.accounts.oauth2.initTokenClient({ 3822 client_id: CONFIG.CLIENT_ID, 3823 scope: 'https://www.googleapis.com/auth/drive.file', 3824 callback: (resp) => { 3825 if (resp.error) { 3826 if (resp.error === 'immediate_failed') { 3827 // é黿æå¤±è´¥ï¼prompt: ''ï¼çå¤ççç¥å å¹³å°èå¼ï¼ 3828 // 3829 // â æ¡é¢ç«¯ï¼immediate_failed åè°å¨æçæ¶é´å 触åï¼æ ç½ç»å¾è¿ï¼ï¼ 3830 // æå¿ä¸ä¸æé叏仿æï¼å¯ä»¥å¨æ¤ç´æ¥è°ç¨ select_account å¼¹çªã 3831 // 3832 // â ç§»å¨ç«¯ï¼iOS Safari ITP / Android SameSite Cookie éå¶ï¼ï¼ 3833 // immediate_failed åè°å±äºå¼æ¥è°ç¨ï¼æå¿ä¸ä¸æå·²å¤±æï¼ 3834 // æ¤å¤è°ç¨ requestAccessToken ä¼è¢«æµè§å¨é黿¦æªââå¼¹çªä¸åºç°ã 3835 // 没æä»»ä½é误æç¤ºï¼å¯¼è´ç¨æ·åå¤ç¹å»åæ¥å´å§ç»æ²¡æååºï¼æ»å¾ªç¯ï¼ã 3836 // æ£ç¡®åæ³ï¼æ¸ é¤ enabled æ è®° + æç¤ºç¨æ·éæ°ç¹å»ï¼ 3837 // 让ä¸ä¸æ¬¡ tap å¨çæ£çç¨æ·æå¿ä¸ä¸æä¸åèµ· select_account 请æ±ã 3838 const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 3839 if (!isMobile) { 3840 // æ¡é¢ç«¯åéï¼å¨æå¿ä¸ä¸æä»æææ¶å¼¹åºè´¦å·éæ©å¨ 3841 console.warn('[GAuth] é黿æå¤±è´¥ï¼æ¡é¢ç«¯åéå°å®æ´ææå¼¹çª'); 3842 this.client.requestAccessToken({ prompt: 'select_account' }); 3843 } else { 3844 // ç§»å¨ç«¯åéï¼æ¸ 餿 è®°ï¼æç¤ºç¨æ·éæ°ç¹å»åæ¥æé® 3845 console.warn('[GAuth] ç§»å¨ç«¯é黿æå¤±è´¥ï¼æ¸ 餿æç¶æï¼çå¾ ç¨æ·éæ°æä½'); 3846 localStorage.removeItem('MD2PDF_DRIVE_SYNC_ENABLED'); 3847 const lang = getLang(); 3848 const dict = langMap[lang] || langMap['en']; 3849 uiActions.showToast(dict.syncReauth || 'Authorization required. Please tap Sync again to connect Google Drive.', 'info'); 3850 } 3851 return; 3852 } 3853 // å ¶ä»é误ï¼popup_closed_by_user çï¼ï¼æ¸ é¤åæ¥æ è®°ï¼é¿å 䏿¬¡ä»èµ°é黿¨¡å¼ 3854 console.error('[GAuth] ææé误:', resp.error); 3855 localStorage.removeItem('MD2PDF_DRIVE_SYNC_ENABLED'); 3856 const lang = getLang(); 3857 const dict = langMap[lang] || langMap['en']; 3858 uiActions.showToast((dict.syncFail || 'Sync failed: ') + resp.error, 'error'); 3859 return; 3860 } 3861 this.accessToken = resp.access_token; 3862 this.tokenExpiry = Date.now() + (resp.expires_in || 3600) * 1000; 3863 localStorage.setItem('MD2PDF_DRIVE_SYNC_ENABLED', 'true'); 3864 this.startSync(); 3865 } 3866 }); 3867 } 3868 3869 // 5. å³å®ææçç¥ 3870 // â ç§»å¨ç«¯ï¼å§ç»ç´æ¥å¼¹åº select_accountï¼å½åè°ç¨æ å¨ç¨æ· tap æå¿ä¸ï¼å¼¹çªä¸ä¼è¢«æ¦æªï¼ 3871 // è·³è¿ prompt: '' é黿æï¼é¿å 触å immediate_failed â 弿¥åè° â å¼¹çªè¢«æ¦æªçæ»å¾ªç¯ã 3872 // â æ¡é¢ç«¯ + æ¾ç»ææï¼å å°è¯ prompt: '' éé»å·æ°ï¼å¤±è´¥æ¶å¨åè°ä¸åéå°å¼¹çªã 3873 // â æ¡é¢ç«¯ + 馿¬¡ä½¿ç¨ï¼ç´æ¥å¼¹åº select_accountã 3874 if (opt) { 3875 this.client.requestAccessToken(opt); 3876 } else { 3877 const wasEnabled = localStorage.getItem('MD2PDF_DRIVE_SYNC_ENABLED') === 'true'; 3878 if (wasEnabled && !isMobileDevice) { 3879 // æ¡é¢ç«¯éé»å·æ°ï¼å¤±è´¥æ¶ç±åè°å¤ç 3880 this.client.requestAccessToken({ prompt: '' }); 3881 } else { 3882 // ç§»å¨ç«¯æé¦æ¬¡ä½¿ç¨ï¼ç´æ¥å¨ç¨æ·æå¿ä¸ä¸æä¸å¼¹åºææçªå£ 3883 this.client.requestAccessToken({ prompt: 'select_account' }); 3884 } 3885 } 3886 }, 3887 3888 async startSync() { 3889 const lang = getLang(); 3890 const dict = langMap[lang] || langMap['en']; 3891 uiActions.showToast(dict.syncStart, "info"); 3892 3893 this.isSyncing = true; 3894 try { 3895 // 1. Find or Create root folder for MD2PDF 3896 const vaultId = await this.getOrCreateFolder("MD2PDF_Vault"); 3897 3898 // 2. Handle Deletions 3899 if (db.deletedNodes && db.deletedNodes.length > 0) { 3900 const remainingDeletes = [];
3901 for (const delNode of db.deletedNodes) { 3902 if (delNode.remoteId) { 3903 const success = await this.deleteRemoteFile(delNode.remoteId); 3904 if (!success) remainingDeletes.push(delNode); 3905 } 3906 } 3907 db.deletedNodes = remainingDeletes; 3908 save(); 3909 } 3910 3911 // 3. Map remote files 3912 const remoteFiles = await this.getAllRemoteFiles(vaultId); 3913 const remoteMap = new Map(); 3914 remoteFiles.forEach(f => { 3915 if (f.id !== vaultId) remoteMap.set(f.id, f); 3916 }); 3917 3918 // 4. Local -> Remote Sync (Update & Upload) 3919 const idMap = { 'null': vaultId }; 3920 const sortedNodes = [...db.nodes].sort((a, b) => { 3921 if (a.type !== b.type) return a.type === 'folder' ? -1 : 1; 3922 return (a.parentId || '').localeCompare(b.parentId || ''); 3923 }); 3924 3925 const nodesToDeleteLocally = new Set(); 3926 for (const node of sortedNodes) { 3927 if (nodesToDeleteLocally.has(node.parentId)) { 3928 nodesToDeleteLocally.add(node.id); 3929 continue; 3930 } 3931 3932 const parentGId = idMap[node.parentId || 'null'] || vaultId; 3933 let remoteNode = node.remoteId ? remoteMap.get(node.remoteId) : null; 3934 3935 // Detection: Remote Deletion 3936 if (!remoteNode && node.remoteId) { 3937 // This node was previously synced but is no longer on Google Drive 3938 const localTime = node.updatedAt || 0; 3939 const lastSyncTime = node.remoteModifiedTime || 0; 3940 if (localTime > lastSyncTime + 2000) { 3941 // Local has changes since last sync, re-upload to GDrive 3942 const gId = await this.uploadNewNode(node, parentGId); 3943 node.remoteId = gId; 3944 node.remoteModifiedTime = await this.getRemoteModifiedTime(gId); 3945 } else { 3946 // No local changes, accept the remote deletion 3947 nodesToDeleteLocally.add(node.id); 3948 continue; 3949 } 3950 } 3951 3952 // If not found by ID (new node), try match by name and parent to avoid duplicates 3953 if (!remoteNode && !node.remoteId) { 3954 const nodeGName = node.type === 'file' ? (node.name.endsWith('.md') ? node.name : node.name + '.md') : node.name; 3955 remoteNode = remoteFiles.find(f => f.name === nodeGName && f.parents && f.parents.includes(parentGId)); 3956 } 3957 3958 if (remoteNode) { 3959 const remoteTime = new Date(remoteNode.modifiedTime).getTime(); 3960 const localTime = node.updatedAt || 0; 3961 const lastSyncTime = node.remoteModifiedTime || 0; 3962 3963 if (localTime > lastSyncTime && (localTime - remoteTime > 2000)) { 3964 // æ¬å°æ´æ°ï¼ä¸æ¯è¿ç«¯æ° 2s ä»¥ä¸ â ä¸ä¼ å°è¿ç«¯ 3965 await this.updateRemoteNode(node, remoteNode.id); 3966 node.remoteId = remoteNode.id; 3967 node.remoteModifiedTime = await this.getRemoteModifiedTime(node.remoteId); 3968 } else if (remoteTime > lastSyncTime && (remoteTime - localTime > 2000)) { 3969 // è¿ç«¯æ´æ°ï¼ä¸æ¯æ¬å°æ° 2s ä»¥ä¸ â ä¸è½½å°æ¬å° 3970 await this.downloadRemoteNode(node, remoteNode); 3971 } else if (localTime > lastSyncTime && remoteTime > lastSyncTime) { 3972 // å²çªï¼ä¸¤ç«¯é½å¨ä¸æ¬¡åæ¥åä¿®æ¹ï¼åæ¶é´æ³è¾æ°ç䏿¹ï¼2s éå¼å æ æ³åºåæ¶åæ¬å°ï¼ 3973 if (localTime >= remoteTime) { 3974 await this.updateRemoteNode(node, remoteNode.id); 3975 node.remoteId = remoteNode.id; 3976 node.remoteModifiedTime = await this.getRemoteModifiedTime(node.remoteId); 3977 } else { 3978 await this.downloadRemoteNode(node, remoteNode); 3979 } 3980 } else if (localTime > lastSyncTime && remoteTime <= lastSyncTime) { 3981 // Bug fix: æ¬å°å¨ä¸æ¬¡åæ¥åæä¿®æ¹ï¼ä½ä¸è¿ç«¯æ¶é´å·® ⤠2sï¼æ¶é误差ææå¿«ä¿®æ¹ï¼ 3982 // è¿ç«¯æªåæ´ï¼åºä»ä»¥æ¬å°ä¸ºåä¸ä¼ ï¼é¿å ä¿®æ¹æ°¸ä¹ 丢失 3983 await this.updateRemoteNode(node, remoteNode.id); 3984 node.remoteId = remoteNode.id; 3985 node.remoteModifiedTime = await this.getRemoteModifiedTime(node.remoteId); 3986 } else { 3987 // æ é忥ï¼ä» æ´æ°æ¬å°è®°å½çè¿ç«¯å æ°æ® 3988 node.remoteId = remoteNode.id; 3989 node.remoteModifiedTime = remoteTime; 3990 } 3991 remoteMap.delete(remoteNode.id); 3992 } else { 3993 // Truly new local node -> Upload new 3994 const gId = await this.uploadNewNode(node, parentGId); 3995 node.remoteId = gId; 3996 node.remoteModifiedTime = await this.getRemoteModifiedTime(gId); 3997 } 3998 idMap[node.id] = node.remoteId; 3999 } 4000 4001 if (nodesToDeleteLocally.size > 0) { 4002 db.nodes = db.nodes.filter(n => !nodesToDeleteLocally.has(n.id)); 4003 } 4004 4005 // 5. Remote -> Local Sync (Download missing) 4006 let remotesToCreate = Array.from(remoteMap.values()); 4007 let attempts = 0; 4008 while (remotesToCreate.length > 0 && attempts < 100) { 4009 const initialLen = remotesToCreate.length; 4010 for (let i = 0; i < remotesToCreate.length; i++) { 4011 const rNode = remotesToCreate[i]; 4012 const parentGId = rNode.parents ? rNode.parents[0] : null; 4013 4014 const localParent = db.nodes.find(n => n.remoteId === parentGId); 4015 if (parentGId === vaultId || localParent) { 4016 const parentId = localParent ? localParent.id : null; 4017 const newNode = { 4018 id: createId(), 4019 name: rNode.mimeType.includes('folder') ? rNode.name : rNode.name.replace(/\.md$/, ''), 4020 type: rNode.mimeType.includes('folder') ? 'folder' : 'file', 4021 parentId: parentId, 4022 remoteId: rNode.id, 4023 remoteModifiedTime: new Date(rNode.modifiedTime).getTime(), 4024 updatedAt: new Date(rNode.modifiedTime).getTime(), 4025 order: db.nodes.filter(n => n.parentId === parentId).length, 4026 content: '' 4027 }; 4028 if (newNode.type === 'file') { 4029 // exportSettings åµå ¥æä»¶å 容æ«å°¾æ³¨éï¼_parseContent ä¼å¥ç¦»å¹¶è§£æ 4030 const raw = await this.downloadFile(rNode.id); 4031 const { content, exportSettings } = this._parseContent(raw); 4032 newNode.content = content; 4033 if (exportSettings) newNode.exportSettings = exp
4033ortSettings; 4034 } 4035 db.nodes.push(newNode); 4036 remotesToCreate.splice(i, 1); 4037 i--; 4038 } 4039 } 4040 if (remotesToCreate.length === initialLen) break; 4041 attempts++; 4042 } 4043 4044 save(); 4045 renderTree(); 4046 uiActions.showToast(dict.syncSuccess, "success"); 4047 } catch (err) { 4048 console.error("Sync Error:", err); 4049 // ç¹æ®å¤çï¼Drive API 403/401 表示ææå·²å¤±æï¼æ¸ é¤ token å¹¶æç¤ºéæ°ææ 4050 const isAuthError = err.message && (err.message.includes('403') || err.message.includes('401')); 4051 if (isAuthError) { 4052 this.accessToken = null; 4053 this.tokenExpiry = 0; 4054 localStorage.removeItem('MD2PDF_DRIVE_SYNC_ENABLED'); 4055 uiActions.showToast(dict.syncReauth || 'Authorization expired. Please tap Sync again to reconnect Google Drive.', 'error'); 4056 } else { 4057 uiActions.showToast(dict.syncFail + err.message, "error"); 4058 } 4059 } finally { 4060 this.isSyncing = false; 4061 } 4062 }, 4063 4064 async getAllRemoteFiles(vaultId) { 4065 let allFiles = []; 4066 let pageToken = null; 4067 // Bug fix: do NOT swallow errors here â a fetch failure (e.g. expired token) must 4068 // propagate so startSync can catch it and show the user an error, rather than 4069 // silently returning [] and causing all local files to be re-uploaded as duplicates. 4070 do { 4071 const query = "trashed = false"; 4072 const resp = await this.fetchWithRetry(`https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=nextPageToken,files(id,name,mimeType,modifiedTime,parents)&pageSize=1000${pageToken ? `&pageToken=${pageToken}` : ''}`, { 4073 headers: { 'Authorization': `Bearer ${this.accessToken}` } 4074 }); 4075 if (!resp.ok) throw new Error(`Drive API ${resp.status}: getAllRemoteFiles`); 4076 const data = await resp.json(); 4077 allFiles = allFiles.concat(data.files || []); 4078 pageToken = data.nextPageToken; 4079 } while (pageToken); 4080 return allFiles; 4081 }, 4082 4083 async getRemoteModifiedTime(id) { 4084 const resp = await this.fetchWithRetry(`https://www.googleapis.com/drive/v3/files/${id}?fields=modifiedTime`, { 4085 headers: { 'Authorization': `Bearer ${this.accessToken}` } 4086 }); 4087 if (!resp.ok) throw new Error(`Drive API ${resp.status}: getRemoteModifiedTime`); 4088 const data = await resp.json(); 4089 return new Date(data.modifiedTime).getTime(); 4090 }, 4091 4092 async downloadFile(id) { 4093 const resp = await this.fetchWithRetry(`https://www.googleapis.com/drive/v3/files/${id}?alt=media`, { 4094 headers: { 'Authorization': `Bearer ${this.accessToken}` } 4095 }); 4096 if (!resp.ok) throw new Error(`Drive API ${resp.status}: downloadFile`); 4097 return await resp.text(); 4098 }, 4099 4100 async deleteRemoteFile(id) { 4101 try { 4102 const resp = await this.fetchWithRetry(`https://www.googleapis.com/drive/v3/files/${id}`, { 4103 method: 'DELETE', 4104 headers: { 'Authorization': `Bearer ${this.accessToken}` } 4105 }); 4106 if (resp.ok || resp.status === 404) { 4107 return true; 4108 } else { 4109 console.warn(`Failed to delete remote file ${id}: ${resp.status}`); 4110 return false; 4111 } 4112 } catch (e) { 4113 console.warn("Network error deleting remote file", id, e); 4114 return false; 4115 } 4116 }, 4117 4118 // å° exportSettings åºåå为æä»¶æ«å°¾ç HTML 注éï¼é¿å Drive appProperties 124åèéå¶ã 4119 // HTML 注éä¸ä¼è¢« Markdown 渲æå¨æ¾ç¤ºï¼å¯¹é 读ä½éªæ å½±åã 4120 _serializeContent(node) { 4121 const base = node.content || ''; 4122 if (node.type !== 'file' || !node.exportSettings || !Object.keys(node.exportSettings).length) { 4123 return base; 4124 } 4125 const tag = `<!-- md2pdf-settings:${JSON.stringify(node.exportSettings)} -->`; 4126 return base + '\n' + tag; 4127 }, 4128 4129 // ä»ä¸è½½çæä»¶å 容䏿åå¹¶å¥ç¦» exportSettings 注éï¼è¿å { content, exportSettings } 4130 _parseContent(raw) { 4131 const marker = '<!-- md2pdf-settings:'; 4132 const idx = raw.lastIndexOf(marker); 4133 if (idx === -1) return { content: raw, exportSettings: null }; 4134 const end = raw.indexOf('-->', idx); 4135 if (end === -1) return { content: raw, exportSettings: null }; 4136 const json = raw.slice(idx + marker.length, end).trim(); 4137 const content = raw.slice(0, idx).replace(/\n$/, ''); // å»æè¿½å æ¶çæ¢è¡ 4138 try { 4139 return { content, exportSettings: JSON.parse(json) }; 4140 } catch (e) { 4141 return { content: raw, exportSettings: null }; 4142 } 4143 }, 4144 4145 async updateRemoteNode(node, remoteId) { 4146 const name = node.type === 'file' ? (node.name.endsWith('.md') ? node.name : node.name + '.md') : node.name; 4147 const updateMeta = { name: name }; 4148 4149 // Bug fix: check response status for metadata PATCH 4150 const metaResp = await this.fetchWithRetry(`https://www.googleapis.com/drive/v3/files/${remoteId}`, { 4151 method: 'PATCH', 4152 headers: { 4153 'Authorization': `Bearer ${this.accessToken}`, 4154 'Content-Type': 'application/json' 4155 }, 4156 body: JSON.stringify(updateMeta) 4157 }); 4158 if (!metaResp.ok) throw new Error(`Drive API ${metaResp.status}: updateRemoteNode (metadata)`); 4159 4160 if (node.type === 'file') { 4161 // exportSettings 以 HTML 注éå½¢å¼åµå ¥æä»¶æ«å°¾ï¼è§é¿ appProperties 124åèéå¶ 4162 const mediaResp = await this.fetchWithRetry(`https://www.googleapis.com/upload/drive/v3/files/${remoteId}?uploadType=media`, { 4163 method: 'PATCH', 4164 headers: { 4165 'Authorization': `Bearer ${this.accessToken}`, 4166 'Content-Type': 'text/markdown' 4167 }, 4168 body: this._serializeContent(node) 4169 }); 4170 if (!mediaResp.ok) throw new Error(`Drive API ${mediaResp.status}: updateRemoteNode (content)`); 4171 } 4172 }, 4173 4174 async downloadRemoteNode(localNode, remoteNode) { 4175 localNode.name = localNode.type === 'file' ? remoteNode.name.replace(/\.md$/, '') : remoteNode.name; 4176 if (localNode.type === 'file') { 4177 const raw = await this.downloadFile(remoteNode.id); 4178 // exportSettings 以 HTML 注éåµå ¥æä»¶æ«å°¾ï¼è§£æå¹¶å¥ç¦»ååå ¥ node 4179 const { content, exportSettings } = this._parseContent(raw); 4180 localNode.content = content;
4181 if (exportSettings) localNode.exportSettings = exportSettings; 4182 } 4183 localNode.remoteId = remoteNode.id; 4184 localNode.remoteModifiedTime = new Date(remoteNode.modifiedTime).getTime(); 4185 localNode.updatedAt = localNode.remoteModifiedTime; 4186 }, 4187 4188 async uploadNewNode(node, parentGId) { 4189 const name = node.type === 'file' ? (node.name.endsWith('.md') ? node.name : node.name + '.md') : node.name; 4190 const mimeType = node.type === 'folder' ? 'application/vnd.google-apps.folder' : 'text/markdown'; 4191 const meta = { name: name, mimeType: mimeType, parents: [parentGId] }; 4192 // exportSettings éè¿æä»¶å 容æ«å°¾ç HTML 注éä¼ è¾ï¼ä¸ä½¿ç¨ appPropertiesï¼æ 124åèéå¶ï¼ 4193 4194 if (node.type === 'folder') { 4195 const resp = await this.fetchWithRetry('https://www.googleapis.com/drive/v3/files', { 4196 method: 'POST', 4197 headers: { 'Authorization': `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' }, 4198 body: JSON.stringify(meta) 4199 }); 4200 // Bug fix: check response before reading id; bad response => throw to surface the error 4201 if (!resp.ok) throw new Error(`Drive API ${resp.status}: uploadNewNode (folder)`); 4202 const data = await resp.json(); 4203 if (!data.id) throw new Error('Drive API: uploadNewNode folder returned no id'); 4204 return data.id; 4205 } else { 4206 const form = new FormData(); 4207 form.append('metadata', new Blob([JSON.stringify(meta)], { type: 'application/json' })); 4208 // exportSettings åºåå为å 容æ«å°¾æ³¨éï¼é¿å appProperties 124åèé¿åº¦éå¶ 4209 form.append('file', new Blob([this._serializeContent(node)], { type: 'text/markdown' })); 4210 const resp = await this.fetchWithRetry('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', { 4211 method: 'POST', 4212 headers: { 'Authorization': `Bearer ${this.accessToken}` }, 4213 body: form 4214 }); 4215 // Bug fix: check response before reading id 4216 if (!resp.ok) throw new Error(`Drive API ${resp.status}: uploadNewNode (file)`); 4217 const data = await resp.json(); 4218 if (!data.id) throw new Error('Drive API: uploadNewNode file returned no id'); 4219 return data.id; 4220 } 4221 }, 4222 4223 async fetchWithRetry(url, options = {}, retries = 3) { 4224 let lastErr; 4225 4226 // Android/Mobile optimization: 4227 // 1. Omit credentials to avoid cookie issues (crucial for mobile ITP/SameSite) 4228 // 2. Set cache to 'no-store' to bypass SW/browser cache issues 4229 // 3. Explicitly set mode to 'cors' 4230 if (url.includes('googleapis.com')) { 4231 if (!options.credentials) options.credentials = 'omit'; 4232 if (!options.cache) options.cache = 'no-store'; 4233 options.mode = 'cors'; // Ensure CORS mode is explicit 4234 4235 // Remove referrerPolicy='no-referrer' as it might block valid API calls checking origin 4236 // if (!options.referrerPolicy) options.referrerPolicy = 'no-referrer'; 4237 } 4238 4239 for (let i = 0; i < retries; i++) { 4240 try { 4241 // Check online status first 4242 if (navigator.onLine === false) { 4243 throw new Error('Network offline'); 4244 } 4245 4246 const response = await fetch(url, options); 4247 4248 // If 401/403, read error body for diagnostics, then return for caller to handle 4249 if (response.status === 401 || response.status === 403) { 4250 try { 4251 const errBody = await response.clone().json(); 4252 const reason = errBody?.error?.errors?.[0]?.reason || errBody?.error?.message || ''; 4253 if (reason) console.warn(`[Drive API] ${response.status} - ${reason}`, url); 4254 } catch (_) { /* response body may not be JSON */ } 4255 return response; 4256 } 4257 4258 // If 5xx, retry 4259 if (response.status >= 500) { 4260 throw new Error(`Server error: ${response.status}`); 4261 } 4262 4263 return response; 4264 } catch (err) { 4265 lastErr = err;
4266 if (i < retries - 1) { 4267 await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))); 4268 } 4269 } 4270 } 4271 throw lastErr; 4272 }, 4273 4274 async getOrCreateFolder(name, parentId = null) { 4275 // Bug fix: when searching for the root vault folder (no parentId), add 'root' in parents 4276 // constraint to prevent matching a same-named folder elsewhere in the user's Drive. 4277 let query = `name = '${name}' and mimeType = 'application/vnd.google-apps.folder' and trashed = false`; 4278 if (parentId) { 4279 query += ` and '${parentId}' in parents`; 4280 } else { 4281 query += ` and 'root' in parents`; 4282 } 4283 4284 const resp = await this.fetchWithRetry(`https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id)`, { 4285 headers: { 'Authorization': `Bearer ${this.accessToken}` } 4286 }); 4287 if (!resp.ok) throw new Error(`Drive API ${resp.status}: getOrCreateFolder`); 4288 const data = await resp.json(); 4289 4290 if (data.files && data.files.length > 0) { 4291 return data.files[0].id; 4292 } 4293 4294 const meta = { 4295 name: name, 4296 mimeType: 'application/vnd.google-apps.folder' 4297 }; 4298 if (parentId) meta.parents = [parentId]; 4299 4300 const createResp = await this.fetchWithRetry('https://www.googleapis.com/drive/v3/files', { 4301 method: 'POST', 4302 headers: { 4303 'Authorization': `Bearer ${this.accessToken}`, 4304 'Content-Type': 'application/json' 4305 }, 4306 body: JSON.stringify(meta) 4307 }); 4308 if (!createResp.ok) throw new Error(`Drive API ${createResp.status}: getOrCreateFolder (create)`); 4309 const created = await createResp.json(); 4310 if (!created.id) throw new Error('Drive API: getOrCreateFolder returned no id'); 4311 return created.id; 4312 } 4313}; 4314 4315/** 4316 * HELPERS 4317 */ 4318function createId() { return Date.now().toString(36) + Math.random().toString(36).substr(2, 5); } 4319 4320function save() { 4321 const toSave = { ...db, expanded: Array.from(db.expanded) }; 4322 localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify(toSave)); 4323} 4324 4325function updateBreadcrumb(id) { 4326 let path = []; 4327 let curr = db.nodes.find(n => n.id === id); 4328 while (curr) { 4329 if (curr.parentId) { 4330 curr = db.nodes.find(n => n.id === curr.parentId); 4331 if (curr) path.unshift(curr.name); 4332 } else break; 4333 } 4334 const el = document.getElementById('breadcrumb'); 4335 if (el) el.innerText = path.join(' / ') + (path.length ? ' /' : ''); 4336} 4337 4338function updatePreview() { 4339 const editorValue = document.getElementById('editor').value; 4340 const previewContent = document.getElementById('preview-content'); 4341 if (previewContent) { 4342 window._footnotes = []; // Reset footnotes collection 4343 let html = marked.parse(editorValue || ''); 4344 4345 // Render footnotes at the bottom 4346 if (window._footnotes && window._footnotes.length > 0) { 4347 html += '<div class="footnotes"><hr><section class="footnotes-list"><ol>'; 4348 window._footnotes.forEach(fn => { 4349 html += `<li id="fn-${fn.id}">${fn.content} <a href="#fnref-${fn.id}" class="footnote-backref">â©</a></li>`; 4350 }); 4351 html += '</ol></section></div>'; 4352 } 4353 4354 previewContent.innerHTML = html; 4355 4356 // Render Mermaid Diagrams if present 4357 if (window.mermaid) { 4358 // Use a slight delay to ensure the DOM is ready and avoid measurement errors 4359 setTimeout(() => { 4360 // IMPORTANT: Only render if the preview is actually visible to avoid "NaN" transform errors 4361 const previewPane = document.getElementById('preview-pane'); 4362 if (previewPane && previewPane.offsetParent === null) return; 4363 4364 const mermaidNodes = previewContent.querySelectorAll('.mermaid'); 4365 if (mermaidNodes.length > 0) { 4366 mermaid.run({ 4367 nodes: mermaidNodes, 4368 }).catch(err => { 4369 // Prevent error loop by logging but not crashing 4370 console.error("Mermaid Render Error:", err); 4371 }); 4372 } 4373 }, 50); 4374 } 4375 } 4376} 4377 4378function setupEventListeners() {
4379 // GA4 Tracking: Affiliate Links & External Tools 4380 document.addEventListener('click', (e) => { 4381 const link = e.target.closest('a'); 4382 if (link && link.target === '_blank' && !link.href.includes(window.location.host)) { 4383 // Track clicks on affiliate links 4384 const isAffiliate = link.href.includes('quantaflow.cc') || link.getAttribute('data-affiliate') === 'true' || link.innerText.includes('AI Power Tools'); 4385 if (isAffiliate) { 4386 const linkName = link.getAttribute('data-link-name') || link.innerText.trim() || link.hostname; 4387 trackEvent('md_affiliate_click', { link_name: linkName }); 4388 } 4389 } 4390 }); 4391 4392 // GA4 Tracking: Landing Page Hero CTA Clicks 4393 document.addEventListener('click', (e) => { 4394 const ctaButton = e.target.closest('button'); 4395 if (ctaButton) { 4396 const onclickAttr = ctaButton.getAttribute('onclick') || ''; 4397 let ctaType = ''; 4398 // Detect primary CTA types 4399 if (onclickAttr.includes('performPdfExport')) ctaType = 'export_pdf'; 4400 if (onclickAttr.includes('toggleGuide')) ctaType = 'ai_assistant'; 4401 if (onclickAttr.includes('toggleCopyMenu')) ctaType = 'copy_content'; 4402 4403 if (ctaType) { 4404 // Determine LP context from pathname for cta_location 4405 const path = window.location.pathname; 4406 let lpId = 'home'; 4407 4408 if (path !== '/' && path !== '/index.html') { 4409 // Extract the primary folder name or filename 4410 const segments = path.split('/').filter(s => s && s !== 'index.html'); 4411 if (segments.length > 0) { 4412 lpId = segments[0].replace('-to-pdf', '').replace('-exporter', '').replace('-to-pdf-online', ''); 4413 } 4414 } 4415 4416 trackEvent('md_lp_cta_click', { 4417 cta_location: `hero_section_${lpId}`, 4418 cta_type: ctaType 4419 }); 4420 } 4421 } 4422 }); 4423 4424 const mobileMenuBtn = document.getElementById('mobileMenuBtn'); 4425 if (mobileMenuBtn) { 4426 mobileMenuBtn.onclick = () => { 4427 const sidebar = document.getElementById('sidebar'); 4428 sidebar.classList.toggle('open'); 4429 }; 4430 } 4431 4432 // Close sidebar when clicking outside on mobile 4433 document.addEventListener('click', (e) => { 4434 if (window.innerWidth < 768) { 4435 const sidebar = document.getElementById('sidebar'); 4436 const mobileMenuBtn = document.getElementById('mobileMenuBtn'); 4437 if (sidebar && sidebar.classList.contains('open') && 4438 !sidebar.contains(e.target) && 4439 mobileMenuBtn && !mobileMenuBtn.contains(e.target)) { 4440 sidebar.classList.remove('open'); 4441 } 4442 } 4443 }); 4444 4445 // Close sidebar when clicking a file on mobile 4446 const treeContainer = document.getElementById('treeContainer'); 4447 if (treeContainer) { 4448 treeContainer.addEventListener('click', (e) => { 4449 if (window.innerWidth < 768 && e.target.closest('.tree-node')) { 4450 const node = e.target.closest('.tree-node'); 4451 // Check if it's a file by verifying no folder icon 4452 if (node.querySelector('.fa-file-lines')) { 4453 document.getElementById('sidebar').classList.remove('open'); 4454 } 4455 } 4456 }); 4457 } 4458 4459 // Fix footnote links jumping causing page layout shift 4460 const previewPane = document.getElementById('preview-pane'); 4461 if (previewPane) { 4462 previewPane.addEventListener('click', (e) => { 4463 const anchor = e.target.closest('a'); 4464 if (anchor && anchor.getAttribute('href')?.startsWith('#fn')) { 4465 const targetId = anchor.getAttribute('href').slice(1); 4466 const targetEl = document.getElementById(targetId); 4467 if (targetEl) { 4468 e.preventDefault(); 4469 targetEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); 4470 } 4471 } 4472 }); 4473 } 4474 4475 document.getElementById('editor').addEventListener('input', (e) => { 4476 const file = db.nodes.find(n => n.id === db.activeId); 4477 if (file) { 4478 file.content = e.target.value; 4479 file.updatedAt = Date.now(); 4480 save(); 4481 } 4482 updatePreview(); 4483 }); 4484 document.addEventListener('contextmenu', e => { 4485 if (!e.target.closest('.tree-node')) e.preventDefault(); 4486 }); 4487} 4488 4489function initResizer() { 4490 const resizer = document.getElementById('resizer'); 4491 const editor = document.getElementById('editor'); 4492 const ws = document.getElementById('workspace'); 4493 if (!resizer) return; 4494 let isDragging = false;
4495 4496 resizer.addEventListener('mousedown', (e) => { 4497 isDragging = true; 4498 resizer.classList.add('dragging'); 4499 document.body.style.cursor = 'col-resize'; 4500 }); 4501 4502 document.addEventListener('mousemove', (e) => { 4503 if (!isDragging) return; 4504 const containerRect = ws.getBoundingClientRect(); 4505 const relativeX = e.clientX - containerRect.left; 4506 const percentage = (relativeX / containerRect.width) * 100; 4507 4508 if (percentage > 10 && percentage < 90) { 4509 editor.style.flex = `0 0 ${percentage}%`; 4510 } 4511 }); 4512 4513 document.addEventListener('mouseup', () => { 4514 if (isDragging) { 4515 isDragging = false; 4516 resizer.classList.remove('dragging'); 4517 document.body.style.cursor = 'default'; 4518 } 4519 }); 4520} 4521 4522/** 4523 * SYNC SCROLL (EDITOR & PREVIEW) 4524 */ 4525function initSyncScroll() { 4526 const editor = document.getElementById('editor'); 4527 const previewPane = document.getElementById('preview-pane'); 4528 if (!editor || !previewPane) return; 4529 4530 let isSyncingEditor = false; 4531 let isSyncingPreview = false; 4532 4533 editor.onscroll = () => { 4534 if (isSyncingEditor) { 4535 isSyncingEditor = false; 4536 return; 4537 } 4538 const ws = document.getElementById('workspace'); 4539 // Only sync in split mode 4540 if (!ws || !ws.classList.contains('mode-split')) return; 4541 4542 isSyncingPreview = true; 4543 const denominator = editor.scrollHeight - editor.clientHeight; 4544 if (denominator <= 0) return; 4545 4546 const percentage = editor.scrollTop / denominator; 4547 previewPane.scrollTop = percentage * (previewPane.scrollHeight - previewPane.clientHeight); 4548 }; 4549 4550 previewPane.onscroll = () => { 4551 if (isSyncingPreview) { 4552 isSyncingPreview = false; 4553 return; 4554 } 4555 const ws = document.getElementById('workspace'); 4556 // Only sync in split mode 4557 if (!ws || !ws.classList.contains('mode-split')) return; 4558 4559 isSyncingEditor = true; 4560 const denominator = previewPane.scrollHeight - previewPane.clientHeight; 4561 if (denominator <= 0) return; 4562 4563 const percentage = previewPane.scrollTop / denominator; 4564 editor.scrollTop = percentage * (editor.scrollHeight - editor.clientHeight); 4565 }; 4566} 4567 4568window.onload = () => { 4569 // Landing Page Detection & Storage 4570 if (!sessionStorage.getItem('original_lp')) { 4571 const params = new URLSearchParams(window.location.search); 4572 const source = params.get('utm_source') || params.get('ref') || 'organic'; 4573 const campaign = params.get('utm_campaign') || ''; 4574 const lpValue = `${window.location.pathname}${source !== 'organic' ? '?source=' + source : ''}${campaign ? '&campaign=' + campaign : ''}`; 4575 sessionStorage.setItem('original_lp', lpValue); 4576 4577 // Trigger Initial Visit Event 4578 trackEvent('md_lp_view', { 4579 lp_path: window.location.pathname, 4580 entry_source: source, 4581 campaign: campaign 4582 }); 4583 } 4584 4585 // GA4 Tracking: Traffic Source & Entry Context 4586 const urlParamsForGA = new URLSearchParams(window.location.search); 4587 if (urlParamsForGA.has('content')) { 4588 trackEvent('md_entry_api', { source: urlParamsForGA.get('source') || 'api' }); 4589 } 4590 4591 const referrer = document.referrer; 4592 if (referrer) { 4593 try { 4594 const refUrl = new URL(referrer); 4595 const domain = refUrl.hostname.replace('www.', ''); 4596 const referralSources = ['v2ex.com', 'reddit.com', 'producthunt.com']; 4597 if (referralSources.includes(domain)) { 4598 trackEvent('md_referral_source', { source: domain }); 4599 } 4600 } catch (e) { /* ignore parse errors */ } 4601 } 4602 4603 // è¡¥ä¸ï¼è§£å³ html2pdf.js è°ç¨ jsPDF æé 彿°æ¶åæ°è¿æ¶è¦åçé®é¢ 4604 const fixJsPDF = () => { 4605 const _jsPDF = window.jsPDF || (window.jspdf && window.jspdf.jsPDF); 4606 if (_jsPDF && !_jsPDF.__patched) { 4607 const PatchedjsPDF = function (orientation, unit, format, compressPDF) { 4608 if (arguments.length > 1 && typeof orientation !== 'object') { 4609 return new _jsPDF({ orientation, unit, format, compressPDF }); 4610 } 4611 return new _jsPDF(...arguments); 4612 }; 4613 PatchedjsPDF.prototype = _jsPDF.prototype; 4614 PatchedjsPDF.__patched = true; 4615 window.jsPDF = PatchedjsPDF; 4616 if (window.jspdf) window.jspdf.jsPDF = PatchedjsPDF; 4617 } 4618 }; 4619 fixJsPDF(); 4620 4621 translateUI(); 4622 uiActions.updateLogoVisibility(); 4623 window.addEventListener('resize', () => { 4624 uiActions.updateLogoVisibility(); 4625 renderTree(); 4626 }); 4627 4628 // Load saved theme 4629 const savedTheme = localStorage.getItem('MD2PDF_THEME') || 'light'; 4630 document.body.setAttribute('data-theme', savedTheme); 4631 4632 marked.setOptions({ 4633 gfm: true, 4634 breaks: true, 4635 headerIds: false, 4636 mangle: false, 4637 headerPrefix: '' 4638 }); 4639 4640 // Custom Extensions for Marked 4641 marked.use({ 4642 renderer: { 4643 code(code, lang) { 4644 const text = typeof code === 'object' ? code.text : code; 4645 const language = typeof code === 'object' ? code.lang : lang; 4646 if (language === 'mermaid') { 4647 return `<div class="mermaid">${text}</div>`; 4648 } 4649 return false;
4650 }, 4651 blockquote(quote) { 4652 const isToken = typeof quote !== 'string'; 4653 const text = isToken ? (quote.text || '') : quote; 4654 const reg = isToken ? /^\s*\[!(NOTE|TIP|WARNING|DANGER|INFO|IMPORTANT|CAUTION|TODO|HINT|BUG|EXAMPLE|QUOTE)\](?:\s*(.*))?/i : /^<p>\[!(NOTE|TIP|WARNING|DANGER|INFO|IMPORTANT|CAUTION|TODO|HINT|BUG|EXAMPLE|QUOTE)\](?:\s*(.*))?<\/p>/i; 4655 4656 const match = text.match(reg); 4657 if (match) { 4658 const type = match[1].toLowerCase(); 4659 const customTitle = match[2] ? match[2].trim() : null; 4660 const icons = { 4661 note: 'fa-info-circle', 4662 info: 'fa-circle-info', 4663 todo: 'fa-check-circle', 4664 tip: 'fa-lightbulb', 4665 hint: 'fa-magnifying-glass', 4666 warning: 'fa-exclamation-triangle', 4667 caution: 'fa-exclamation-circle', 4668 important: 'fa-circle-exclamation', 4669 danger: 'fa-skull-crossbones', 4670 error: 'fa-circle-xmark', 4671 bug: 'fa-bug', 4672 example: 'fa-list-ul', 4673 quote: 'fa-quote-left' 4674 }; 4675 4676 const titlesMap = { 4677 'zh': { note: '夿³¨', tip: 'æç¤º', warning: 'è¦å', danger: 'å±é©', info: 'ä¿¡æ¯', important: 'éè¦', caution: '注æ', todo: 'å¾ å', hint: 'å¯å', bug: '缺é·', example: 'ä¾å', quote: 'å¼ç¨' }, 4678 'tw': { note: 'å註', tip: 'æç¤º', warning: 'è¦å', danger: 'å±éª', info: 'ä¿¡æ¯', important: 'éè¦', caution: '注æ', todo: 'å¾ è¾¦', hint: 'åç¼', bug: '缺é·', example: 'ä¾å', quote: 'å¼ç¨' }, 4679 'es': { note: 'Nota', tip: 'Consejo', warning: 'Advertencia', danger: 'Peligro', info: 'Información', important: 'Importante', caution: 'Precaución', todo: 'Tarea', hint: 'Pista', bug: 'Error', example: 'Ejemplo', quote: 'Cita' }, 4680 'en': { note: 'Note', tip: 'Tip', warning: 'Warning', danger: 'Danger', info: 'Info', important: 'Important', caution: 'Caution', todo: 'Todo', hint: 'Hint', bug: 'Bug', example: 'Example', quote: 'Quote' } 4681 }; 4682 const lang = typeof getLang === 'function' ? getLang() : 'en'; 4683 const titles = titlesMap[lang] || titlesMap['en']; 4684 const displayName = customTitle || titles[type] || type.toUpperCase(); 4685 4686 let content; 4687 if (isToken) { 4688 const tokens = JSON.parse(JSON.stringify(quote.tokens || [])); 4689 if (tokens[0] && tokens[0].type === 'paragraph') { 4690 tokens[0].text = tokens[0].text.replace(reg, '').trim(); 4691 if (tokens[0].tokens && tokens[0].tokens[0] && tokens[0].tokens[0].type === 'text') { 4692 tokens[0].tokens[0].text = tokens[0].tokens[0].text.replace(/^\[!(NOTE|TIP|WARNING|DANGER|INFO|IMPORTANT|CAUTION|TODO|HINT|BUG|EXAMPLE|QUOTE)\](?:\s*(.*))?/i, '').trim(); 4693 } 4694 } 4695 content = this.parser.parse(tokens); 4696 } else { 4697 content = quote.replace(reg, '<p>').replace('<p></p>', ''); 4698 } 4699 4700 const colorMap = { 4701 'note': 'note', 'info': 'info', 'todo': 'todo', 4702 'tip': 'tip', 'hint': 'hint', 4703 'warning': 'warning', 'caution': 'caution', 'important': 'important', 4704 'danger': 'danger', 'error': 'danger', 'bug': 'danger', 4705 'example': 'example', 'quote': 'quote' 4706 }; 4707 const colorType = colorMap[type] || 'note'; 4708 4709 return `<div class="callout callout-${colorType}"> 4710 <p class="callout-title"><i class="fas ${icons[type] || 'fa-info-circle'}"></i> ${displayName}</p> 4711 ${content} 4712 </div>`; 4713 } 4714 return false;
4715 } 4716 } 4717 }); 4718 4719 // Custom Extensions 4720 marked.use({ 4721 extensions: [ 4722 { 4723 name: 'highlight', 4724 level: 'inline', 4725 start(src) { return src.match(/==/)?.index; }, 4726 tokenizer(src, tokens) { 4727 const match = src.match(/^==(?=[^\s=])([\s\S]*?[^\s=])==/); 4728 if (match) { 4729 return { 4730 type: 'highlight', 4731 raw: match[0], 4732 text: this.lexer.inlineTokens(match[1]) 4733 }; 4734 } 4735 }, 4736 renderer(token) { 4737 return `<mark>${this.parser.parseInline(token.text)}</mark>`; 4738 } 4739 }, 4740 { 4741 name: 'wikilink', 4742 level: 'inline', 4743 start(src) { return src.match(/\[\[/)?.index; }, 4744 tokenizer(src, tokens) { 4745 const match = src.match(/^\[\[([^\]\|]+)(\|([^\]]+))?\]\]/); 4746 if (match) { 4747 return { 4748 type: 'wikilink', 4749 raw: match[0], 4750 link: match[1].trim(), 4751 alias: match[3] ? match[3].trim() : match[1].trim() 4752 }; 4753 } 4754 }, 4755 renderer(token) { 4756 return `<span class="wikilink">${token.alias}</span>`; 4757 } 4758 }, 4759 { 4760 name: 'footnote', 4761 level: 'inline', 4762 start(src) { return src.match(/\[\^/)?.index; }, 4763 tokenizer(src, tokens) { 4764 const match = src.match(/^\[\^([^\]]+)\]/); 4765 if (match) { 4766 return { 4767 type: 'footnote', 4768 raw: match[0], 4769 id: match[1] 4770 }; 4771 } 4772 }, 4773 renderer(token) { 4774 return `<sup class="footnote-ref" id="fnref-${token.id}"><a href="#fn-${token.id}">${token.id}</a></sup>`; 4775 } 4776 }, 4777 { 4778 name: 'footnoteDef', 4779 level: 'block', 4780 start(src) { return src.match(/^\[\^/)?.index; }, 4781 tokenizer(src, tokens) { 4782 const match = src.match(/^\[\^([^\]]+)\]:\s+(.*)(\n|$)/); 4783 if (match) { 4784 return { 4785 type: 'footnoteDef', 4786 raw: match[0], 4787 id: match[1], 4788 content: match[2] 4789 }; 4790 } 4791 }, 4792 renderer(token) { 4793 if (!window._footnotes) window._footnotes = []; 4794 window._footnotes.push(token); 4795 return ''; 4796 } 4797 } 4798 ] 4799 }); 4800 4801 if (typeof markedKatex !== 'undefined') { 4802 marked.use(markedKatex({ throwOnError: false })); 4803 } 4804 4805 // Initialize Mermaid 4806 if (window.mermaid) { 4807 mermaid.initialize({ 4808 startOnLoad: false, 4809 theme: 'default', 4810 flowchart: { htmlLabels: false, useMaxWidth: true, curve: 'linear' }, 4811 sequence: { htmlLabels: false, useMaxWidth: true, boxMargin: 5 }, 4812 gantt: { htmlLabels: false, useMaxWidth: true } 4813 }); 4814 } 4815 4816 const saved = localStorage.getItem(CONFIG.STORAGE_KEY); 4817 if (saved) { 4818 try { 4819 const data = JSON.parse(saved); 4820 db = { ...db, ...data }; // Merge with defaults 4821 db.expanded = new Set(data.expanded || []); 4822 db.deletedNodes = data.deletedNodes || []; 4823 4824 // Migration: Add updatedAt to old nodes if missing 4825 db.nodes.forEach(node => { 4826 if (!node.updatedAt) node.updatedAt = Date.now(); 4827 }); 4828 4829 checkWelcomeUpdate(); 4830 } catch (e) { initDefaultDocs(); } 4831 } else { 4832 initDefaultDocs(); 4833 localStorage.setItem('MD2PDF_WELCOME_VERSION', CONFIG.WELCOME_VERSION); 4834 } 4835 4836 if (!handleUrlParams()) { 4837 if (db.activeId && db.nodes.find(n => n.id === db.activeId)) openFile(db.activeId); 4838 else if (db.nodes.length > 0) { 4839 const firstFile = db.nodes.find(n => n.type === 'file'); 4840 if (firstFile) openFile(firstFile.id); 4841 } 4842 } 4843 renderTree(); 4844 setupEventListeners(); 4845 initResizer(); 4846 initSyncScroll(); 4847 licenseManager.mount(); 4848 4849 if (window.innerWidth <= 1024) { 4850 // å¹³æ¿åç§»å¨ç«¯é»è®¤è¿å ¥ç¼è¾æ¨¡å¼ï¼åæ ï¼ï¼é¿å 两æ å¨å°å
4850±å¹ä¸è¿çª 4851 uiActions.setView('editor'); 4852 } else { 4853 uiActions.setView('split'); 4854 } 4855};
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.