Translsis commited on
Commit
cc9e8f2
·
verified ·
1 Parent(s): 0898674

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +257 -195
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>PDF Tab Viewer</title>
7
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
8
  <style>
9
  * {
@@ -15,9 +15,8 @@
15
  body {
16
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17
  background: #2c3e50;
18
- min-height: 100vh;
19
- padding: 0;
20
- margin: 0;
21
  }
22
 
23
  .container {
@@ -27,67 +26,60 @@
27
  background: white;
28
  }
29
 
30
- .header {
 
31
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
32
- padding: 12px 20px;
33
- color: white;
34
  display: flex;
35
- justify-content: space-between;
36
  align-items: center;
 
 
37
  flex-shrink: 0;
38
  }
39
 
40
- .header h1 {
41
- font-size: 1.3em;
 
42
  margin: 0;
 
43
  }
44
 
45
- .upload-section {
46
- padding: 15px 20px;
47
- background: #f8f9fa;
48
- border-bottom: 1px solid #dee2e6;
49
- display: flex;
50
- align-items: center;
51
- gap: 15px;
52
- flex-shrink: 0;
53
  }
54
 
55
- .upload-area {
56
- border: 2px dashed #667eea;
57
- border-radius: 8px;
58
- padding: 15px 25px;
59
- background: white;
 
60
  cursor: pointer;
 
 
61
  transition: all 0.2s ease;
62
- flex: 1;
63
- display: flex;
64
- align-items: center;
65
- gap: 10px;
66
- }
67
-
68
- .upload-area:hover {
69
- border-color: #764ba2;
70
- background: #f8f9ff;
71
  }
72
 
73
- .upload-area.dragover {
74
- border-color: #764ba2;
75
- background: #f0f0ff;
76
  }
77
 
78
- .upload-icon {
79
- font-size: 1.5em;
 
80
  }
81
 
82
- .upload-text {
83
- font-size: 0.95em;
84
- color: #495057;
 
85
  font-weight: 600;
86
  }
87
 
88
- .upload-hint {
89
- color: #6c757d;
90
- font-size: 0.85em;
91
  }
92
 
93
  #fileInput {
@@ -95,27 +87,33 @@
95
  }
96
 
97
  .file-count {
98
- padding: 10px 20px;
99
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 
 
 
 
 
 
 
100
  color: white;
101
- border-radius: 20px;
102
- font-weight: bold;
103
  font-size: 0.9em;
104
- white-space: nowrap;
105
  }
106
 
 
107
  .tabs {
108
  display: flex;
109
  background: #f8f9fa;
110
  padding: 0 10px;
111
  overflow-x: auto;
112
- border-bottom: 2px solid #dee2e6;
113
- gap: 3px;
114
  flex-shrink: 0;
115
  }
116
 
117
  .tabs::-webkit-scrollbar {
118
- height: 6px;
119
  }
120
 
121
  .tabs::-webkit-scrollbar-track {
@@ -128,21 +126,24 @@
128
  }
129
 
130
  .tab {
131
- padding: 10px 15px;
132
  cursor: pointer;
133
  border: none;
134
  background: transparent;
135
  color: #6c757d;
136
  font-weight: 500;
137
- font-size: 0.85em;
138
  transition: all 0.2s ease;
139
  white-space: nowrap;
140
  position: relative;
141
- border-radius: 8px 8px 0 0;
142
- margin-top: 3px;
143
  display: flex;
144
  align-items: center;
145
- gap: 6px;
 
 
 
146
  }
147
 
