nodebb-plugin-pdf-secure 1.0.2 → 1.0.4
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/package.json +1 -1
- package/plugin.json +14 -5
- package/static/lib/main.js +36 -30
- package/static/lib/viewer.js +113 -135
- package/static/style.less +146 -2
- package/static/lib/viewer.css +0 -158
- package/static/lib/viewer.html +0 -29
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -3,18 +3,27 @@
|
|
|
3
3
|
"url": "https://github.com/NodeBB/nodebb-plugin-pdf-secure",
|
|
4
4
|
"library": "./library.js",
|
|
5
5
|
"hooks": [
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
{
|
|
7
|
+
"hook": "static:app.load",
|
|
8
|
+
"method": "init"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"hook": "static:api.routes",
|
|
12
|
+
"method": "addRoutes"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"hook": "filter:admin.header.build",
|
|
16
|
+
"method": "addAdminNavigation"
|
|
17
|
+
}
|
|
9
18
|
],
|
|
10
19
|
"staticDirs": {
|
|
11
20
|
"static": "./static"
|
|
12
21
|
},
|
|
13
22
|
"less": [
|
|
14
|
-
"static/style.less"
|
|
23
|
+
"static/templates/style.less"
|
|
15
24
|
],
|
|
16
25
|
"scripts": [
|
|
17
26
|
"static/lib/main.js"
|
|
18
27
|
],
|
|
19
28
|
"templates": "./static/templates"
|
|
20
|
-
}
|
|
29
|
+
}
|
package/static/lib/main.js
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
async function openSecureViewer(filename) {
|
|
44
44
|
try {
|
|
45
|
-
// Request nonce from server
|
|
45
|
+
// 1. Request nonce from server
|
|
46
46
|
const response = await fetch(
|
|
47
47
|
`${config.relative_path}/api/v3/plugins/pdf-secure/nonce?file=${encodeURIComponent(filename)}`,
|
|
48
48
|
{
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
const result = await response.json();
|
|
66
66
|
const { nonce, isPremium } = result.response;
|
|
67
67
|
|
|
68
|
-
// Fetch PDF binary
|
|
68
|
+
// 2. Fetch PDF binary data
|
|
69
69
|
const pdfResponse = await fetch(
|
|
70
70
|
`${config.relative_path}/api/v3/plugins/pdf-secure/pdf-data?nonce=${encodeURIComponent(nonce)}`,
|
|
71
71
|
{ credentials: 'same-origin' }
|
|
@@ -76,45 +76,51 @@
|
|
|
76
76
|
}
|
|
77
77
|
const pdfArrayBuffer = await pdfResponse.arrayBuffer();
|
|
78
78
|
|
|
79
|
-
// Create overlay
|
|
79
|
+
// 3. Create overlay DOM (no iframe)
|
|
80
80
|
const overlay = document.createElement('div');
|
|
81
81
|
overlay.className = 'pdf-secure-overlay';
|
|
82
82
|
overlay.id = 'pdf-secure-overlay';
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
overlay.innerHTML = `
|
|
84
|
+
<div class="pdf-secure-viewer">
|
|
85
|
+
<div id="pdf-toolbar">
|
|
86
|
+
<button id="pdf-prev-btn" disabled>« Prev</button>
|
|
87
|
+
<span id="pdf-page-info">Loading...</span>
|
|
88
|
+
<button id="pdf-next-btn" disabled>Next »</button>
|
|
89
|
+
<button id="pdf-close-btn">× Close</button>
|
|
90
|
+
</div>
|
|
91
|
+
<div id="pdf-premium-banner">
|
|
92
|
+
This is a preview (page 1 only). Upgrade to Premium to view the full document.
|
|
93
|
+
</div>
|
|
94
|
+
<div id="pdf-canvas-wrapper">
|
|
95
|
+
<div id="pdf-loading">Loading PDF...</div>
|
|
96
|
+
<div id="pdf-error-msg"></div>
|
|
97
|
+
<canvas id="pdf-canvas"></canvas>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
`;
|
|
91
101
|
document.body.appendChild(overlay);
|
|
92
102
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
window.addEventListener('message', onMessage);
|
|
103
|
+
// 4. Dynamically import viewer.js
|
|
104
|
+
const { initViewer } = await import(
|
|
105
|
+
`${config.relative_path}/plugins/nodebb-plugin-pdf-secure/static/lib/viewer.js`
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// 5. Initialize viewer
|
|
109
|
+
const cleanupViewer = await initViewer(
|
|
110
|
+
overlay.querySelector('.pdf-secure-viewer'),
|
|
111
|
+
pdfArrayBuffer,
|
|
112
|
+
isPremium,
|
|
113
|
+
closeOverlay
|
|
114
|
+
);
|
|
107
115
|
|
|
108
|
-
//
|
|
116
|
+
// 6. Escape key handler (in main.js scope)
|
|
109
117
|
function onKeydown(e) {
|
|
110
|
-
if (e.key === 'Escape')
|
|
111
|
-
closeOverlay();
|
|
112
|
-
}
|
|
118
|
+
if (e.key === 'Escape') closeOverlay();
|
|
113
119
|
}
|
|
114
120
|
document.addEventListener('keydown', onKeydown);
|
|
115
121
|
|
|
116
122
|
function closeOverlay() {
|
|
117
|
-
|
|
123
|
+
if (cleanupViewer) cleanupViewer();
|
|
118
124
|
document.removeEventListener('keydown', onKeydown);
|
|
119
125
|
const el = document.getElementById('pdf-secure-overlay');
|
|
120
126
|
if (el) el.remove();
|
package/static/lib/viewer.js
CHANGED
|
@@ -1,149 +1,122 @@
|
|
|
1
1
|
import { getDocument, GlobalWorkerOptions } from './pdf.min.mjs';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
GlobalWorkerOptions.workerSrc = './pdf.worker.min.mjs';
|
|
3
|
+
// Worker path resolved relative to this file's URL
|
|
4
|
+
GlobalWorkerOptions.workerSrc = new URL('./pdf.worker.min.mjs', import.meta.url).href;
|
|
5
|
+
|
|
6
|
+
export async function initViewer(container, pdfArrayBuffer, isPremium, onClose) {
|
|
7
|
+
// DOM elements scoped to container
|
|
8
|
+
const canvas = container.querySelector('#pdf-canvas');
|
|
9
|
+
const ctx = canvas.getContext('2d');
|
|
10
|
+
const prevBtn = container.querySelector('#pdf-prev-btn');
|
|
11
|
+
const nextBtn = container.querySelector('#pdf-next-btn');
|
|
12
|
+
const pageInfo = container.querySelector('#pdf-page-info');
|
|
13
|
+
const loadingEl = container.querySelector('#pdf-loading');
|
|
14
|
+
const errorEl = container.querySelector('#pdf-error-msg');
|
|
15
|
+
const premiumBanner = container.querySelector('#pdf-premium-banner');
|
|
16
|
+
const closeBtn = container.querySelector('#pdf-close-btn');
|
|
17
|
+
const canvasWrapper = container.querySelector('#pdf-canvas-wrapper');
|
|
18
|
+
|
|
19
|
+
let pdfDoc = null;
|
|
20
|
+
let currentPage = 1;
|
|
21
|
+
let totalPages = 0;
|
|
22
|
+
let rendering = false;
|
|
23
|
+
|
|
24
|
+
// --- Security: scoped to container ---
|
|
25
|
+
container.addEventListener('contextmenu', (e) => e.preventDefault());
|
|
26
|
+
container.addEventListener('dragstart', (e) => e.preventDefault());
|
|
27
|
+
container.addEventListener('selectstart', (e) => e.preventDefault());
|
|
28
|
+
|
|
29
|
+
// Keyboard shortcuts (Ctrl+S/P/U/A, F12) — document level
|
|
30
|
+
function onSecurityKeydown(e) {
|
|
31
|
+
if (e.key === 'F12') {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if ((e.ctrlKey || e.metaKey) && ['s', 'p', 'u', 'a'].includes(e.key.toLowerCase())) {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
document.addEventListener('keydown', onSecurityKeydown);
|
|
5
40
|
|
|
6
|
-
//
|
|
41
|
+
// Premium banner
|
|
42
|
+
if (!isPremium) {
|
|
43
|
+
premiumBanner.style.display = 'block';
|
|
44
|
+
}
|
|
7
45
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
e.preventDefault();
|
|
11
|
-
});
|
|
46
|
+
// Close button
|
|
47
|
+
closeBtn.addEventListener('click', onClose);
|
|
12
48
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if ((e.ctrlKey || e.metaKey) && ['s', 'p', 'u', 'a'].includes(e.key.toLowerCase())) {
|
|
20
|
-
e.preventDefault();
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Block drag
|
|
26
|
-
document.addEventListener('dragstart', (e) => {
|
|
27
|
-
e.preventDefault();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Block text selection via JS as well
|
|
31
|
-
document.addEventListener('selectstart', (e) => {
|
|
32
|
-
e.preventDefault();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// --- PDF Viewer Logic ---
|
|
36
|
-
|
|
37
|
-
const canvas = document.getElementById('pdf-canvas');
|
|
38
|
-
const ctx = canvas.getContext('2d');
|
|
39
|
-
const prevBtn = document.getElementById('prev-btn');
|
|
40
|
-
const nextBtn = document.getElementById('next-btn');
|
|
41
|
-
const pageInfo = document.getElementById('page-info');
|
|
42
|
-
const loadingEl = document.getElementById('loading');
|
|
43
|
-
const errorEl = document.getElementById('error-msg');
|
|
44
|
-
const premiumBanner = document.getElementById('premium-banner');
|
|
45
|
-
const closeBtn = document.getElementById('close-btn');
|
|
46
|
-
|
|
47
|
-
let pdfDoc = null;
|
|
48
|
-
let currentPage = 1;
|
|
49
|
-
let totalPages = 0;
|
|
50
|
-
let rendering = false;
|
|
51
|
-
|
|
52
|
-
// Close button handler
|
|
53
|
-
closeBtn.addEventListener('click', () => {
|
|
54
|
-
if (window.parent && window.parent !== window) {
|
|
55
|
-
window.parent.postMessage({ type: 'pdf-viewer-close' }, '*');
|
|
56
|
-
} else {
|
|
57
|
-
window.close();
|
|
58
|
-
}
|
|
59
|
-
});
|
|
49
|
+
// --- Render ---
|
|
50
|
+
async function renderPage(pageNum) {
|
|
51
|
+
if (rendering) return;
|
|
52
|
+
rendering = true;
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
rendering = true;
|
|
54
|
+
try {
|
|
55
|
+
const page = await pdfDoc.getPage(pageNum);
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const wrapper = document.getElementById('canvas-wrapper');
|
|
70
|
-
const maxWidth = wrapper.clientWidth - 40; // padding
|
|
71
|
-
const viewport = page.getViewport({ scale: 1 });
|
|
72
|
-
const scale = Math.min(maxWidth / viewport.width, 2.0); // max 2x
|
|
73
|
-
const scaledViewport = page.getViewport({ scale });
|
|
74
|
-
|
|
75
|
-
canvas.width = scaledViewport.width;
|
|
76
|
-
canvas.height = scaledViewport.height;
|
|
77
|
-
|
|
78
|
-
await page.render({
|
|
79
|
-
canvasContext: ctx,
|
|
80
|
-
viewport: scaledViewport,
|
|
81
|
-
}).promise;
|
|
82
|
-
|
|
83
|
-
currentPage = pageNum;
|
|
84
|
-
pageInfo.textContent = `Page ${currentPage} / ${totalPages}`;
|
|
85
|
-
prevBtn.disabled = currentPage <= 1;
|
|
86
|
-
nextBtn.disabled = currentPage >= totalPages;
|
|
87
|
-
} catch (err) {
|
|
88
|
-
showError('Error rendering page.');
|
|
89
|
-
}
|
|
57
|
+
const maxWidth = canvasWrapper.clientWidth - 40;
|
|
58
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
59
|
+
const scale = Math.min(maxWidth / viewport.width, 2.0);
|
|
60
|
+
const scaledViewport = page.getViewport({ scale });
|
|
90
61
|
|
|
91
|
-
|
|
92
|
-
|
|
62
|
+
canvas.width = scaledViewport.width;
|
|
63
|
+
canvas.height = scaledViewport.height;
|
|
93
64
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
65
|
+
await page.render({
|
|
66
|
+
canvasContext: ctx,
|
|
67
|
+
viewport: scaledViewport,
|
|
68
|
+
}).promise;
|
|
69
|
+
|
|
70
|
+
currentPage = pageNum;
|
|
71
|
+
pageInfo.textContent = `Page ${currentPage} / ${totalPages}`;
|
|
72
|
+
prevBtn.disabled = currentPage <= 1;
|
|
73
|
+
nextBtn.disabled = currentPage >= totalPages;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
showError('Error rendering page.');
|
|
76
|
+
}
|
|
100
77
|
|
|
101
|
-
|
|
102
|
-
prevBtn.addEventListener('click', () => {
|
|
103
|
-
if (currentPage > 1) renderPage(currentPage - 1);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
nextBtn.addEventListener('click', () => {
|
|
107
|
-
if (currentPage < totalPages) renderPage(currentPage + 1);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Keyboard navigation
|
|
111
|
-
document.addEventListener('keydown', (e) => {
|
|
112
|
-
if (e.key === 'ArrowLeft' && currentPage > 1) {
|
|
113
|
-
renderPage(currentPage - 1);
|
|
114
|
-
} else if (e.key === 'ArrowRight' && currentPage < totalPages) {
|
|
115
|
-
renderPage(currentPage + 1);
|
|
116
|
-
} else if (e.key === 'Escape') {
|
|
117
|
-
closeBtn.click();
|
|
78
|
+
rendering = false;
|
|
118
79
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
resizeTimeout = setTimeout(() => {
|
|
126
|
-
if (pdfDoc) renderPage(currentPage);
|
|
127
|
-
}, 250);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// 30 second timeout for receiving PDF data
|
|
131
|
-
const dataTimeout = setTimeout(() => {
|
|
132
|
-
showError('Timed out waiting for PDF data.');
|
|
133
|
-
}, 30000);
|
|
134
|
-
|
|
135
|
-
// Listen for PDF data from parent
|
|
136
|
-
window.addEventListener('message', async (e) => {
|
|
137
|
-
if (!e.data || e.data.type !== 'pdf-data') return;
|
|
138
|
-
clearTimeout(dataTimeout);
|
|
139
|
-
|
|
140
|
-
const { pdf, isPremium } = e.data;
|
|
141
|
-
if (!isPremium) {
|
|
142
|
-
premiumBanner.style.display = 'block';
|
|
80
|
+
|
|
81
|
+
function showError(msg) {
|
|
82
|
+
loadingEl.style.display = 'none';
|
|
83
|
+
canvas.style.display = 'none';
|
|
84
|
+
errorEl.style.display = 'flex';
|
|
85
|
+
errorEl.textContent = msg;
|
|
143
86
|
}
|
|
144
87
|
|
|
88
|
+
// Navigation buttons
|
|
89
|
+
prevBtn.addEventListener('click', () => {
|
|
90
|
+
if (currentPage > 1) renderPage(currentPage - 1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
nextBtn.addEventListener('click', () => {
|
|
94
|
+
if (currentPage < totalPages) renderPage(currentPage + 1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Keyboard navigation
|
|
98
|
+
function onNavKeydown(e) {
|
|
99
|
+
if (e.key === 'ArrowLeft' && currentPage > 1) {
|
|
100
|
+
renderPage(currentPage - 1);
|
|
101
|
+
} else if (e.key === 'ArrowRight' && currentPage < totalPages) {
|
|
102
|
+
renderPage(currentPage + 1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
document.addEventListener('keydown', onNavKeydown);
|
|
106
|
+
|
|
107
|
+
// Handle window resize
|
|
108
|
+
let resizeTimeout;
|
|
109
|
+
function onResize() {
|
|
110
|
+
clearTimeout(resizeTimeout);
|
|
111
|
+
resizeTimeout = setTimeout(() => {
|
|
112
|
+
if (pdfDoc) renderPage(currentPage);
|
|
113
|
+
}, 250);
|
|
114
|
+
}
|
|
115
|
+
window.addEventListener('resize', onResize);
|
|
116
|
+
|
|
117
|
+
// Load PDF
|
|
145
118
|
try {
|
|
146
|
-
const data = new Uint8Array(
|
|
119
|
+
const data = new Uint8Array(pdfArrayBuffer);
|
|
147
120
|
pdfDoc = await getDocument({ data }).promise;
|
|
148
121
|
totalPages = pdfDoc.numPages;
|
|
149
122
|
loadingEl.style.display = 'none';
|
|
@@ -152,7 +125,12 @@ window.addEventListener('message', async (e) => {
|
|
|
152
125
|
} catch (err) {
|
|
153
126
|
showError('Failed to load PDF. Please try again.');
|
|
154
127
|
}
|
|
155
|
-
});
|
|
156
128
|
|
|
157
|
-
//
|
|
158
|
-
|
|
129
|
+
// Return cleanup function
|
|
130
|
+
return function cleanup() {
|
|
131
|
+
document.removeEventListener('keydown', onSecurityKeydown);
|
|
132
|
+
document.removeEventListener('keydown', onNavKeydown);
|
|
133
|
+
window.removeEventListener('resize', onResize);
|
|
134
|
+
if (pdfDoc) pdfDoc.destroy();
|
|
135
|
+
};
|
|
136
|
+
}
|
package/static/style.less
CHANGED
|
@@ -25,18 +25,162 @@
|
|
|
25
25
|
justify-content: center;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
.pdf-secure-
|
|
28
|
+
.pdf-secure-viewer {
|
|
29
29
|
width: 95%;
|
|
30
30
|
height: 95%;
|
|
31
31
|
border: none;
|
|
32
32
|
border-radius: 8px;
|
|
33
33
|
background: #2b2b2b;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
position: relative;
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
39
|
+
user-select: none;
|
|
40
|
+
-webkit-user-select: none;
|
|
41
|
+
-moz-user-select: none;
|
|
42
|
+
-ms-user-select: none;
|
|
43
|
+
|
|
44
|
+
&, & *, & *::before, & *::after {
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
margin: 0;
|
|
47
|
+
padding: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#pdf-toolbar {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
gap: 12px;
|
|
55
|
+
padding: 8px 16px;
|
|
56
|
+
background: #333;
|
|
57
|
+
color: #fff;
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
z-index: 10;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#pdf-toolbar button {
|
|
63
|
+
background: #555;
|
|
64
|
+
color: #fff;
|
|
65
|
+
border: none;
|
|
66
|
+
padding: 6px 14px;
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#pdf-toolbar button:hover {
|
|
73
|
+
background: #666;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#pdf-toolbar button:disabled {
|
|
77
|
+
opacity: 0.4;
|
|
78
|
+
cursor: not-allowed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#pdf-page-info {
|
|
82
|
+
font-size: 14px;
|
|
83
|
+
min-width: 100px;
|
|
84
|
+
text-align: center;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#pdf-close-btn {
|
|
88
|
+
position: absolute;
|
|
89
|
+
right: 16px;
|
|
90
|
+
top: 8px;
|
|
91
|
+
background: #c0392b !important;
|
|
92
|
+
font-size: 16px !important;
|
|
93
|
+
padding: 6px 12px !important;
|
|
94
|
+
z-index: 20;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#pdf-close-btn:hover {
|
|
98
|
+
background: #e74c3c !important;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#pdf-canvas-wrapper {
|
|
102
|
+
flex: 1;
|
|
103
|
+
overflow: auto;
|
|
104
|
+
display: flex;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
align-items: flex-start;
|
|
107
|
+
padding: 20px;
|
|
108
|
+
background: #2b2b2b;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#pdf-canvas {
|
|
112
|
+
pointer-events: none;
|
|
113
|
+
display: none;
|
|
114
|
+
max-width: 100%;
|
|
115
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#pdf-loading {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
justify-content: center;
|
|
122
|
+
height: 100%;
|
|
123
|
+
color: #aaa;
|
|
124
|
+
font-size: 18px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#pdf-error-msg {
|
|
128
|
+
display: none;
|
|
129
|
+
align-items: center;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
height: 100%;
|
|
132
|
+
color: #e74c3c;
|
|
133
|
+
font-size: 18px;
|
|
134
|
+
text-align: center;
|
|
135
|
+
padding: 20px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#pdf-premium-banner {
|
|
139
|
+
display: none;
|
|
140
|
+
background: linear-gradient(135deg, #f39c12, #e67e22);
|
|
141
|
+
color: #fff;
|
|
142
|
+
text-align: center;
|
|
143
|
+
padding: 10px 16px;
|
|
144
|
+
font-size: 14px;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
flex-shrink: 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* Anti-print: hide overlay when printing */
|
|
151
|
+
@media print {
|
|
152
|
+
.pdf-secure-overlay {
|
|
153
|
+
display: none !important;
|
|
154
|
+
}
|
|
34
155
|
}
|
|
35
156
|
|
|
36
157
|
@media (max-width: 768px) {
|
|
37
|
-
.pdf-secure-
|
|
158
|
+
.pdf-secure-viewer {
|
|
38
159
|
width: 100%;
|
|
39
160
|
height: 100%;
|
|
40
161
|
border-radius: 0;
|
|
41
162
|
}
|
|
42
163
|
}
|
|
164
|
+
|
|
165
|
+
@media (max-width: 600px) {
|
|
166
|
+
.pdf-secure-viewer {
|
|
167
|
+
#pdf-toolbar {
|
|
168
|
+
gap: 6px;
|
|
169
|
+
padding: 6px 8px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#pdf-toolbar button {
|
|
173
|
+
padding: 4px 8px;
|
|
174
|
+
font-size: 12px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#pdf-page-info {
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
min-width: 70px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#pdf-canvas-wrapper {
|
|
183
|
+
padding: 10px;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
package/static/lib/viewer.css
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/* PDF Secure Viewer Styles */
|
|
2
|
-
* {
|
|
3
|
-
margin: 0;
|
|
4
|
-
padding: 0;
|
|
5
|
-
box-sizing: border-box;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
html, body {
|
|
9
|
-
width: 100%;
|
|
10
|
-
height: 100%;
|
|
11
|
-
overflow: hidden;
|
|
12
|
-
background: #2b2b2b;
|
|
13
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
14
|
-
user-select: none;
|
|
15
|
-
-webkit-user-select: none;
|
|
16
|
-
-moz-user-select: none;
|
|
17
|
-
-ms-user-select: none;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#viewer-container {
|
|
21
|
-
display: flex;
|
|
22
|
-
flex-direction: column;
|
|
23
|
-
height: 100%;
|
|
24
|
-
width: 100%;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
#toolbar {
|
|
28
|
-
display: flex;
|
|
29
|
-
align-items: center;
|
|
30
|
-
justify-content: center;
|
|
31
|
-
gap: 12px;
|
|
32
|
-
padding: 8px 16px;
|
|
33
|
-
background: #333;
|
|
34
|
-
color: #fff;
|
|
35
|
-
flex-shrink: 0;
|
|
36
|
-
z-index: 10;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
#toolbar button {
|
|
40
|
-
background: #555;
|
|
41
|
-
color: #fff;
|
|
42
|
-
border: none;
|
|
43
|
-
padding: 6px 14px;
|
|
44
|
-
border-radius: 4px;
|
|
45
|
-
cursor: pointer;
|
|
46
|
-
font-size: 14px;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
#toolbar button:hover {
|
|
50
|
-
background: #666;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
#toolbar button:disabled {
|
|
54
|
-
opacity: 0.4;
|
|
55
|
-
cursor: not-allowed;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
#page-info {
|
|
59
|
-
font-size: 14px;
|
|
60
|
-
min-width: 100px;
|
|
61
|
-
text-align: center;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#close-btn {
|
|
65
|
-
position: absolute;
|
|
66
|
-
right: 16px;
|
|
67
|
-
top: 8px;
|
|
68
|
-
background: #c0392b !important;
|
|
69
|
-
font-size: 16px !important;
|
|
70
|
-
padding: 6px 12px !important;
|
|
71
|
-
z-index: 20;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
#close-btn:hover {
|
|
75
|
-
background: #e74c3c !important;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
#canvas-wrapper {
|
|
79
|
-
flex: 1;
|
|
80
|
-
overflow: auto;
|
|
81
|
-
display: flex;
|
|
82
|
-
justify-content: center;
|
|
83
|
-
align-items: flex-start;
|
|
84
|
-
padding: 20px;
|
|
85
|
-
background: #2b2b2b;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
#pdf-canvas {
|
|
89
|
-
pointer-events: none;
|
|
90
|
-
display: block;
|
|
91
|
-
max-width: 100%;
|
|
92
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
#loading {
|
|
96
|
-
display: flex;
|
|
97
|
-
align-items: center;
|
|
98
|
-
justify-content: center;
|
|
99
|
-
height: 100%;
|
|
100
|
-
color: #aaa;
|
|
101
|
-
font-size: 18px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
#error-msg {
|
|
105
|
-
display: none;
|
|
106
|
-
align-items: center;
|
|
107
|
-
justify-content: center;
|
|
108
|
-
height: 100%;
|
|
109
|
-
color: #e74c3c;
|
|
110
|
-
font-size: 18px;
|
|
111
|
-
text-align: center;
|
|
112
|
-
padding: 20px;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#premium-banner {
|
|
116
|
-
display: none;
|
|
117
|
-
background: linear-gradient(135deg, #f39c12, #e67e22);
|
|
118
|
-
color: #fff;
|
|
119
|
-
text-align: center;
|
|
120
|
-
padding: 10px 16px;
|
|
121
|
-
font-size: 14px;
|
|
122
|
-
font-weight: 600;
|
|
123
|
-
flex-shrink: 0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/* Anti-print */
|
|
127
|
-
@media print {
|
|
128
|
-
html, body {
|
|
129
|
-
visibility: hidden !important;
|
|
130
|
-
}
|
|
131
|
-
body::after {
|
|
132
|
-
content: 'Printing is not allowed.';
|
|
133
|
-
visibility: visible;
|
|
134
|
-
display: block;
|
|
135
|
-
text-align: center;
|
|
136
|
-
padding: 50px;
|
|
137
|
-
font-size: 24px;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/* Responsive */
|
|
142
|
-
@media (max-width: 600px) {
|
|
143
|
-
#toolbar {
|
|
144
|
-
gap: 6px;
|
|
145
|
-
padding: 6px 8px;
|
|
146
|
-
}
|
|
147
|
-
#toolbar button {
|
|
148
|
-
padding: 4px 8px;
|
|
149
|
-
font-size: 12px;
|
|
150
|
-
}
|
|
151
|
-
#page-info {
|
|
152
|
-
font-size: 12px;
|
|
153
|
-
min-width: 70px;
|
|
154
|
-
}
|
|
155
|
-
#canvas-wrapper {
|
|
156
|
-
padding: 10px;
|
|
157
|
-
}
|
|
158
|
-
}
|
package/static/lib/viewer.html
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>PDF Viewer</title>
|
|
7
|
-
<link rel="stylesheet" href="viewer.css">
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<div id="viewer-container">
|
|
11
|
-
<div id="toolbar">
|
|
12
|
-
<button id="prev-btn" disabled>« Prev</button>
|
|
13
|
-
<span id="page-info">Loading...</span>
|
|
14
|
-
<button id="next-btn" disabled>Next »</button>
|
|
15
|
-
<button id="close-btn">× Close</button>
|
|
16
|
-
</div>
|
|
17
|
-
<div id="premium-banner">
|
|
18
|
-
This is a preview (page 1 only). Upgrade to Premium to view the full document.
|
|
19
|
-
</div>
|
|
20
|
-
<div id="canvas-wrapper">
|
|
21
|
-
<div id="loading">Loading PDF...</div>
|
|
22
|
-
<div id="error-msg"></div>
|
|
23
|
-
<canvas id="pdf-canvas"></canvas>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<script type="module" src="viewer.js"></script>
|
|
28
|
-
</body>
|
|
29
|
-
</html>
|