// ==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 = `