148
  .tab:hover {
@@ -158,159 +159,162 @@
158
  .tab.active::after {
159
  content: '';
160
  position: absolute;
161
- bottom: -2px;
162
  left: 0;
163
  right: 0;
164
  height: 2px;
165
- background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
166
  }
167
 
168
  .close-tab {
169
- margin-left: 5px;
170
  color: #dc3545;
171
  font-weight: bold;
172
- font-size: 1.1em;
173
  opacity: 0.6;
174
- transition: opacity 0.2s;
175
  }
176
 
177
  .close-tab:hover {
178
  opacity: 1;
179
  }
180
 
 
181
  .content {
182
  flex: 1;
183
  overflow: hidden;
184
  display: flex;
185
  flex-direction: column;
 
186
  }
187
 
188
  .tab-content {
189
  display: none;
190
- animation: fadeIn 0.3s ease;
 
191
  }
192
 
193
  .tab-content.active {
194
- display: block;
195
- }
196
-
197
- @keyframes fadeIn {
198
- from {
199
- opacity: 0;
200
- transform: translateY(10px);
201
- }
202
- to {
203
- opacity: 1;
204
- transform: translateY(0);
205
- }
206
  }
207
 
208
- .pdf-controls {
 
 
 
 
209
  display: flex;
210
- justify-content: center;
211
  align-items: center;
212
- gap: 12px;
213
- padding: 10px;
214
- background: #f8f9fa;
215
- border-bottom: 1px solid #dee2e6;
216
- flex-shrink: 0;
217
  }
218
 
219
- .pdf-controls button {
220
- padding: 8px 16px;
221
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
222
- color: white;
223
- border: none;
224
- border-radius: 6px;
225
- cursor: pointer;
226
- font-weight: 600;
227
- font-size: 0.85em;
228
- transition: all 0.2s ease;
229
  }
230
 
231
- .pdf-controls button:hover:not(:disabled) {
232
- transform: translateY(-1px);
233
- box-shadow: 0 3px 8px rgba(102, 126, 234, 0.3);
234
  }
235
 
236
- .pdf-controls button:disabled {
237
- opacity: 0.4;
238
- cursor: not-allowed;
239
  }
240
 
241
- .pdf-controls span {
242
- font-weight: 600;
243
- font-size: 0.9em;
244
- color: #495057;
245
  }
246
 
247
- .pdf-viewer-container {
248
  flex: 1;
249
- overflow: auto;
250
- background: #525252;
251
  display: flex;
 
252
  align-items: center;
253
  justify-content: center;
254
- }
255
-
256
- .pdf-canvas {
257
- display: block;
258
- max-width: 100%;
259
- height: auto;
260
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
261
- }
262
-
263
- .empty-state {
264
- text-align: center;
265
- padding: 100px 20px;
266
- color: #6c757d;
267
  }
268
 
269
  .empty-state svg {
270
- width: 150px;
271
- height: 150px;
272
  margin-bottom: 20px;
273
  opacity: 0.5;
274
  }
275
 
276
  .empty-state h2 {
277
- font-size: 1.8em;
278
  margin-bottom: 10px;
279
  }
280
 
281
- .empty-state p {
282
- font-size: 1.1em;
283
- }
284
-
285
  .loading {
286
  text-align: center;
287
  padding: 50px;
288
- color: #667eea;
289
  font-size: 1.2em;
290
  font-weight: bold;
291
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  </style>
293
  </head>
294
  <body>
 
 
 
 
295
  <div class="container">
296
- <div class="header">
 
297
  <h1>📄 PDF Viewer</h1>
 
 
 
 
 
 
298
  <div class="file-count" id="fileCount" style="display: none;">0 file</div>
299
- </div>
300
-
301
- <div class="upload-section">
302
- <div class="upload-area" id="uploadArea">
303
- <div class="upload-icon">📁</div>
304
- <div>
305
- <div class="upload-text">Chọn PDF</div>
306
- <div class="upload-hint">hoặc kéo thả (nhiều file)</div>
307
- </div>
308
  </div>
309
- <input type="file" id="fileInput" accept=".pdf" multiple>
 
 
310
  </div>
311
 
 
312
  <div class="tabs" id="tabs"></div>
313
 
 
314
  <div class="content" id="content">
315
  <div class="empty-state">
316
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -320,48 +324,58 @@
320
  <line x1="16" y1="17" x2="8" y2="17"></line>
321
  </svg>
322
  <h2>Chưa có PDF nào</h2>
323
- <p>Chọn hoặc kéo thả file PDF trên để bắt đầu xem</p>
324
  </div>
325
  </div>
326
  </div>
327
 
328
  <script>
329
- // Setup PDF.js worker
330
  pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
331
 
332
- const uploadArea = document.getElementById('uploadArea');
333
  const fileInput = document.getElementById('fileInput');
334
  const tabsContainer = document.getElementById('tabs');
335
  const contentContainer = document.getElementById('content');
336
  const fileCountEl = document.getElementById('fileCount');
 
 
 
 
 
 
337
 
338
  let pdfFiles = [];
339
  let pdfDocs = [];
340
  let currentPages = [];
341
- let activeIndex = 0;
342
-
343
- uploadArea.addEventListener('click', () => fileInput.click());
344
 
 
345
  fileInput.addEventListener('change', (e) => {
346
  handleFiles(e.target.files);
347
  });
348
 
349
- uploadArea.addEventListener('dragover', (e) => {
 
350
  e.preventDefault();
351
- uploadArea.classList.add('dragover');
352
  });
353
 
354
- uploadArea.addEventListener('dragleave', () => {
355
- uploadArea.classList.remove('dragover');
 
 
356
  });
357
 
358
- uploadArea.addEventListener('drop', (e) => {
359
  e.preventDefault();
360
- uploadArea.classList.remove('dragover');
361
  const files = Array.from(e.dataTransfer.files).filter(f => f.type === 'application/pdf');
362
  handleFiles(files);
363
  });
364
 
 
 
 
 
365
  function handleFiles(files) {
366
  if (files.length === 0) return;
367
 
@@ -377,14 +391,14 @@
377
  addTab(file, fileIndex);
378
  });
379
 
380
- if (newFiles.length > 0) {
381
  switchTab(startIndex);
382
  }
383
  }
