nodebb-plugin-pdf-secure 1.2.6 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/library.js +35 -2
- package/package.json +1 -1
- package/plugin.json +4 -0
- package/static/lib/main.js +136 -94
- package/static/viewer.html +11 -0
package/library.js
CHANGED
|
@@ -131,8 +131,8 @@ plugin.filterMetaTags = async (hookData) => {
|
|
|
131
131
|
|
|
132
132
|
// Filter out PDF-related meta tags
|
|
133
133
|
hookData.tags = hookData.tags.filter(tag => {
|
|
134
|
-
// Remove og:image if it contains .pdf
|
|
135
|
-
if (tag.property === 'og:image' && tag.content && tag.content.toLowerCase().includes('.pdf')) {
|
|
134
|
+
// Remove og:image and og:image:url if it contains .pdf
|
|
135
|
+
if ((tag.property === 'og:image' || tag.property === 'og:image:url') && tag.content && tag.content.toLowerCase().includes('.pdf')) {
|
|
136
136
|
return false;
|
|
137
137
|
}
|
|
138
138
|
// Remove twitter:image if it contains .pdf
|
|
@@ -154,4 +154,37 @@ plugin.filterMetaTags = async (hookData) => {
|
|
|
154
154
|
return hookData;
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
+
// Transform PDF links to secure placeholders (server-side)
|
|
158
|
+
// This hides PDF URLs from: page source, API, RSS, ActivityPub
|
|
159
|
+
plugin.transformPdfLinks = async (data) => {
|
|
160
|
+
if (!data || !data.postData || !data.postData.content) {
|
|
161
|
+
return data;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Regex to match PDF links: <a href="...xxx.pdf">text</a>
|
|
165
|
+
// Captures: full URL path, filename, link text
|
|
166
|
+
const pdfLinkRegex = /<a\s+[^>]*href=["']([^"']*\/([^"'\/]+\.pdf))["'][^>]*>([^<]*)<\/a>/gi;
|
|
167
|
+
|
|
168
|
+
data.postData.content = data.postData.content.replace(pdfLinkRegex, (match, fullPath, filename, linkText) => {
|
|
169
|
+
// Decode filename to prevent double encoding (URL may already be encoded)
|
|
170
|
+
let decodedFilename;
|
|
171
|
+
try { decodedFilename = decodeURIComponent(filename); }
|
|
172
|
+
catch (e) { decodedFilename = filename; }
|
|
173
|
+
|
|
174
|
+
// Sanitize for HTML attribute
|
|
175
|
+
const safeFilename = decodedFilename.replace(/[<>"'&]/g, '');
|
|
176
|
+
const displayName = linkText.trim() || safeFilename;
|
|
177
|
+
|
|
178
|
+
// Return secure placeholder div instead of actual link
|
|
179
|
+
return `<div class="pdf-secure-placeholder" data-filename="${safeFilename}">
|
|
180
|
+
<svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:#e81224;vertical-align:middle;margin-right:8px;">
|
|
181
|
+
<path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
|
|
182
|
+
</svg>
|
|
183
|
+
<span>${displayName}</span>
|
|
184
|
+
</div>`;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return data;
|
|
188
|
+
};
|
|
189
|
+
|
|
157
190
|
module.exports = plugin;
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/static/lib/main.js
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
// Main plugin logic - PDF links become inline embedded viewers with lazy loading + queue
|
|
4
4
|
(async function () {
|
|
5
|
+
// ============================================
|
|
6
|
+
// PDF.js PRELOAD - Cache CDN assets before iframe loads
|
|
7
|
+
// ============================================
|
|
8
|
+
(function preloadPdfJs() {
|
|
9
|
+
const preloads = [
|
|
10
|
+
{ href: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js', as: 'script' },
|
|
11
|
+
{ href: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css', as: 'style' }
|
|
12
|
+
];
|
|
13
|
+
preloads.forEach(({ href, as }) => {
|
|
14
|
+
if (!document.querySelector(`link[href="${href}"]`)) {
|
|
15
|
+
const link = document.createElement('link');
|
|
16
|
+
link.rel = 'preload';
|
|
17
|
+
link.href = href;
|
|
18
|
+
link.as = as;
|
|
19
|
+
link.crossOrigin = 'anonymous';
|
|
20
|
+
document.head.appendChild(link);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
})();
|
|
24
|
+
|
|
5
25
|
// Loading queue - only load one PDF at a time
|
|
6
26
|
const loadQueue = [];
|
|
7
27
|
let isLoading = false;
|
|
@@ -9,6 +29,9 @@
|
|
|
9
29
|
|
|
10
30
|
// Listen for postMessage from iframe when PDF is fully rendered
|
|
11
31
|
window.addEventListener('message', function (event) {
|
|
32
|
+
// Security: Only accept messages from same origin
|
|
33
|
+
if (event.origin !== window.location.origin) return;
|
|
34
|
+
|
|
12
35
|
if (event.data && event.data.type === 'pdf-secure-ready') {
|
|
13
36
|
console.log('[PDF-Secure] Queue: PDF ready -', event.data.filename);
|
|
14
37
|
if (currentResolver) {
|
|
@@ -59,8 +82,20 @@
|
|
|
59
82
|
var postContents = document.querySelectorAll('[component="post/content"]');
|
|
60
83
|
|
|
61
84
|
postContents.forEach(function (content) {
|
|
62
|
-
|
|
85
|
+
// NEW: Detect server-rendered secure placeholders (hides URL from source)
|
|
86
|
+
var placeholders = content.querySelectorAll('.pdf-secure-placeholder');
|
|
87
|
+
placeholders.forEach(function (placeholder) {
|
|
88
|
+
if (placeholder.dataset.pdfSecureProcessed) return;
|
|
89
|
+
placeholder.dataset.pdfSecureProcessed = 'true';
|
|
63
90
|
|
|
91
|
+
var filename = placeholder.dataset.filename;
|
|
92
|
+
var displayName = placeholder.querySelector('span')?.textContent || filename;
|
|
93
|
+
|
|
94
|
+
createPdfViewer(placeholder, filename, displayName);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// FALLBACK: Detect old-style PDF links (for backwards compatibility)
|
|
98
|
+
var pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
|
|
64
99
|
pdfLinks.forEach(function (link) {
|
|
65
100
|
if (link.dataset.pdfSecure) return;
|
|
66
101
|
link.dataset.pdfSecure = 'true';
|
|
@@ -68,112 +103,119 @@
|
|
|
68
103
|
var href = link.getAttribute('href');
|
|
69
104
|
var parts = href.split('/');
|
|
70
105
|
var filename = parts[parts.length - 1];
|
|
106
|
+
var displayName = link.textContent || filename;
|
|
71
107
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
108
|
+
createPdfViewer(link, filename, displayName);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function createPdfViewer(targetElement, filename, displayName) {
|
|
114
|
+
|
|
115
|
+
// Create container
|
|
116
|
+
var container = document.createElement('div');
|
|
117
|
+
container.className = 'pdf-secure-embed';
|
|
118
|
+
container.style.cssText = 'margin:16px 0;border-radius:12px;overflow:hidden;background:#1f1f1f;border:1px solid rgba(255,255,255,0.1);box-shadow:0 4px 20px rgba(0,0,0,0.25);';
|
|
119
|
+
|
|
120
|
+
// Header
|
|
121
|
+
var header = document.createElement('div');
|
|
122
|
+
header.className = 'pdf-secure-embed-header';
|
|
123
|
+
header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:linear-gradient(135deg,#2d2d2d 0%,#252525 100%);border-bottom:1px solid rgba(255,255,255,0.08);';
|
|
124
|
+
|
|
125
|
+
var title = document.createElement('div');
|
|
126
|
+
title.className = 'pdf-secure-embed-title';
|
|
127
|
+
title.style.cssText = 'display:flex;align-items:center;gap:10px;color:#fff;font-size:14px;font-weight:500;';
|
|
128
|
+
|
|
129
|
+
var icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
130
|
+
icon.setAttribute('viewBox', '0 0 24 24');
|
|
131
|
+
icon.style.cssText = 'width:20px;height:20px;min-width:20px;max-width:20px;fill:#e81224;flex-shrink:0;';
|
|
132
|
+
icon.innerHTML = '<path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>';
|
|
133
|
+
|
|
134
|
+
var nameSpan = document.createElement('span');
|
|
135
|
+
nameSpan.style.cssText = 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:400px;';
|
|
136
|
+
try { nameSpan.textContent = decodeURIComponent(displayName); }
|
|
137
|
+
catch (e) { nameSpan.textContent = displayName; }
|
|
138
|
+
|
|
139
|
+
title.appendChild(icon);
|
|
140
|
+
title.appendChild(nameSpan);
|
|
141
|
+
|
|
142
|
+
// Actions
|
|
143
|
+
var actions = document.createElement('div');
|
|
144
|
+
actions.style.cssText = 'display:flex;gap:8px;';
|
|
145
|
+
|
|
146
|
+
var fullscreenBtn = document.createElement('button');
|
|
147
|
+
fullscreenBtn.className = 'pdf-secure-fullscreen-btn';
|
|
148
|
+
fullscreenBtn.title = 'Tam Ekran';
|
|
149
|
+
fullscreenBtn.style.cssText = 'display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:rgba(255,255,255,0.08);border:none;border-radius:6px;cursor:pointer;';
|
|
150
|
+
|
|
151
|
+
var fullscreenIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
152
|
+
fullscreenIcon.setAttribute('viewBox', '0 0 24 24');
|
|
153
|
+
fullscreenIcon.style.cssText = 'width:18px;height:18px;min-width:18px;max-width:18px;fill:#fff;';
|
|
154
|
+
fullscreenIcon.innerHTML = '<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>';
|
|
155
|
+
fullscreenBtn.appendChild(fullscreenIcon);
|
|
156
|
+
actions.appendChild(fullscreenBtn);
|
|
157
|
+
|
|
158
|
+
header.appendChild(title);
|
|
159
|
+
header.appendChild(actions);
|
|
160
|
+
container.appendChild(header);
|
|
161
|
+
|
|
162
|
+
// Body with loading placeholder
|
|
163
|
+
var iframeWrapper = document.createElement('div');
|
|
164
|
+
iframeWrapper.className = 'pdf-secure-embed-body';
|
|
165
|
+
iframeWrapper.style.cssText = 'position:relative;width:100%;height:600px;background:#525659;';
|
|
166
|
+
|
|
167
|
+
// Loading placeholder - ALWAYS VISIBLE until PDF ready (z-index: 10)
|
|
168
|
+
var loadingPlaceholder = document.createElement('div');
|
|
169
|
+
loadingPlaceholder.className = 'pdf-loading-placeholder';
|
|
170
|
+
loadingPlaceholder.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#2d2d2d;color:#fff;gap:16px;z-index:10;transition:opacity 0.3s;';
|
|
171
|
+
loadingPlaceholder.innerHTML = `
|
|
128
172
|
<svg viewBox="0 0 24 24" style="width:48px;height:48px;fill:#555;">
|
|
129
173
|
<path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
|
|
130
174
|
</svg>
|
|
131
175
|
<div class="pdf-loading-text" style="font-size:14px;color:#a0a0a0;">Sırada bekliyor...</div>
|
|
132
176
|
<style>@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}</style>
|
|
133
177
|
`;
|
|
134
|
-
|
|
178
|
+
iframeWrapper.appendChild(loadingPlaceholder);
|
|
135
179
|
|
|
136
|
-
|
|
180
|
+
container.appendChild(iframeWrapper);
|
|
137
181
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
182
|
+
// Fullscreen handler
|
|
183
|
+
fullscreenBtn.addEventListener('click', function () {
|
|
184
|
+
var iframe = iframeWrapper.querySelector('iframe');
|
|
185
|
+
if (iframe) {
|
|
186
|
+
if (iframe.requestFullscreen) iframe.requestFullscreen();
|
|
187
|
+
else if (iframe.webkitRequestFullscreen) iframe.webkitRequestFullscreen();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
targetElement.replaceWith(container);
|
|
192
|
+
|
|
193
|
+
// LAZY LOADING with Intersection Observer + Queue
|
|
194
|
+
var observer = new IntersectionObserver(function (entries) {
|
|
195
|
+
entries.forEach(function (entry) {
|
|
196
|
+
if (entry.isIntersecting) {
|
|
197
|
+
// Update placeholder to show loading state
|
|
198
|
+
var textEl = loadingPlaceholder.querySelector('.pdf-loading-text');
|
|
199
|
+
if (textEl) textEl.textContent = 'PDF Yükleniyor...';
|
|
200
|
+
|
|
201
|
+
var svgEl = loadingPlaceholder.querySelector('svg');
|
|
202
|
+
if (svgEl) {
|
|
203
|
+
svgEl.style.fill = '#0078d4';
|
|
204
|
+
svgEl.style.animation = 'spin 1s linear infinite';
|
|
205
|
+
svgEl.innerHTML = '<path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"/>';
|
|
144
206
|
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
link.replaceWith(container);
|
|
148
|
-
|
|
149
|
-
// LAZY LOADING with Intersection Observer + Queue
|
|
150
|
-
var observer = new IntersectionObserver(function (entries) {
|
|
151
|
-
entries.forEach(function (entry) {
|
|
152
|
-
if (entry.isIntersecting) {
|
|
153
|
-
// Update placeholder to show loading state
|
|
154
|
-
var textEl = loadingPlaceholder.querySelector('.pdf-loading-text');
|
|
155
|
-
if (textEl) textEl.textContent = 'PDF Yükleniyor...';
|
|
156
|
-
|
|
157
|
-
var svgEl = loadingPlaceholder.querySelector('svg');
|
|
158
|
-
if (svgEl) {
|
|
159
|
-
svgEl.style.fill = '#0078d4';
|
|
160
|
-
svgEl.style.animation = 'spin 1s linear infinite';
|
|
161
|
-
svgEl.innerHTML = '<path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"/>';
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Add to queue
|
|
165
|
-
queuePdfLoad(iframeWrapper, filename, loadingPlaceholder);
|
|
166
|
-
observer.disconnect();
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}, {
|
|
170
|
-
rootMargin: '200px',
|
|
171
|
-
threshold: 0
|
|
172
|
-
});
|
|
173
207
|
|
|
174
|
-
|
|
208
|
+
// Add to queue
|
|
209
|
+
queuePdfLoad(iframeWrapper, filename, loadingPlaceholder);
|
|
210
|
+
observer.disconnect();
|
|
211
|
+
}
|
|
175
212
|
});
|
|
213
|
+
}, {
|
|
214
|
+
rootMargin: '200px',
|
|
215
|
+
threshold: 0
|
|
176
216
|
});
|
|
217
|
+
|
|
218
|
+
observer.observe(container);
|
|
177
219
|
}
|
|
178
220
|
|
|
179
221
|
function loadPdfIframe(wrapper, filename, placeholder) {
|
package/static/viewer.html
CHANGED
|
@@ -1568,6 +1568,17 @@
|
|
|
1568
1568
|
|
|
1569
1569
|
} catch (err) {
|
|
1570
1570
|
console.error('[PDF-Secure] Auto-load error:', err);
|
|
1571
|
+
|
|
1572
|
+
// Notify parent of error (prevents 60s queue hang)
|
|
1573
|
+
if (window.parent && window.parent !== window) {
|
|
1574
|
+
const config = window.PDF_SECURE_CONFIG || {};
|
|
1575
|
+
window.parent.postMessage({
|
|
1576
|
+
type: 'pdf-secure-ready',
|
|
1577
|
+
filename: config.filename,
|
|
1578
|
+
error: err.message
|
|
1579
|
+
}, '*');
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1571
1582
|
if (dropzone) {
|
|
1572
1583
|
dropzone.innerHTML = `
|
|
1573
1584
|
<svg viewBox="0 0 24 24" style="fill: #e81224;">
|