PDF Viewer Embedder

Embeds PDFs directly in the page instead of downloading if it is stored on the same domain

目前为 2025-04-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name PDF Viewer Embedder
  3. // @namespace *
  4. // @version 1.0
  5. // @author zinchaiku
  6. // @description Embeds PDFs directly in the page instead of downloading if it is stored on the same domain
  7. // @match *://*/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. // Helper to create and insert the PDF viewer
  16. function showPDF(blobUrl) {
  17. // Remove any existing viewer
  18. let existing = document.getElementById('tampermonkey-pdf-viewer');
  19. if (existing) existing.remove();
  20.  
  21. // Create overlay
  22. const overlay = document.createElement('div');
  23. overlay.id = 'tampermonkey-pdf-viewer';
  24. overlay.style.position = 'fixed';
  25. overlay.style.top = 0;
  26. overlay.style.left = 0;
  27. overlay.style.width = '100vw';
  28. overlay.style.height = '100vh';
  29. overlay.style.background = 'rgba(0,0,0,0.8)';
  30. overlay.style.zIndex = 99999;
  31. overlay.style.display = 'flex';
  32. overlay.style.alignItems = 'center';
  33. overlay.style.justifyContent = 'center';
  34.  
  35. // Create iframe
  36. const iframe = document.createElement('iframe');
  37. iframe.src = blobUrl;
  38. iframe.title = "Embedded PDF Viewer";
  39. iframe.style.width = '80vw';
  40. iframe.style.height = '90vh';
  41. iframe.style.border = 'none';
  42. iframe.style.background = '#fff';
  43. overlay.appendChild(iframe);
  44.  
  45. // Close button
  46. const closeBtn = document.createElement('button');
  47. closeBtn.textContent = 'Close PDF';
  48. closeBtn.style.position = 'absolute';
  49. closeBtn.style.top = '20px';
  50. closeBtn.style.right = '40px';
  51. closeBtn.style.zIndex = 100000;
  52. closeBtn.style.padding = '2px 6px';
  53. closeBtn.style.fontSize = '1.2em';
  54. closeBtn.onclick = () => {
  55. overlay.remove();
  56. URL.revokeObjectURL(blobUrl);
  57. };
  58. overlay.appendChild(closeBtn);
  59.  
  60. document.body.appendChild(overlay);
  61. }
  62.  
  63. // Attach to all download links (adjust selector as needed)
  64. function attachPDFInterceptors() {
  65. document.querySelectorAll('a[href$=".pdf"], a[href*="_download.html"]').forEach(link => {
  66. // Avoid double-binding
  67. if (link.dataset.tmPdfBound) return;
  68. link.dataset.tmPdfBound = "1";
  69.  
  70. link.addEventListener('click', function(e) {
  71. e.preventDefault();
  72.  
  73. const linkHost = new URL(link.href).host;
  74. const sameOrigin = linkHost === window.location.host;
  75.  
  76. // Use fetch only for same-origin links (like ILIAS downloads)
  77. if (sameOrigin) {
  78. fetch(link.href, { credentials: 'include' })
  79. .then(res => {
  80. const contentType = res.headers.get('Content-Type') || '';
  81. if (contentType.includes('pdf')) {
  82. return res.blob().then(blob => {
  83. const blobUrl = URL.createObjectURL(blob);
  84. showPDF(blobUrl);
  85. });
  86. } else {
  87. window.location.href = link.href;
  88. }
  89. })
  90. .catch(err => {
  91. alert("Could not open PDF: " + err);
  92. window.location.href = link.href;
  93. });
  94. } else {
  95. // External .pdf link — open in a new tab
  96. window.open(link.href, '_blank');
  97. }
  98. });
  99. });
  100. }
  101. window.addEventListener('keydown', (e) => {
  102. if (e.key === 'Escape') {
  103. const viewer = document.getElementById('tampermonkey-pdf-viewer');
  104. if (viewer) {
  105. const iframe = viewer.querySelector('iframe');
  106. if (iframe?.src?.startsWith('blob:')) {
  107. URL.revokeObjectURL(iframe.src);
  108. }
  109. viewer.remove();
  110. }
  111. }
  112. });
  113.  
  114. // Run on page load and after AJAX navigation (if any)
  115. attachPDFInterceptors();
  116. // Optional: observe DOM changes for dynamically loaded links
  117. const observer = new MutationObserver(attachPDFInterceptors);
  118. observer.observe(document.body, { childList: true, subtree: true });
  119. })();