384
 
385
  function addTab(file, index) {
386
  const tab = document.createElement('button');
387
- tab.className = 'tab' + (index === 0 ? ' active' : '');
388
  tab.innerHTML = `
389
  <span>📄</span>
390
  <span>${file.name}</span>
@@ -392,16 +406,14 @@
392
  `;
393
  tab.onclick = () => switchTab(index);
394
  tab.dataset.index = index;
 
395
  tabsContainer.appendChild(tab);
396
 
397
  const tabContent = document.createElement('div');
398
- tabContent.className = 'tab-content' + (index === 0 ? ' active' : '');
399
  tabContent.dataset.index = index;
400
  tabContent.innerHTML = '<div class="loading">⏳ Đang tải PDF...</div>';
401
 
402
- contentContainer.innerHTML = '';
403
- const allContents = document.querySelectorAll('.tab-content');
404
- allContents.forEach(c => contentContainer.appendChild(c));
405
  contentContainer.appendChild(tabContent);
406
 
407
  loadPDF(file, index, tabContent);
@@ -415,18 +427,36 @@
415
  pdfDocs[index] = pdf;
416
  currentPages[index] = 1;
417
 
418
- container.innerHTML = `
419
- <div class="pdf-controls">
420
- <button onclick="changePage(${index}, -1)">◀ Trang trước</button>
421
- <span>Trang <span id="pageNum${index}">1</span> / <span id="pageCount${index}">${pdf.numPages}</span></span>
422
- <button onclick="changePage(${index}, 1)">Trang sau ▶</button>
423
- </div>
424
- <div class="pdf-viewer-container">
425
- <canvas id="pdfCanvas${index}" class="pdf-canvas"></canvas>
426
- </div>
427
- `;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
- renderPage(index, 1);
430
  } catch (error) {
431
  container.innerHTML = `
432
  <div style="text-align: center; padding: 50px; color: #dc3545;">
@@ -437,32 +467,52 @@
437
  }
438
  }
439
 
440
- async function renderPage(index, pageNum) {
441
- const pdf = pdfDocs[index];
442
- const page = await pdf.getPage(pageNum);
443
-
444
- const canvas = document.getElementById(`pdfCanvas${index}`);
445
- const context = canvas.getContext('2d');
446
-
447
- const viewport = page.getViewport({scale: 1.5});
448
- canvas.height = viewport.height;
449
- canvas.width = viewport.width;
 
 
 
 
 
 
 
 
 
 
450
 
451
- await page.render({
452
- canvasContext: context,
453
- viewport: viewport
454
- }).promise;
455
 
456
- currentPages[index] = pageNum;
457
- document.getElementById(`pageNum${index}`).textContent = pageNum;
 
 
 
 
458
  }
459
 
460
- function changePage(index, delta) {
461
- const newPage = currentPages[index] + delta;
462
- const pdf = pdfDocs[index];
463
-
464
- if (newPage >= 1 && newPage <= pdf.numPages) {
465
- renderPage(index, newPage);
 
 
 
 
 
 
 
466
  }
467
  }
468
 
@@ -478,6 +528,15 @@
478
  contents.forEach((content) => {
479
  content.classList.toggle('active', parseInt(content.dataset.index) === index);
480
  });
 
 
 
 
 
 
 
 
 
481
  }
482
 
483
  function closeTab(index) {
@@ -488,11 +547,11 @@
488
  const tab = tabsContainer.querySelector(`[data-index="${index}"]`);
489
  if (tab) tab.remove();
490
 
491
- const content = contentContainer.querySelector(`[data-index="${index}"]`);
492
  if (content) content.remove();
493
 
494
- const tabs = tabsContainer.querySelectorAll('.tab');
495
- tabs.forEach((tab) => {
496
  const idx = parseInt(tab.dataset.index);
497
  if (idx > index) {
498
  tab.dataset.index = idx - 1;
@@ -502,8 +561,7 @@
502
  }
503
  });
504
 
505
- const contents = contentContainer.querySelectorAll('.tab-content');
506
- contents.forEach((content) => {
507
  const idx = parseInt(content.dataset.index);
508
  if (idx > index) {
509
  content.dataset.index = idx - 1;
@@ -512,6 +570,10 @@
512
 
513
  if (pdfFiles.length === 0) {
514
  fileCountEl.style.display = 'none';
 
 
 
 
515
  contentContainer.innerHTML = `
516
  <div class="empty-state">
517
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -521,13 +583,13 @@
521
  <line x1="16" y1="17" x2="8" y2="17"></line>
522
  </svg>
523
  <h2>Chưa có PDF nào</h2>
524
- <p>Chọn hoặc kéo thả file PDF trên để bắt đầu xem</p>
525
  </div>
526
  `;
527
  } else {
528
  fileCountEl.textContent = `${pdfFiles.length} file`;
529
- if (activeIndex >= pdfFiles.length) {
530
- switchTab(Math.max(0, pdfFiles.length - 1));
531
  }
532
  }
533
  }
 
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
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
8
  <style>
9
  * {
 
15
  body {
16
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17
  background: #2c3e50;
18
+ height: 100vh;
19
+ overflow: hidden;
 
20
  }
21
 
22
  .container {
 
26
  background: white;
27
  }
28
 
29
+ /* Menu Bar */
30
+ .menubar {
31
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
32
+ padding: 8px 15px;
 
33
  display: flex;
 
34
  align-items: center;
35
+ gap: 15px;
36
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
37
  flex-shrink: 0;
38
  }
39
 
40
+ .menubar h1 {
41
+ font-size: 1.1em;
42
+ color: white;
43
  margin: 0;
44
+ font-weight: 600;
45
  }
46
 
47
+ .menubar-spacer {
48
+ flex: 1;
 
 
 
 
 
 
49
  }
50
 
51
+ .btn {
52
+ padding: 6px 14px;
53
+ background: rgba(255,255,255,0.2);
54
+ color: white;
55
+ border: 1px solid rgba(255,255,255,0.3);
56
+ border-radius: 5px;
57
  cursor: pointer;
58
+ font-weight: 500;
59
+ font-size: 0.85em;
60
  transition: all 0.2s ease;
61
+ white-space: nowrap;
 
 
 
 
 
 
 
 
62
  }
63
 
64
+ .btn:hover:not(:disabled) {
65
+ background: rgba(255,255,255,0.3);
66
+ transform: translateY(-1px);
67
  }
68
 
69
+ .btn:disabled {
70
+ opacity: 0.4;
71
+ cursor: not-allowed;
72
  }
73
 
74
+ .btn-primary {
75
+ background: white;
76
+ color: #667eea;
77
+ border-color: white;
78
  font-weight: 600;
79
  }
80
 
81
+ .btn-primary:hover {
82
+ background: #f8f9fa;
 
83
  }
84
 
85
  #fileInput {
 
87
  }
88
 
89
  .file-count {
90
+ padding: 6px 12px;
91
+ background: rgba(255,255,255,0.9);
92
+ color: #667eea;
93
+ border-radius: 15px;
94
+ font-weight: 600;
95
+ font-size: 0.85em;
96
+ }
97
+
98
+ .page-info {
99
  color: white;
 
 
100
  font-size: 0.9em;
101
+ font-weight: 500;
102
  }
103
 
104
+ /* Tabs */
105
  .tabs {
106
  display: flex;
107
  background: #f8f9fa;
108
  padding: 0 10px;
109
  overflow-x: auto;
110
+ border-bottom: 1px solid #dee2e6;
111
+ gap: 2px;
112
  flex-shrink: 0;
113
  }
114
 
115
  .tabs::-webkit-scrollbar {
116
+ height: 5px;
117
  }
118
 
119
  .tabs::-webkit-scrollbar-track {
 
126
  }
127
 
128
  .tab {
129
+ padding: 8px 12px;
130
  cursor: pointer;
131
  border: none;
132
  background: transparent;
133
  color: #6c757d;
134
  font-weight: 500;
135
+ font-size: 0.8em;
136
  transition: all 0.2s ease;
137
  white-space: nowrap;
138
  position: relative;
139
+ border-radius: 6px 6px 0 0;
140
+ margin-top: 2px;
141
  display: flex;
142
  align-items: center;
143
+ gap: 5px;
144
+ max-width: 200px;
145
+ overflow: hidden;
146
+ text-overflow: ellipsis;
147
  }
148
 
149
  .tab:hover {
 
159
  .tab.active::after {
160
  content: '';
161
  position: absolute;
162
+ bottom: -1px;
163
  left: 0;
164
  right: 0;
165
  height: 2px;
166
+ background: #667eea;
167
  }
168
 
169
  .close-tab {
170
+ margin-left: auto;
171
  color: #dc3545;
172
  font-weight: bold;
173
+ font-size: 1em;
174
  opacity: 0.6;
175
+ padding: 0 4px;
176
  }
177
 
178
  .close-tab:hover {
179
  opacity: 1;
180
  }
181
 
182
+ /* Content Area */
183
  .content {
184
  flex: 1;
185
  overflow: hidden;
186
  display: flex;
187
  flex-direction: column;
188
+ background: #525252;
189
  }
190
 
191
  .tab-content {
192
  display: none;
193
+ flex: 1;
194
+ overflow: auto;
195
  }
196
 
197
  .tab-content.active {
198
+ display: flex;
199
+ flex-direction: column;
 
 
 
 
 
 
 
 
 
 
200
  }
201
 
202
+ .pdf-viewer-container {
203
+ flex: 1;
204
+ overflow: auto;
205
+ background: #525252;
206
+ padding: 20px;
207
  display: flex;
208
+ flex-direction: column;
209
  align-items: center;
210
+ gap: 20px;
 
 
 
 
211
  }
212
 
213
+ .pdf-viewer-container::-webkit-scrollbar {
214
+ width: 10px;
 
 
 
 
 
 
 
 
215
  }
216
 
217
+ .pdf-viewer-container::-webkit-scrollbar-track {
218
+ background: #3a3a3a;
 
219
  }
220
 
221
+ .pdf-viewer-container::-webkit-scrollbar-thumb {
222
+ background: #667eea;
223
+ border-radius: 5px;
224
  }
225
 
226
+ .pdf-page {
227
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
228
+ background: white;
 
229
  }
230
 
231
+ .empty-state {
232
  flex: 1;
 
 
233
  display: flex;
234
+ flex-direction: column;
235
  align-items: center;
236
  justify-content: center;
237
+ color: #aaa;
238
+ padding: 50px;
 
 
 
 
 
 
 
 
 
 
 
239
  }
240
 
241
  .empty-state svg {
242
+ width: 100px;
243
+ height: 100px;
244
  margin-bottom: 20px;
245
  opacity: 0.5;
246
  }
247
 
248
  .empty-state h2 {
249
+ font-size: 1.5em;
250
  margin-bottom: 10px;
251
  }
252
 
 
 
 
 
253
  .loading {
254
  text-align: center;
255
  padding: 50px;
256
+ color: white;
257
  font-size: 1.2em;
258
  font-weight: bold;
259
  }
260
+
261
+ /* Drag and Drop Overlay */
262
+ .drop-overlay {
263
+ position: fixed;
264
+ top: 0;
265
+ left: 0;
266
+ right: 0;
267
+ bottom: 0;
268
+ background: rgba(102, 126, 234, 0.95);
269
+ display: none;
270
+ align-items: center;
271
+ justify-content: center;
272
+ z-index: 9999;
273
+ }
274
+
275
+ .drop-overlay.active {
276
+ display: flex;
277
+ }
278
+
279
+ .drop-message {
280
+ color: white;
281
+ font-size: 2em;
282
+ font-weight: bold;
283
+ text-align: center;
284
+ }
285
  </style>
286
  </head>
287
  <body>
288
+ <div class="drop-overlay" id="dropOverlay">
289
+ <div class="drop-message">📄 Thả file PDF vào đây</div>
290
+ </div>
291
+
292
  <div class="container">
293
+ <!-- Menu Bar -->
294
+ <div class="menubar">
295
  <h1>📄 PDF Viewer</h1>
296
+
297
+ <label for="fileInput" class="btn btn-primary">
298
+ 📁 Mở file
299
+ </label>
300
+ <input type="file" id="fileInput" accept=".pdf" multiple>
301
+
302
  <div class="file-count" id="fileCount" style="display: none;">0 file</div>
303
+
304
+ <div class="menubar-spacer"></div>
305
+
306
+ <div class="page-info" id="pageInfo" style="display: none;">
307
+ Trang <span id="currentPage">1</span> / <span id="totalPages">1</span>
 
 
 
 
308
  </div>
309
+
310
+ <button class="btn" id="prevPage" style="display: none;">◀</button>
311
+ <button class="btn" id="nextPage" style="display: none;">▶</button>
312
  </div>
313
 
314
+ <!-- Tabs -->
315
  <div class="tabs" id="tabs"></div>
316
 
317
+ <!-- Content -->
318
  <div class="content" id="content">
319
  <div class="empty-state">
320
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 
324
  <line x1="16" y1="17" x2="8" y2="17"></line>
325
  </svg>
326
  <h2>Chưa có PDF nào</h2>
327
+ <p>Nhấn "Mở file" hoặc kéo thả file PDF vào đây</p>
328
  </div>
329
  </div>
330
  </div>
331
 
332
  <script>
 
333
  pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
334
 
 
335
  const fileInput = document.getElementById('fileInput');
336
  const tabsContainer = document.getElementById('tabs');
337
  const contentContainer = document.getElementById('content');
338
  const fileCountEl = document.getElementById('fileCount');
339
+ const pageInfo = document.getElementById('pageInfo');
340
+ const currentPageEl = document.getElementById('currentPage');
341
+ const totalPagesEl = document.getElementById('totalPages');
342
+ const prevPageBtn = document.getElementById('prevPage');
343
+ const nextPageBtn = document.getElementById('nextPage');
344
+ const dropOverlay = document.getElementById('dropOverlay');
345
 
346
  let pdfFiles = [];
347
  let pdfDocs = [];
348
  let currentPages = [];
349
+ let activeIndex = -1;
 
 
350
 
351
+ // File input
352
  fileInput.addEventListener('change', (e) => {
353
  handleFiles(e.target.files);
354
  });
355
 
356
+ // Global drag and drop
357
+ document.body.addEventListener('dragover', (e) => {
358
  e.preventDefault();
359
+ dropOverlay.classList.add('active');
360
  });
361
 
362
+ document.body.addEventListener('dragleave', (e) => {
363
+ if (e.target === document.body) {
364
+ dropOverlay.classList.remove('active');
365
+ }
366
  });
367
 
368
+ document.body.addEventListener('drop', (e) => {
369
  e.preventDefault();
370
+ dropOverlay.classList.remove('active');
371
  const files = Array.from(e.dataTransfer.files).filter(f => f.type === 'application/pdf');
372
  handleFiles(files);
373
  });
374
 
375
+ // Page navigation
376
+ prevPageBtn.addEventListener('click', () => scrollToPrevPage());
377
+ nextPageBtn.addEventListener('click', () => scrollToNextPage());
378
+
379
  function handleFiles(files) {
380
  if (files.length === 0) return;
381
 
 
391
  addTab(file, fileIndex);
392
  });
393
 
394
+ if (activeIndex === -1) {
395
  switchTab(startIndex);
396
  }
397
  }
398
 
399
  function addTab(file, index) {
400
  const tab = document.createElement('button');
401
+ tab.className = 'tab';
402
  tab.innerHTML = `
403
  <span>📄</span>
404
  <span>${file.name}</span>
 
406
  `;
407
  tab.onclick = () => switchTab(index);
408
  tab.dataset.index = index;
409
+ tab.title = file.name;
410
  tabsContainer.appendChild(tab);
411
 
412
  const tabContent = document.createElement('div');
413
+ tabContent.className = 'tab-content';
414
  tabContent.dataset.index = index;
415
  tabContent.innerHTML = '<div class="loading">⏳ Đang tải PDF...</div>';
416
 
 
 
 
417
  contentContainer.appendChild(tabContent);
418
 
419
  loadPDF(file, index, tabContent);
 
427
  pdfDocs[index] = pdf;
428
  currentPages[index] = 1;
429
 
430
+ const viewerContainer = document.createElement('div');
431
+ viewerContainer.className = 'pdf-viewer-container';
432
+ viewerContainer.dataset.index = index;
433
+
434
+ // Render all pages
435
+ for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
436
+ const page = await pdf.getPage(pageNum);
437
+ const viewport = page.getViewport({scale: 1.5});
438
+
439
+ const canvas = document.createElement('canvas');
440
+ canvas.className = 'pdf-page';
441
+ canvas.height = viewport.height;
442
+ canvas.width = viewport.width;
443
+ canvas.dataset.page = pageNum;
444
+
445
+ const context = canvas.getContext('2d');
446
+ await page.render({
447
+ canvasContext: context,
448
+ viewport: viewport
449
+ }).promise;
450
+
451
+ viewerContainer.appendChild(canvas);
452
+ }
453
+
454
+ container.innerHTML = '';
455
+ container.appendChild(viewerContainer);
456
+
457
+ // Setup scroll tracking
458
+ viewerContainer.addEventListener('scroll', () => updateCurrentPage(index, viewerContainer));
459
 
 
460
  } catch (error) {
461
  container.innerHTML = `
462
  <div style="text-align: center; padding: 50px; color: #dc3545;">
 
467
  }
