// ==UserScript== // @name Web2PDF 网页转PDF // @name:zh-CN Web2PDF 网页转PDF // @name:zh-TW Web2PDF 網頁轉檔PDF // @namespace http://tampermonkey.net/ // @version 1.4 // @description Convert web pages to PDF with support for reading mode, editing, and custom styles.将网页转换为PDF,支持阅读模式、编辑和自定义样式。 // @description:zh-CN 将网页转换为PDF,支持阅读模式、编辑和自定义样式。 // @description:zh-TW 將網頁轉檔PDF,支援閱讀模式、編輯和自定義樣式。 // @author martjay // @match *://*/* // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZmlsbD0iIzRDQUY1MCIgZD0iTTIwIDJIOGMtMS4xIDAtMiAuOS0yIDJ2MTJjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlY0YzAtMS4xLS45LTItMi0yem0tOC41IDcuNWMwIC44My0uNjcgMS41LTEuNSAxLjVIOXYySDcuNVY3SDEwYy44MyAwIDEuNS42NyAxLjUgMS41djF6bTUgMmMwIC44My0uNjcgMS41LTEuNSAxLjVoLTIuNVY3SDE1Yy44MyAwIDEuNS42NyAxLjUgMS41djN6bTQtM0gxOXYxaDEuNVYxMUgxOHYyaC0xLjVWN2gzdjEuNXpNOSA5LjVoMXYtMUg5djF6TTQgNkgydjE0YzAgMS4xLjkgMiAyIDJoMTR2LTJINFY2em0xMCA1LjVoMXYtM2gtMXYzeiIvPjwvc3ZnPg== // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @license GPL-3.0 License // @require https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @downloadURL https://update.greasyfork.icu/scripts/523995/Web2PDF%20%E7%BD%91%E9%A1%B5%E8%BD%ACPDF.user.js // @updateURL https://update.greasyfork.icu/scripts/523995/Web2PDF%20%E7%BD%91%E9%A1%B5%E8%BD%ACPDF.meta.js // ==/UserScript== (function() { 'use strict'; // i18n 配置 const i18n = { zh: { readMode: '阅读模式', printPDF: '打印PDF', editMode: '编辑模式', switchLang: '中/En', loading: '正在加载页面内容和图片,请稍候...', loadingImages: '正在加载图片', noImages: '没有找到需要加载的图片', loadComplete: '加载完成,现在可以打印PDF了', loadFailed: '加载失败,请重试', deleteBlock: '删除此块', confirmDelete: '确定要删除这个内容块吗?', enterLink: '请输入链接地址:', bold: '加粗', italic: '斜体', underline: '下划线', heading1: '标题1', heading2: '标题2', paragraph: '段落', alignLeft: '左对齐', alignCenter: '居中', alignRight: '右对齐', bulletList: '无序列表', numberList: '有序列表', addLink: '添加链接', undo: '撤销', redo: '重做', pdfError: '生成PDF时出错,请重试', close: '关闭 (ESC)' }, en: { readMode: 'Reading Mode', printPDF: 'Print PDF', editMode: 'Edit Mode', switchLang: '中/En', loading: 'Loading content and images, please wait...', loadingImages: 'Loading images', noImages: 'No images found to load', loadComplete: 'Loading complete, you can print PDF now', loadFailed: 'Loading failed, please try again', deleteBlock: 'Delete Block', confirmDelete: 'Are you sure you want to delete this block?', enterLink: 'Please enter the link URL:', bold: 'Bold', italic: 'Italic', underline: 'Underline', heading1: 'Heading 1', heading2: 'Heading 2', paragraph: 'Paragraph', alignLeft: 'Align Left', alignCenter: 'Center', alignRight: 'Align Right', bulletList: 'Bullet List', numberList: 'Number List', addLink: 'Add Link', undo: 'Undo', redo: 'Redo', pdfError: 'Error generating PDF, please try again', close: 'Close (ESC)' } }; // 获取用户语言 async function getUserLanguage() { const userLang = GM_getValue('userLanguage'); if (userLang) { return userLang; } const lang = navigator.language.toLowerCase(); const defaultLang = lang.startsWith('zh') ? 'zh' : 'en'; return defaultLang; } // 设置用户语言 async function setUserLanguage(lang) { GM_setValue('userLanguage', lang); } // 获取翻译文本 async function t(key) { const lang = await getUserLanguage(); return i18n[lang][key] || i18n.en[key]; } // 添加样式 GM_addStyle(` .web2pdf-floating-button { position: fixed; z-index: 2147483647; width: 48px; height: 48px; border-radius: 24px; background: white; border: none; box-shadow: 0 2px 10px rgba(0,0,0,0.2); cursor: move; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; user-select: none; touch-action: none; color: #333; left: 20px; bottom: 20px; } .web2pdf-floating-button:hover { background: #f5f5f5; } .web2pdf-floating-button.dragging { opacity: 0.8; cursor: grabbing; } .web2pdf-floating-button svg { pointer-events: none; fill: currentColor; } .web2pdf-menu { position: fixed; background: white; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); display: none; z-index: 2147483646; min-width: 180px; max-width: 250px; } .web2pdf-menu.show { display: block; } .menu-item { padding: 10px 15px; display: flex; align-items: center; gap: 8px; cursor: pointer; color: #333; transition: background-color 0.2s; white-space: nowrap; } .menu-item:hover { background-color: #f5f5f5; } .menu-item svg { flex-shrink: 0; fill: currentColor; } .web2pdf-reader-mode { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: white; z-index: 2147483645; padding: 40px; overflow-y: auto; line-height: 1.6; font-family: Arial, sans-serif; } .web2pdf-reader-mode .content { max-width: 800px; margin: 0 auto; } .reader-close-button { position: fixed; top: 20px; right: 20px; width: 40px; height: 40px; border-radius: 20px; border: none; background: #f0f0f0; cursor: pointer; font-size: 24px; display: flex; align-items: center; justify-content: center; z-index: 2147483646; } .reader-close-button:hover { background: #e0e0e0; } .web2pdf-context-menu { position: fixed; background: white; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); display: none; z-index: 2147483645; min-width: 150px; } .context-menu-item { padding: 8px 12px; display: flex; align-items: center; gap: 8px; cursor: pointer; transition: background-color 0.2s; } .context-menu-item:hover { background-color: #f5f5f5; } .web2pdf-reader-mode .web2pdf-floating-button { z-index: 2147483647; } .web2pdf-content.editing { outline: 2px solid #4CAF50; padding: 10px; } .editing-toolbar { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: white; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 5px; z-index: 2147483646; display: flex; gap: 5px; } .editing-toolbar button { width: 30px; height: 30px; border: none; background: #f0f0f0; border-radius: 4px; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; } .editing-toolbar button:hover { background: #e0e0e0; } [contenteditable=true]:focus { outline: none; } .element-controls { position: absolute; top: 0; right: 0; display: none; background: rgba(0, 0, 0, 0.1); border-radius: 4px; } [contenteditable=true] *:hover > .element-controls { display: block; } .element-controls button { padding: 2px 6px; background: none; border: none; color: #ff4444; cursor: pointer; font-size: 16px; } .element-controls button:hover { background: rgba(255, 0, 0, 0.1); } .image-wrapper { position: relative; display: inline-block; } .image-resizer { position: absolute; right: -5px; bottom: -5px; width: 10px; height: 10px; background: #4CAF50; border-radius: 50%; cursor: se-resize; display: none; } [contenteditable=true] .image-wrapper:hover .image-resizer { display: block; } #switchLang { border-top: 1px solid #eee; margin-top: 5px; padding-top: 10px; } #switchLang svg { transform: scale(0.9); } `); // 原有的功能函数 // 将 content.js 的代码粘贴到这里 // 创建浮动按钮和菜单 function createFloatingButton() { // 检查是否已存在按钮 let button = document.querySelector('.web2pdf-floating-button'); if (button) { return button; } button = document.createElement('button'); button.className = 'web2pdf-floating-button'; button.innerHTML = ` `; // 设置初始位置 const savedPosition = GM_getValue('buttonPosition', { left: '20px', bottom: '20px' }); button.style.left = savedPosition.left; button.style.bottom = savedPosition.bottom; // 添加拖动功能 let isDragging = false; let currentX; let currentY; let initialX; let initialY; button.addEventListener('mousedown', function(e) { if (e.target.closest('.web2pdf-menu')) return; isDragging = true; button.style.transition = 'none'; const rect = button.getBoundingClientRect(); initialX = e.clientX - rect.left; initialY = e.clientY - rect.top; button.classList.add('dragging'); }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; const buttonRect = button.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; currentX = Math.max(0, Math.min(currentX, viewportWidth - buttonRect.width)); currentY = Math.max(0, Math.min(currentY, viewportHeight - buttonRect.height)); button.style.left = currentX + 'px'; button.style.top = currentY + 'px'; button.style.bottom = 'auto'; }); document.addEventListener('mouseup', function() { if (!isDragging) return; isDragging = false; button.style.transition = 'background-color 0.2s'; button.classList.remove('dragging'); GM_setValue('buttonPosition', { left: button.style.left, bottom: button.style.bottom }); }); button.addEventListener('click', showMenu); // 确保按钮被添加到页面 document.body.appendChild(button); console.log('Floating button created and added to page'); return button; } // 创建菜单 async function createMenu() { const menu = document.createElement('div'); menu.className = 'web2pdf-menu'; menu.innerHTML = ` `; document.body.appendChild(menu); // 添加事件监听 document.getElementById('readMode').addEventListener('click', toggleReadMode); document.getElementById('printPDF').addEventListener('click', printToPDF); document.getElementById('switchLang').addEventListener('click', async function() { const currentLang = await getUserLanguage(); const newLang = currentLang === 'zh' ? 'en' : 'zh'; await setUserLanguage(newLang); // 重新创建菜单以更新语言 const oldMenu = document.querySelector('.web2pdf-menu'); if (oldMenu) oldMenu.remove(); createMenu(); }); } // 显示/隐藏菜单 function showMenu(event) { event.stopPropagation(); const button = event.currentTarget; const menu = document.querySelector('.web2pdf-menu'); const buttonRect = button.getBoundingClientRect(); // 根据按钮位置调整菜单位置 if (buttonRect.left < window.innerWidth / 2) { // 按钮在左半边,菜单显示在按钮右边 menu.style.left = (buttonRect.right + 10) + 'px'; menu.style.right = 'auto'; } else { // 按钮在右半边,菜单显示在按钮左边 menu.style.right = (window.innerWidth - buttonRect.left + 10) + 'px'; menu.style.left = 'auto'; } // 垂直位置调整 const menuHeight = menu.offsetHeight || 100; // 预估高度 if (buttonRect.top + menuHeight > window.innerHeight) { // 如果菜单会超出底部,就显示在按钮上方 menu.style.bottom = (window.innerHeight - buttonRect.top + 10) + 'px'; menu.style.top = 'auto'; } else { // 否则显示在按钮下方 menu.style.top = buttonRect.top + 'px'; menu.style.bottom = 'auto'; } menu.classList.toggle('show'); // 点击其他地方关闭菜单 document.addEventListener('click', function closeMenu(e) { if (!menu.contains(e.target) && !e.target.closest('.web2pdf-floating-button')) { menu.classList.remove('show'); document.removeEventListener('click', closeMenu); } }); } // 切换阅读模式 async function toggleReadMode() { const existingReader = document.querySelector('.web2pdf-reader-mode'); if (existingReader) { existingReader.remove(); document.body.style.overflow = ''; return; } // 添加加载提示 const loadingTip = document.createElement('div'); loadingTip.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 20px; border-radius: 10px; z-index: 100000; `; loadingTip.textContent = await t('loading'); document.body.appendChild(loadingTip); try { const content = extractMainContent(); const tempContainer = document.createElement('div'); tempContainer.innerHTML = content; // 预加载图片,传入 loadingTip 参数 await preloadImages(tempContainer, loadingTip); // 创建阅读模式容器 const readerMode = document.createElement('div'); readerMode.className = 'web2pdf-reader-mode'; readerMode.innerHTML = tempContainer.innerHTML; // 添加关闭按钮 const closeButton = document.createElement('button'); closeButton.className = 'web2pdf-close-button'; closeButton.innerHTML = ` ESC `; closeButton.style.cssText = ` position: fixed; top: 20px; right: 20px; width: 40px; height: 40px; border-radius: 50%; border: none; background: #f0f0f0; cursor: pointer; font-size: 24px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); transition: background-color 0.3s, transform 0.3s; z-index: 2147483647; `; closeButton.addEventListener('mouseover', () => { closeButton.style.backgroundColor = '#e0e0e0'; closeButton.style.transform = 'scale(1.1)'; }); closeButton.addEventListener('mouseout', () => { closeButton.style.backgroundColor = '#f0f0f0'; closeButton.style.transform = 'scale(1)'; }); closeButton.addEventListener('click', () => { readerMode.remove(); document.body.style.overflow = ''; // 重新创建浮动按钮和菜单 createFloatingButton(); createMenu(); }); readerMode.appendChild(closeButton); // 添加 ESC 快捷键支持 document.addEventListener('keydown', function escKeyHandler(e) { if (e.key === 'Escape') { readerMode.remove(); document.body.style.overflow = ''; // 重新创建浮动按钮和菜单 createFloatingButton(); createMenu(); document.removeEventListener('keydown', escKeyHandler); } }); document.body.appendChild(readerMode); document.body.style.overflow = 'hidden'; document.querySelector('.web2pdf-menu')?.classList.remove('show'); // 获取原始按钮的位置 const originalButton = document.querySelector('.web2pdf-floating-button'); const originalMenu = document.querySelector('.web2pdf-menu'); const buttonPosition = originalButton ? { left: originalButton.style.left, top: originalButton.style.top, bottom: originalButton.style.bottom } : null; if (originalButton) originalButton.remove(); if (originalMenu) originalMenu.remove(); // 在阅读模式中创建新的浮动按钮,使用相同的位置 const button = document.createElement('button'); button.className = 'web2pdf-floating-button'; button.innerHTML = ` `; // 应用保存的位置或使用默认位置 const savedPosition = GM_getValue('buttonPosition', { left: '20px', top: '20px' }); button.style.left = savedPosition.left; button.style.top = savedPosition.top; // 添加拖动功能 let isDragging = false; let currentX; let currentY; let initialX; let initialY; button.addEventListener('mousedown', function(e) { if (e.target.closest('.web2pdf-menu')) return; isDragging = true; button.style.transition = 'none'; const rect = button.getBoundingClientRect(); initialX = e.clientX - rect.left; initialY = e.clientY - rect.top; button.classList.add('dragging'); }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; const buttonRect = button.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; currentX = Math.max(0, Math.min(currentX, viewportWidth - buttonRect.width)); currentY = Math.max(0, Math.min(currentY, viewportHeight - buttonRect.height)); button.style.left = currentX + 'px'; button.style.top = currentY + 'px'; button.style.bottom = 'auto'; }); document.addEventListener('mouseup', function() { if (!isDragging) return; isDragging = false; button.style.transition = 'background-color 0.2s'; button.classList.remove('dragging'); // 保存新位置到 storage,这样主页面的按钮也会使用这个位置 GM_setValue('buttonPosition', { left: button.style.left, top: button.style.top }); }); button.addEventListener('click', showMenu); readerMode.appendChild(button); // 在阅读模式中创建新的菜单 const menu = document.createElement('div'); menu.className = 'web2pdf-menu'; menu.innerHTML = ` `; readerMode.appendChild(menu); // 为菜单项添加事件监听 menu.querySelector('#toggleEdit').addEventListener('click', () => { toggleEditMode(readerMode); menu.classList.remove('show'); }); menu.querySelector('#printPDF').addEventListener('click', () => { printToPDF(); menu.classList.remove('show'); }); menu.querySelector('#switchLang').addEventListener('click', async function() { const currentLang = await getUserLanguage(); const newLang = currentLang === 'zh' ? 'en' : 'zh'; await setUserLanguage(newLang); // 重新创建菜单以更新语言 const oldMenu = readerMode.querySelector('.web2pdf-menu'); if (oldMenu) oldMenu.remove(); await createReaderModeMenu(readerMode); }); // 显示成功提示 loadingTip.style.background = 'rgba(76, 175, 80, 0.9)'; loadingTip.textContent = await t('loadComplete'); setTimeout(() => { loadingTip.remove(); // 自动开启编辑模式 toggleEditMode(readerMode); }, 2000); } catch (error) { console.error('Error loading content:', error); loadingTip.style.background = 'rgba(244, 67, 54, 0.9)'; loadingTip.textContent = await t('loadFailed'); setTimeout(() => { loadingTip.remove(); }, 2000); } } // 添加一个新函数来创建阅读模式的菜单 async function createReaderModeMenu(readerMode) { const menu = document.createElement('div'); menu.className = 'web2pdf-menu'; menu.innerHTML = ` `; readerMode.appendChild(menu); // 添加事件监听 menu.querySelector('#toggleEdit').addEventListener('click', () => { toggleEditMode(readerMode); menu.classList.remove('show'); }); menu.querySelector('#printPDF').addEventListener('click', () => { printToPDF(); menu.classList.remove('show'); }); menu.querySelector('#switchLang').addEventListener('click', async function() { const currentLang = await getUserLanguage(); const newLang = currentLang === 'zh' ? 'en' : 'zh'; await setUserLanguage(newLang); // 重新创建菜单以更新语言 const oldMenu = readerMode.querySelector('.web2pdf-menu'); if (oldMenu) oldMenu.remove(); await createReaderModeMenu(readerMode); }); return menu; } // 添加预加载图片的函数 async function preloadImages(container, loadingTip) { const images = container.getElementsByTagName('img'); const totalImages = images.length; const imageLoadPromises = []; let loadedCount = 0; // 如果没有图片需要加载 if (totalImages === 0) { if (loadingTip) { loadingTip.innerHTML = '没有找到需要加载的图片'; await new Promise(resolve => setTimeout(resolve, 1000)); } return; } // 创建进度条容器 if (loadingTip) { loadingTip.innerHTML = `
正在加载图片 (0/${totalImages})
准备加载...
`; } // 处理所有图片 Array.from(images).forEach((img, index) => { // 处理懒加载图片 const originalSrc = img.getAttribute('data-src') || img.getAttribute('data-original') || img.getAttribute('data-lazy-src') || img.getAttribute('data-lazy-loaded') || img.getAttribute('data-url') || img.src; if (originalSrc) { // 创建一个加载Promise const loadPromise = new Promise((resolve) => { const tempImg = new Image(); tempImg.onload = () => { loadedCount++; if (loadingTip) { // 更新加载进度 const progress = Math.round((loadedCount / totalImages) * 100); const progressBar = loadingTip.querySelector('.progress'); const loadingDetails = loadingTip.querySelector('.loading-details'); loadingTip.querySelector('div').textContent = `正在加载图片 (${loadedCount}/${totalImages})`; if (progressBar) { progressBar.style.width = `${progress}%`; } if (loadingDetails) { loadingDetails.textContent = `正在加载: ${originalSrc.substring(0, 50)}...`; } } img.src = originalSrc; // 设置实际图片源 img.removeAttribute('data-src'); img.removeAttribute('data-original'); img.removeAttribute('data-lazy-src'); img.removeAttribute('data-lazy-loaded'); img.removeAttribute('data-url'); img.removeAttribute('loading'); // 移除懒加载属性 img.classList.remove('lazyload', 'lazy'); // 移除懒加载类 resolve(); }; tempImg.onerror = () => { loadedCount++; if (loadingTip) { // 更新加载进度,即使加载失败 const progress = Math.round((loadedCount / totalImages) * 100); const progressBar = loadingTip.querySelector('.progress'); const loadingDetails = loadingTip.querySelector('.loading-details'); loadingTip.querySelector('div').textContent = `正在加载图片 (${loadedCount}/${totalImages})`; if (progressBar) { progressBar.style.width = `${progress}%`; } if (loadingDetails) { loadingDetails.textContent = `加载失败: ${originalSrc.substring(0, 50)}...`; } } console.warn('Failed to load image:', originalSrc); resolve(); }; // 开始加载图片 tempImg.src = originalSrc; }); imageLoadPromises.push(loadPromise); } }); // 等待所有图片加载完成 await Promise.all(imageLoadPromises); // 显示加载完成信息 if (loadingTip) { loadingTip.innerHTML = `
图片加载完成 (${loadedCount}/${totalImages})
所有图片加载完成
`; // 等待一会儿再消失,让用户看到完成状态 await new Promise(resolve => setTimeout(resolve, 1000)); } } // 添加 A4 尺寸计算和内容分页函数 function calculateA4Pages(container) { // A4 尺寸(以像素为单位,假设 96 DPI) // A4 纸张尺寸为 210mm x 297mm const A4_WIDTH_PX = 794; // 210mm = 794px const A4_HEIGHT_PX = 1123; // 297mm = 1123px const MARGIN = 40; // 页边距 const EFFECTIVE_HEIGHT = A4_HEIGHT_PX - (MARGIN * 2); // 创建一个临时容器来计算布局 const tempDiv = document.createElement('div'); tempDiv.style.cssText = ` position: absolute; top: -9999px; left: -9999px; width: ${A4_WIDTH_PX - (MARGIN * 2)}px; visibility: hidden; `; document.body.appendChild(tempDiv); // 克隆内容到临时容器 const contentClone = container.cloneNode(true); tempDiv.appendChild(contentClone); // 处理所有图片,使其等宽 const images = tempDiv.getElementsByTagName('img'); Array.from(images).forEach(img => { img.style.width = '100%'; img.style.height = 'auto'; // 确保图片容器也是等宽的 if (img.parentElement.classList.contains('img-container')) { img.parentElement.style.width = '100%'; } }); // 存储分页后的内容 const pages = []; let currentPage = document.createElement('div'); let currentHeight = 0; // 遍历所有子元素 Array.from(contentClone.children).forEach(element => { const elementHeight = element.offsetHeight; const isImage = element.tagName.toLowerCase() === 'img' || element.querySelector('img') !== null; // 如果当前元素是图片或包含图片,且会导致超出页面高度 if (isImage && currentHeight + elementHeight > EFFECTIVE_HEIGHT) { // 将当前页添加到页面集合中 if (currentPage.children.length > 0) { pages.push(currentPage); } // 创建新页面,并将图片放在新页面的开始 currentPage = document.createElement('div'); currentPage.appendChild(element.cloneNode(true)); currentHeight = elementHeight; } // 如果是普通元素,且会导致超出页面高度 else if (currentHeight + elementHeight > EFFECTIVE_HEIGHT) { // 将当前页添加到页面集合中 pages.push(currentPage); // 创建新页面 currentPage = document.createElement('div'); currentPage.appendChild(element.cloneNode(true)); currentHeight = elementHeight; } // 如果不会超出页面高度 else { currentPage.appendChild(element.cloneNode(true)); currentHeight += elementHeight; } }); // 添加最后一页 if (currentPage.children.length > 0) { pages.push(currentPage); } // 清理临时元素 document.body.removeChild(tempDiv); return pages; } // 修改 printToPDF 函数 async function printToPDF() { // 创建一个临时容器来存放打印内容 const tempContainer = document.createElement('div'); if (document.querySelector('.web2pdf-reader-mode')) { // 如果在阅读模式下,复制阅读模式的内容 const readerContent = document.querySelector('.web2pdf-reader-mode').cloneNode(true); // 清理所有UI元素 const elementsToRemove = [ '.web2pdf-floating-button', '.web2pdf-menu', '.reader-close-button', '.editing-toolbar', '.element-controls', '.image-resizer', 'button' ]; elementsToRemove.forEach(selector => { readerContent.querySelectorAll(selector).forEach(el => el.remove()); }); // 只保留主要内容 const mainContent = readerContent.querySelector('.web2pdf-content'); if (mainContent) { // 移除所有编辑相关的属性和类 mainContent.querySelectorAll('*').forEach(el => { el.removeAttribute('contenteditable'); el.classList.remove('editing'); if (el.style.position === 'relative') { el.style.position = ''; } }); // 处理图片包装器 mainContent.querySelectorAll('.image-wrapper').forEach(wrapper => { const img = wrapper.querySelector('img'); if (img) { const imgContainer = document.createElement('div'); imgContainer.className = 'img-container'; wrapper.parentNode.insertBefore(imgContainer, wrapper); imgContainer.appendChild(img); wrapper.remove(); } }); tempContainer.appendChild(mainContent); } } else { // 如果不在阅读模式下,使用提取的内容 tempContainer.innerHTML = extractMainContent(); // 为所有图片添加包装容器 tempContainer.querySelectorAll('img').forEach(img => { const imgContainer = document.createElement('div'); imgContainer.className = 'img-container'; img.parentNode.insertBefore(imgContainer, img); imgContainer.appendChild(img); }); } // 添加加载提示 const loadingTip = document.createElement('div'); loadingTip.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 20px; border-radius: 10px; z-index: 100000; `; loadingTip.textContent = '正在加载图片,请稍候...'; document.body.appendChild(loadingTip); try { // 预加载所有图片 await preloadImages(tempContainer); // 计算分页 const pages = calculateA4Pages(tempContainer); const printWindow = window.open('', '_blank'); printWindow.document.write(` ${document.title} ${pages.map(page => `
${page.innerHTML}
`).join('')} `); printWindow.document.close(); // 等待一段时间确保图片完全加载和布局完成 setTimeout(() => { printWindow.print(); loadingTip.remove(); }, 2000); } catch (error) { console.error('Error during PDF generation:', error); alert('生成PDF时出错,请重试'); } finally { loadingTip.remove(); } } // 添加清理打印内容的函数 function cleanupForPrint(element) { // 移除所有UI相关元素 const removeSelectors = [ '.web2pdf-floating-button', '.web2pdf-menu', '.reader-close-button', '.editing-toolbar', 'button', '[contenteditable]' ]; removeSelectors.forEach(selector => { element.querySelectorAll(selector).forEach(el => el.remove()); }); // 移除编辑相关的属性和类 element.querySelectorAll('*').forEach(el => { el.removeAttribute('contenteditable'); el.classList.remove('editing'); }); // 返回清理后的HTML return element.innerHTML; } // 提取主要内容 function extractMainContent() { // 针对32r.com的特定选择器 const specificSelectors = [ '.details_content', // 详情内容区 'article', // 文章区 '[role="article"]', // 文章角色 '.article-content', // 文章内容 '#article-content', // 文章内容ID '.content', // 内容区 '.main-content', // 主要内容 'main', // 主要区域 '[role="main"]' // 主要区域角色 ]; let mainContent = null; // 首先尝试使用特定选择器 for (const selector of specificSelectors) { const element = document.querySelector(selector); if (element && element.textContent.trim().length > 100) { // 检查内容是否包含导航等不相关内容 const navigationText = element.textContent.toLowerCase(); if (!navigationText.includes('网站导航') && !navigationText.includes('手机版') && !navigationText.includes('关于我们')) { mainContent = element; break; } } } // 如果没找到合适的容器,尝试通过内容密度分析 if (!mainContent) { const allElements = document.querySelectorAll('div, article, section'); let maxTextDensity = 0; let bestElement = null; allElements.forEach(element => { // 计算文本密度(文本长度/HTML长度的比率) const textLength = element.textContent.trim().length; const htmlLength = element.innerHTML.length; const density = textLength / htmlLength; // 检查是否包含标题和正文特征 const hasTitle = element.querySelector('h1, h2, h3'); const hasContent = textLength > 500; // 假设正文至少500字符 if (density > maxTextDensity && hasTitle && hasContent) { maxTextDensity = density; bestElement = element; } }); if (bestElement) { mainContent = bestElement; } } // 如果仍然没找到,使用body作为后备方案 if (!mainContent) { mainContent = document.body; } // 创建一个新的容器 const container = document.createElement('div'); container.className = 'web2pdf-content'; // 添加标题 const title = document.createElement('h1'); title.style.marginBottom = '20px'; title.textContent = document.title.split('_')[0].split('-')[0].trim(); // 清理标题中的额外文本 container.appendChild(title); // 克隆内容 const contentClone = mainContent.cloneNode(true); // 清理不需要的元素 const removeSelectors = [ 'script', 'style', 'iframe', 'nav', 'header', 'footer', '.advertisement', '.ads', '.social-share', '.comments', '#comments', '.sidebar', '.related-posts', '.nav', '.navigation', '.menu', '.share', '.social', 'button', 'input', 'form', '.web2pdf-floating-button', '.web2pdf-menu', '.pagination', '.breadcrumb', '.category-list', '.tag-list', '.footer', '.header', '.site-nav', '.site-header', '.site-footer', '[role="complementary"]', '[role="navigation"]' ]; removeSelectors.forEach(selector => { contentClone.querySelectorAll(selector).forEach(el => { if (el.parentNode) { el.parentNode.removeChild(el); } }); }); // 处理图片 contentClone.querySelectorAll('img').forEach(img => { if (img.src) { img.src = img.src; // 确保使用完整URL img.style.maxWidth = '100%'; img.style.height = 'auto'; img.removeAttribute('srcset'); // 移除响应式图片源 } }); // 添加内容 container.appendChild(contentClone); return container.outerHTML; } // 添加编辑模式相关的函数 async function toggleEditMode(readerMode) { const content = readerMode.querySelector('.web2pdf-content') || readerMode; const isEditing = content.getAttribute('contenteditable') === 'true'; if (isEditing) { // 退出编辑模式 content.setAttribute('contenteditable', 'false'); content.classList.remove('editing'); removeEditingToolbar(); removeElementControls(); } else { // 进入编辑模式 content.setAttribute('contenteditable', 'true'); content.classList.add('editing'); await createEditingToolbar(readerMode); await addElementControls(content); } } // 修改创建编辑工具栏函数 async function createEditingToolbar(readerMode) { const existingToolbar = document.querySelector('.editing-toolbar'); if (existingToolbar) { existingToolbar.remove(); } const toolbar = document.createElement('div'); toolbar.className = 'editing-toolbar'; toolbar.innerHTML = ` `; toolbar.addEventListener('click', async (e) => { const button = e.target.closest('button'); if (!button) return; e.preventDefault(); const command = button.dataset.command; const value = button.dataset.value; if (command === 'createLink') { const url = prompt(await t('enterLink'), 'http://'); if (url) document.execCommand(command, false, url); } else { document.execCommand(command, false, value); } }); readerMode.appendChild(toolbar); } // 移除编辑工具栏 function removeEditingToolbar() { const toolbar = document.querySelector('.editing-toolbar'); if (toolbar) { toolbar.remove(); } } // 修改添加元素控制的函数为异步 async function addElementControls(container) { const blockElements = container.querySelectorAll('p, div, section, article, aside, h1, h2, h3, h4, h5, h6'); for (const element of blockElements) { if (!element.classList.contains('web2pdf-content')) { await addElementControl(element); } } // 为图片添加调整大小的功能 const images = container.querySelectorAll('img'); images.forEach(img => addImageResize(img)); } // 修改为单个元素添加控制按钮的函数 async function addElementControl(element) { const controls = document.createElement('div'); controls.className = 'element-controls'; controls.innerHTML = ` `; element.style.position = 'relative'; element.appendChild(controls); controls.querySelector('.delete-btn').addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); // 直接删除元素,不需要确认 element.remove(); }); } // 为图片添加缩放功能 function addImageResize(img) { const wrapper = document.createElement('div'); wrapper.className = 'image-wrapper'; img.parentNode.insertBefore(wrapper, img); wrapper.appendChild(img); const resizer = document.createElement('div'); resizer.className = 'image-resizer'; wrapper.appendChild(resizer); let startX, startY, startWidth, startHeight; resizer.addEventListener('mousedown', initResize); function initResize(e) { startX = e.clientX; startY = e.clientY; startWidth = img.offsetWidth; startHeight = img.offsetHeight; document.addEventListener('mousemove', resize); document.addEventListener('mouseup', stopResize); } function resize(e) { const width = startWidth + (e.clientX - startX); const height = startHeight * (width / startWidth); // 保持宽高比 img.style.width = width + 'px'; img.style.height = height + 'px'; } function stopResize() { document.removeEventListener('mousemove', resize); document.removeEventListener('mouseup', stopResize); } } // 移除元素控制 function removeElementControls() { document.querySelectorAll('.element-controls').forEach(control => control.remove()); document.querySelectorAll('.image-resizer').forEach(resizer => resizer.remove()); document.querySelectorAll('.image-wrapper').forEach(wrapper => { const img = wrapper.querySelector('img'); if (img) { wrapper.parentNode.insertBefore(img, wrapper); wrapper.remove(); } }); } // 修改初始化函数 function initializeExtension() { try { createFloatingButton(); createMenu(); console.log('Web2PDF initialized successfully'); } catch (error) { console.error('Failed to initialize Web2PDF:', error); } } // 等待页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeExtension); } else { initializeExtension(); } // 添加错误处理 window.addEventListener('error', function(e) { console.error('Web2PDF error:', e.error); }); })();