468
  }
469
 
470
+ function updateCurrentPage(index, container) {
471
+ if (index !== activeIndex) return;
472
+
473
+ const pages = container.querySelectorAll('.pdf-page');
474
+ const containerRect = container.getBoundingClientRect();
475
+ const containerCenter = containerRect.top + containerRect.height / 2;
476
+
477
+ let closestPage = 1;
478
+ let closestDistance = Infinity;
479
+
480
+ pages.forEach((page, idx) => {
481
+ const pageRect = page.getBoundingClientRect();
482
+ const pageCenter = pageRect.top + pageRect.height / 2;
483
+ const distance = Math.abs(pageCenter - containerCenter);
484
+
485
+ if (distance < closestDistance) {
486
+ closestDistance = distance;
487
+ closestPage = idx + 1;
488
+ }
489
+ });
490
 
491
+ currentPages[index] = closestPage;
492
+ currentPageEl.textContent = closestPage;
493
+ }
 
494
 
495
+ function scrollToPrevPage() {
496
+ const container = contentContainer.querySelector(`.tab-content.active .pdf-viewer-container`);
497
+ if (!container) return;
498
+
499
+ const targetPage = Math.max(1, currentPages[activeIndex] - 1);
500
+ scrollToPage(container, targetPage);
501
  }
502
 
503
+ function scrollToNextPage() {
504
+ const container = contentContainer.querySelector(`.tab-content.active .pdf-viewer-container`);
505
+ if (!container) return;
506
+
507
+ const pdf = pdfDocs[activeIndex];
508
+ const targetPage = Math.min(pdf.numPages, currentPages[activeIndex] + 1);
509
+ scrollToPage(container, targetPage);
510
+ }
511
+
512
+ function scrollToPage(container, pageNum) {
513
+ const page = container.querySelector(`[data-page="${pageNum}"]`);
514
+ if (page) {
515
+ page.scrollIntoView({ behavior: 'smooth', block: 'center' });
516
  }
517
  }
518
 
 
528
  contents.forEach((content) => {
529
  content.classList.toggle('active', parseInt(content.dataset.index) === index);
530
  });
531
+
532
+ // Update page info
533
+ if (pdfDocs[index]) {
534
+ pageInfo.style.display = 'block';
535
+ prevPageBtn.style.display = 'inline-block';
536
+ nextPageBtn.style.display = 'inline-block';
537
+ totalPagesEl.textContent = pdfDocs[index].numPages;
538
+ currentPageEl.textContent = currentPages[index] || 1;
539
+ }
540
  }
541
 
542
  function closeTab(index) {
 
547
  const tab = tabsContainer.querySelector(`[data-index="${index}"]`);
548
  if (tab) tab.remove();
549
 
550
+ const content = contentContainer.querySelector(`.tab-content[data-index="${index}"]`);
551
  if (content) content.remove();
552
 
553
+ // Update indices
554
+ tabsContainer.querySelectorAll('.tab').forEach((tab) => {
555
  const idx = parseInt(tab.dataset.index);
556
  if (idx > index) {
557
  tab.dataset.index = idx - 1;
 
561
  }
562
  });
563
 
564
+ contentContainer.querySelectorAll('.tab-content').forEach((content) => {
 
565
  const idx = parseInt(content.dataset.index);
566
  if (idx > index) {
567
  content.dataset.index = idx - 1;
 
570
 
571
  if (pdfFiles.length === 0) {
572
  fileCountEl.style.display = 'none';
573
+ pageInfo.style.display = 'none';
574
+ prevPageBtn.style.display = 'none';
575
+ nextPageBtn.style.display = 'none';
576
+ activeIndex = -1;
577
  contentContainer.innerHTML = `
578
  <div class="empty-state">
579
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 
583
  <line x1="16" y1="17" x2="8" y2="17"></line>
584
  </svg>
585
  <h2>Chưa có PDF nào</h2>
586
+ <p>Nhấn "Mở file" hoặc kéo thả file PDF vào đây</p>
587
  </div>
588
  `;
589
  } else {
590
  fileCountEl.textContent = `${pdfFiles.length} file`;
591
+ if (activeIndex >= pdfFiles.length || activeIndex === index) {
592
+ switchTab(Math.max(0, Math.min(index, pdfFiles.length - 1)));
593
  }
594
  }
595
  }