ng2-pdfjs-viewer 25.0.11 → 25.0.13
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/README.md +30 -2
- package/fesm2022/ng2-pdfjs-viewer.mjs +29 -38
- package/fesm2022/ng2-pdfjs-viewer.mjs.map +1 -1
- package/index.d.ts +1 -0
- package/package.json +1 -1
- package/pdfjs/web/postmessage-wrapper.js +1966 -1966
- package/pdfjs/web/viewer.css +5 -0
- package/pdfjs/web/viewer.html +1 -1
|
@@ -1,1967 +1,1967 @@
|
|
|
1
|
-
(function() {
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
// This will be wrapped in diagnostic logs check after diagnosticLogs is set
|
|
5
|
-
|
|
6
|
-
// #region URL Security Validation
|
|
7
|
-
let originalFileUrl = null;
|
|
8
|
-
let urlValidationEnabled = true;
|
|
9
|
-
|
|
10
|
-
function initializeUrlValidation() {
|
|
11
|
-
// Get the initial file URL from query parameters
|
|
12
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
13
|
-
const currentFileUrl = urlParams.get('file');
|
|
14
|
-
|
|
15
|
-
// Check if we already have a stored original URL
|
|
16
|
-
const storedOriginalUrl = sessionStorage.getItem('ng2-pdfjs-viewer-original-file-url');
|
|
17
|
-
|
|
18
|
-
if (storedOriginalUrl) {
|
|
19
|
-
// Use the stored original URL (from previous page load)
|
|
20
|
-
originalFileUrl = storedOriginalUrl;
|
|
21
|
-
} else if (currentFileUrl) {
|
|
22
|
-
// Store the current URL as the original (first time loading)
|
|
23
|
-
originalFileUrl = currentFileUrl;
|
|
24
|
-
sessionStorage.setItem('ng2-pdfjs-viewer-original-file-url', originalFileUrl);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function validateCurrentUrl() {
|
|
29
|
-
if (!urlValidationEnabled || !originalFileUrl) {
|
|
30
|
-
return true; // Skip validation if disabled or no original URL
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const currentFileParam = new URLSearchParams(window.location.search).get('file');
|
|
34
|
-
|
|
35
|
-
// Compare the file parameter specifically
|
|
36
|
-
if (currentFileParam !== originalFileUrl) {
|
|
37
|
-
console.error('🚨 SECURITY ALERT: File URL has been modified!');
|
|
38
|
-
console.error('Original:', originalFileUrl);
|
|
39
|
-
console.error('Current:', currentFileParam);
|
|
40
|
-
|
|
41
|
-
// Prevent loading and show error
|
|
42
|
-
showSecurityError('Unauthorized file access detected. The file URL has been tampered with.');
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function showSecurityError(message) {
|
|
50
|
-
// Simple console warning (less intrusive)
|
|
51
|
-
console.warn('🚨 Security Warning:', message);
|
|
52
|
-
|
|
53
|
-
// Send notification to parent window for Angular template handling
|
|
54
|
-
if (window.parent && window.parent !== window) {
|
|
55
|
-
window.parent.postMessage({
|
|
56
|
-
type: 'ng2-pdfjs-viewer-security-warning',
|
|
57
|
-
message: message,
|
|
58
|
-
originalUrl: originalFileUrl,
|
|
59
|
-
currentUrl: new URLSearchParams(window.location.search).get('file')
|
|
60
|
-
}, '*');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Initialize URL validation on page load
|
|
65
|
-
initializeUrlValidation();
|
|
66
|
-
|
|
67
|
-
// Add validation check before PDF.js loads
|
|
68
|
-
window.addEventListener('DOMContentLoaded', () => {
|
|
69
|
-
if (!validateCurrentUrl()) {
|
|
70
|
-
// Prevent PDF.js from loading if URL validation fails
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Validate on page load
|
|
76
|
-
window.addEventListener('load', () => {
|
|
77
|
-
validateCurrentUrl();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Also validate on hash change (in case user tries to modify URL)
|
|
81
|
-
window.addEventListener('hashchange', () => {
|
|
82
|
-
validateCurrentUrl();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Validate on popstate (back/forward navigation)
|
|
86
|
-
window.addEventListener('popstate', () => {
|
|
87
|
-
validateCurrentUrl();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Run validation immediately (in case page is already loaded)
|
|
91
|
-
validateCurrentUrl();
|
|
92
|
-
// #endregion
|
|
93
|
-
|
|
94
|
-
// Override the webviewerloaded event to dispatch to parent window
|
|
95
|
-
// This allows us to capture the event outside the iframe and set locale before PDF.js initializes
|
|
96
|
-
const originalDispatchEvent = document.dispatchEvent;
|
|
97
|
-
document.dispatchEvent = function(event) {
|
|
98
|
-
if (event.type === 'webviewerloaded') {
|
|
99
|
-
// Dispatch to parent window so it can be captured outside the iframe
|
|
100
|
-
if (window.parent && window.parent !== window) {
|
|
101
|
-
window.parent.document.dispatchEvent(event);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return originalDispatchEvent.call(this, event);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
// #region Constants and Configuration
|
|
108
|
-
// Command tracking for zoom to prevent infinite loops
|
|
109
|
-
const ZoomCommandTracker = {
|
|
110
|
-
activeZoomCommand: false,
|
|
111
|
-
|
|
112
|
-
markZoomCommandStart() {
|
|
113
|
-
this.activeZoomCommand = true;
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
markZoomCommandEnd() {
|
|
117
|
-
this.activeZoomCommand = false;
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
isZoomCommandActive() {
|
|
121
|
-
return this.activeZoomCommand;
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// Readiness state management
|
|
126
|
-
const ViewerReadiness = {
|
|
127
|
-
NOT_LOADED: 0,
|
|
128
|
-
VIEWER_LOADED: 1, // PDFViewerApplication exists
|
|
129
|
-
VIEWER_INITIALIZED: 2, // PDFViewerApplication.initialized = true
|
|
130
|
-
EVENTBUS_READY: 3, // Event bus is available and ready
|
|
131
|
-
COMPONENTS_READY: 4, // All required components available
|
|
132
|
-
DOCUMENT_LOADED: 5 // PDF document fully loaded
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// Pure event-driven architecture - no action categorization needed
|
|
136
|
-
// All actions are dispatched at required readiness levels by Universal Dispatcher
|
|
137
|
-
// #endregion
|
|
138
|
-
|
|
139
|
-
// #region State Management
|
|
140
|
-
let currentReadiness = ViewerReadiness.NOT_LOADED;
|
|
141
|
-
let readinessCallbacks = [];
|
|
142
|
-
let diagnosticLogs = false; // Default to false for production
|
|
143
|
-
// #endregion
|
|
144
|
-
|
|
145
|
-
// #region Utility Functions
|
|
146
|
-
function log(message, level = 'info') {
|
|
147
|
-
if (!diagnosticLogs) return; // Only log when diagnostic logs are enabled
|
|
148
|
-
|
|
149
|
-
const timestamp = new Date().toISOString();
|
|
150
|
-
const prefix = '[PostMessage]';
|
|
151
|
-
|
|
152
|
-
switch (level) {
|
|
153
|
-
case 'error':
|
|
154
|
-
console.error(`${prefix} ${timestamp} ERROR: ${message}`);
|
|
155
|
-
break;
|
|
156
|
-
case 'warn':
|
|
157
|
-
console.warn(`${prefix} ${timestamp} WARN: ${message}`);
|
|
158
|
-
break;
|
|
159
|
-
default:
|
|
160
|
-
console.log(`${prefix} ${timestamp} INFO: ${message}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Set diagnostic logs mode
|
|
165
|
-
function setDiagnosticLogs(enabled) {
|
|
166
|
-
diagnosticLogs = enabled;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Button visibility control helper (consolidated from separate module)
|
|
170
|
-
function toggleButtonVisibility(primaryId, secondaryId, visible) {
|
|
171
|
-
const primary = document.getElementById(primaryId);
|
|
172
|
-
const secondary = secondaryId ? document.getElementById(secondaryId) : null;
|
|
173
|
-
if (primary) primary.classList.toggle('hidden', !visible);
|
|
174
|
-
if (secondary) secondary.classList.toggle('hidden', !visible);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Generic element visibility control for toolbar/sidebar groups
|
|
178
|
-
function toggleElementVisibilityById(elementId, visible) {
|
|
179
|
-
const el = document.getElementById(elementId);
|
|
180
|
-
if (!el) {
|
|
181
|
-
log(`Element not found for visibility toggle: ${elementId}`, 'warn');
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
el.classList.toggle('hidden', !visible);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Toolbar section visibility control that preserves layout
|
|
188
|
-
function toggleToolbarSectionVisibility(elementId, visible) {
|
|
189
|
-
const el = document.getElementById(elementId);
|
|
190
|
-
if (!el) {
|
|
191
|
-
log(`Toolbar section not found for visibility toggle: ${elementId}`, 'warn');
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Use CSS class instead of inline styles (CSP-safe)
|
|
196
|
-
el.classList.toggle('ng2-hidden-section', !visible);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Readiness state management
|
|
200
|
-
function setReadiness(readiness) {
|
|
201
|
-
if (readiness > currentReadiness) {
|
|
202
|
-
log(`Readiness state changed: ${currentReadiness} → ${readiness}`);
|
|
203
|
-
currentReadiness = readiness;
|
|
204
|
-
|
|
205
|
-
// Execute callbacks for this readiness level
|
|
206
|
-
readinessCallbacks.forEach(callback => {
|
|
207
|
-
if (callback.readiness <= readiness && !callback.executed) {
|
|
208
|
-
callback.executed = true;
|
|
209
|
-
callback.callback();
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function onReadiness(readiness, callback) {
|
|
216
|
-
if (currentReadiness >= readiness) {
|
|
217
|
-
callback();
|
|
218
|
-
} else {
|
|
219
|
-
readinessCallbacks.push({ readiness, callback, executed: false });
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Note: Density styles now loaded from external ng2-customization.css (CSP-safe)
|
|
224
|
-
|
|
225
|
-
// Enhanced zoom transformation function
|
|
226
|
-
function transformZoomFromViewer(scale, app) {
|
|
227
|
-
if (typeof scale === 'string') {
|
|
228
|
-
return scale;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (app?.pdfViewer) {
|
|
232
|
-
const viewer = app.pdfViewer;
|
|
233
|
-
|
|
234
|
-
// Check for special zoom modes
|
|
235
|
-
const specialModes = ['page-fit', 'page-width', 'page-actual', 'auto'];
|
|
236
|
-
if (specialModes.includes(viewer.currentScaleValue)) {
|
|
237
|
-
return viewer.currentScaleValue;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Check predefined zoom levels
|
|
241
|
-
const predefinedZooms = {
|
|
242
|
-
0.5: '0.5', 0.75: '0.75', 1: '1', 1.25: '1.25',
|
|
243
|
-
1.5: '1.5', 2: '2', 3: '3', 4: '4'
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
for (const [zoomLevel, zoomValue] of Object.entries(predefinedZooms)) {
|
|
247
|
-
if (Math.abs(scale - parseFloat(zoomLevel)) < 0.01) {
|
|
248
|
-
return zoomValue;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return typeof scale === 'number' ? `${Math.round(scale * 100)}%` : 'auto';
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Send state change notifications to Angular component
|
|
257
|
-
function sendStateChangeNotification(property, value, source = 'user') {
|
|
258
|
-
try {
|
|
259
|
-
const message = {
|
|
260
|
-
type: 'state-change',
|
|
261
|
-
property: property,
|
|
262
|
-
value: value,
|
|
263
|
-
source: source,
|
|
264
|
-
timestamp: Date.now()
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
window.parent.postMessage(message, '*');
|
|
268
|
-
} catch (error) {
|
|
269
|
-
log(`Failed to send state change notification: ${error.message}`, 'error');
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Send event notifications to Angular component
|
|
274
|
-
function sendEventNotification(eventName, eventData) {
|
|
275
|
-
try {
|
|
276
|
-
const message = {
|
|
277
|
-
type: 'event-notification',
|
|
278
|
-
eventName: eventName,
|
|
279
|
-
eventData: eventData,
|
|
280
|
-
timestamp: Date.now()
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
window.parent.postMessage(message, '*');
|
|
284
|
-
} catch (error) {
|
|
285
|
-
log(`Failed to send event notification: ${error.message}`, 'error');
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function sendResponse(id, response) {
|
|
290
|
-
if (id && window.parent) {
|
|
291
|
-
window.parent.postMessage({
|
|
292
|
-
type: 'control-response',
|
|
293
|
-
id: id,
|
|
294
|
-
...response,
|
|
295
|
-
timestamp: Date.now()
|
|
296
|
-
}, '*');
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function getState() {
|
|
301
|
-
const app = PDFViewerApplication;
|
|
302
|
-
if (!app) {
|
|
303
|
-
return { ready: false };
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
ready: app.initialized,
|
|
308
|
-
page: app.page,
|
|
309
|
-
pagesCount: app.pagesCount,
|
|
310
|
-
currentScale: app.pdfViewer ? app.pdfViewer.currentScale : null,
|
|
311
|
-
currentScaleValue: app.pdfViewer ? app.pdfViewer.currentScaleValue : null,
|
|
312
|
-
pagesRotation: app.pdfViewer ? app.pdfViewer.pagesRotation : null,
|
|
313
|
-
scrollMode: app.pdfViewer ? app.pdfViewer.scrollMode : null,
|
|
314
|
-
spreadMode: app.pdfViewer ? app.pdfViewer.spreadMode : null
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Common control update utilities
|
|
319
|
-
const ControlUtils = {
|
|
320
|
-
// Common button visibility update pattern
|
|
321
|
-
updateButtonVisibility: function(buttonId, secondaryId, visible) {
|
|
322
|
-
const button = document.getElementById(buttonId);
|
|
323
|
-
const secondaryButton = secondaryId ? document.getElementById(secondaryId) : null;
|
|
324
|
-
|
|
325
|
-
if (button) {
|
|
326
|
-
button.classList.toggle('hidden', !visible);
|
|
327
|
-
}
|
|
328
|
-
if (secondaryButton) {
|
|
329
|
-
secondaryButton.classList.toggle('hidden', !visible);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
// Common mode update pattern with event bus
|
|
335
|
-
updateModeViaEventBus: function(eventName, modeValue, modeMap, propertyName) {
|
|
336
|
-
const app = PDFViewerApplication;
|
|
337
|
-
if (!app?.eventBus) {
|
|
338
|
-
log('EventBus not available for mode update', 'error');
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
try {
|
|
343
|
-
const upperMode = typeof modeValue === 'string' ? modeValue.toUpperCase() : modeValue;
|
|
344
|
-
const mode = modeMap[upperMode];
|
|
345
|
-
|
|
346
|
-
if (mode !== undefined) {
|
|
347
|
-
app.eventBus.dispatch(eventName, { mode });
|
|
348
|
-
|
|
349
|
-
// NOTE: State change notifications are sent by the bidirectional event listeners
|
|
350
|
-
// when the actual mode change occurs in PDF.js, not immediately when we dispatch
|
|
351
|
-
// This prevents infinite loops and ensures we only notify on actual changes
|
|
352
|
-
} else {
|
|
353
|
-
log(`Unknown ${propertyName} mode: ${modeValue}`, 'warn');
|
|
354
|
-
}
|
|
355
|
-
} catch (error) {
|
|
356
|
-
log(`Error updating ${propertyName} mode: ${error.message}`, 'error');
|
|
357
|
-
}
|
|
358
|
-
},
|
|
359
|
-
|
|
360
|
-
// Common direct property update pattern
|
|
361
|
-
updatePropertyDirectly: function(propertyPath, value, propertyName) {
|
|
362
|
-
const app = PDFViewerApplication;
|
|
363
|
-
if (!app?.pdfViewer) {
|
|
364
|
-
log(`PDFViewer not available for ${propertyName} update`, 'warn');
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
// Navigate to nested property
|
|
370
|
-
const pathParts = propertyPath.split('.');
|
|
371
|
-
let target = app;
|
|
372
|
-
|
|
373
|
-
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
374
|
-
target = target[pathParts[i]];
|
|
375
|
-
if (!target) {
|
|
376
|
-
log(`Property path ${propertyPath} not found for ${propertyName}`, 'warn');
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
target[pathParts[pathParts.length - 1]] = value;
|
|
382
|
-
} catch (error) {
|
|
383
|
-
log(`Direct ${propertyName} update failed: ${error.message}`, 'warn');
|
|
384
|
-
}
|
|
385
|
-
},
|
|
386
|
-
|
|
387
|
-
// Common validation pattern
|
|
388
|
-
validateAndExecute: function(condition, action, errorMessage) {
|
|
389
|
-
if (condition) {
|
|
390
|
-
action();
|
|
391
|
-
} else {
|
|
392
|
-
log(errorMessage, 'warn');
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
// #endregion
|
|
397
|
-
|
|
398
|
-
// #region Initialization and Setup
|
|
399
|
-
|
|
400
|
-
// Enhanced readiness checking with proper async initialization handling
|
|
401
|
-
function checkViewerReadiness() {
|
|
402
|
-
const app = PDFViewerApplication;
|
|
403
|
-
|
|
404
|
-
if (!app) {
|
|
405
|
-
setReadiness(ViewerReadiness.NOT_LOADED);
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
setReadiness(ViewerReadiness.VIEWER_LOADED);
|
|
410
|
-
|
|
411
|
-
// Handle async initialization properly
|
|
412
|
-
if (app.initializedPromise) {
|
|
413
|
-
app.initializedPromise.then(() => {
|
|
414
|
-
setReadiness(ViewerReadiness.VIEWER_INITIALIZED);
|
|
415
|
-
|
|
416
|
-
// Check if event bus is ready
|
|
417
|
-
if (app.eventBus && typeof app.eventBus.dispatch === 'function') {
|
|
418
|
-
setReadiness(ViewerReadiness.EVENTBUS_READY);
|
|
419
|
-
|
|
420
|
-
// Check if key components are available
|
|
421
|
-
if (app.pdfViewer && app.pdfCursorTools) {
|
|
422
|
-
setReadiness(ViewerReadiness.COMPONENTS_READY);
|
|
423
|
-
|
|
424
|
-
// CSS zoom setting is now handled via PostMessage configuration
|
|
425
|
-
// No need to check global window variables
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}).catch(error => {
|
|
429
|
-
log(`PDFViewerApplication initialization failed: ${error.message}`, 'error');
|
|
430
|
-
});
|
|
431
|
-
} else if (app.initialized) {
|
|
432
|
-
// Fallback for synchronous check (though this should be async)
|
|
433
|
-
setReadiness(ViewerReadiness.VIEWER_INITIALIZED);
|
|
434
|
-
|
|
435
|
-
// Check if event bus is ready
|
|
436
|
-
if (app.eventBus && typeof app.eventBus.dispatch === 'function') {
|
|
437
|
-
setReadiness(ViewerReadiness.EVENTBUS_READY);
|
|
438
|
-
|
|
439
|
-
// Check if key components are available
|
|
440
|
-
if (app.pdfViewer && app.pdfCursorTools) {
|
|
441
|
-
setReadiness(ViewerReadiness.COMPONENTS_READY);
|
|
442
|
-
|
|
443
|
-
// CSS zoom setting is now handled via PostMessage configuration
|
|
444
|
-
// No need to check global window variables
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Wait for PDF.js viewer to be ready with event-driven approach
|
|
451
|
-
function waitForViewer() {
|
|
452
|
-
// Use MutationObserver to detect when PDFViewerApplication becomes available
|
|
453
|
-
const observer = new MutationObserver((mutations, obs) => {
|
|
454
|
-
if (typeof PDFViewerApplication !== 'undefined') {
|
|
455
|
-
obs.disconnect(); // Stop observing
|
|
456
|
-
checkViewerReadiness();
|
|
457
|
-
|
|
458
|
-
// Set up event-driven readiness monitoring
|
|
459
|
-
setupEventDrivenReadinessMonitoring();
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
// Start observing DOM changes
|
|
464
|
-
observer.observe(document, {
|
|
465
|
-
childList: true,
|
|
466
|
-
subtree: true
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// Also check immediately in case it's already available
|
|
470
|
-
if (typeof PDFViewerApplication !== 'undefined') {
|
|
471
|
-
log('PDFViewerApplication already available, starting readiness check');
|
|
472
|
-
observer.disconnect();
|
|
473
|
-
checkViewerReadiness();
|
|
474
|
-
setupEventDrivenReadinessMonitoring();
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Set up event-driven readiness monitoring
|
|
479
|
-
function setupEventDrivenReadinessMonitoring() {
|
|
480
|
-
// Use a custom event system for readiness changes
|
|
481
|
-
const readinessEventTarget = new EventTarget();
|
|
482
|
-
|
|
483
|
-
// Override setReadiness to emit events
|
|
484
|
-
const originalSetReadiness = setReadiness;
|
|
485
|
-
setReadiness = function(readiness) {
|
|
486
|
-
originalSetReadiness(readiness);
|
|
487
|
-
|
|
488
|
-
// Emit custom event for readiness change
|
|
489
|
-
readinessEventTarget.dispatchEvent(new CustomEvent('readiness-change', {
|
|
490
|
-
detail: { readiness, previousReadiness: currentReadiness }
|
|
491
|
-
}));
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
// Listen for readiness changes
|
|
495
|
-
readinessEventTarget.addEventListener('readiness-change', (event) => {
|
|
496
|
-
const { readiness } = event.detail;
|
|
497
|
-
|
|
498
|
-
if (readiness >= ViewerReadiness.EVENTBUS_READY) {
|
|
499
|
-
|
|
500
|
-
initializePostMessageAPI();
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// Check initial readiness
|
|
505
|
-
if (currentReadiness >= ViewerReadiness.EVENTBUS_READY) {
|
|
506
|
-
|
|
507
|
-
initializePostMessageAPI();
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function initializePostMessageAPI() {
|
|
512
|
-
// Add message listener
|
|
513
|
-
window.addEventListener('message', handleControlMessage);
|
|
514
|
-
|
|
515
|
-
// Expose API for external access
|
|
516
|
-
window.Ng2PdfJsViewerAPI = {
|
|
517
|
-
updateControl: updateControl,
|
|
518
|
-
getState: getState,
|
|
519
|
-
isReady: () => currentReadiness >= ViewerReadiness.EVENTBUS_READY,
|
|
520
|
-
getReadiness: () => currentReadiness
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
// Notify parent that PostMessage API is ready
|
|
524
|
-
window.parent.postMessage({
|
|
525
|
-
type: 'postmessage-ready',
|
|
526
|
-
timestamp: Date.now(),
|
|
527
|
-
readiness: currentReadiness
|
|
528
|
-
}, '*');
|
|
529
|
-
|
|
530
|
-
// Set up event listeners for readiness changes
|
|
531
|
-
setupReadinessEventListeners();
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function setupReadinessEventListeners() {
|
|
535
|
-
const app = PDFViewerApplication;
|
|
536
|
-
if (!app || !app.eventBus) return;
|
|
537
|
-
|
|
538
|
-
// Set up error event listeners immediately (before document loads)
|
|
539
|
-
setupErrorEventListeners();
|
|
540
|
-
|
|
541
|
-
// Listen for document loaded event
|
|
542
|
-
app.eventBus.on('documentloaded', () => {
|
|
543
|
-
setReadiness(ViewerReadiness.DOCUMENT_LOADED);
|
|
544
|
-
|
|
545
|
-
// Set up bidirectional event listeners once document is loaded
|
|
546
|
-
setupBidirectionalEventListeners();
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
// Listen for pages loaded event
|
|
550
|
-
app.eventBus.on('pagesloaded', () => {
|
|
551
|
-
// Ensure we're at least at components ready level
|
|
552
|
-
if (currentReadiness < ViewerReadiness.COMPONENTS_READY) {
|
|
553
|
-
setReadiness(ViewerReadiness.COMPONENTS_READY);
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
// #endregion
|
|
558
|
-
|
|
559
|
-
// #region Message Handling
|
|
560
|
-
function handleControlMessage(event) {
|
|
561
|
-
const { type, action, payload, id } = event.data;
|
|
562
|
-
|
|
563
|
-
if (type === 'control-update') {
|
|
564
|
-
try {
|
|
565
|
-
|
|
566
|
-
// Universal Dispatcher has already verified readiness - just execute
|
|
567
|
-
updateControl(action, payload);
|
|
568
|
-
sendResponse(id, { success: true, action, payload });
|
|
569
|
-
} catch (error) {
|
|
570
|
-
const errorMsg = `Error processing ${action}: ${error.message}`;
|
|
571
|
-
log(errorMsg, 'error');
|
|
572
|
-
sendResponse(id, { success: false, error: errorMsg });
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
// #endregion
|
|
577
|
-
|
|
578
|
-
// #region Control Update Functions
|
|
579
|
-
function updateControl(action, payload) {
|
|
580
|
-
const app = PDFViewerApplication;
|
|
581
|
-
|
|
582
|
-
// Validate that PDFViewerApplication is available
|
|
583
|
-
if (!app) {
|
|
584
|
-
log('PDFViewerApplication not available', 'error');
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
try {
|
|
589
|
-
// Note: Density styles loaded from external ng2-customization.css (CSP-safe)
|
|
590
|
-
switch (action) {
|
|
591
|
-
// Button visibility controls (using consolidated helper function)
|
|
592
|
-
case 'show-download':
|
|
593
|
-
toggleButtonVisibility('downloadButton', 'secondaryDownload', payload);
|
|
594
|
-
break;
|
|
595
|
-
case 'show-print':
|
|
596
|
-
toggleButtonVisibility('printButton', 'secondaryPrint', payload);
|
|
597
|
-
break;
|
|
598
|
-
case 'show-fullscreen':
|
|
599
|
-
toggleButtonVisibility('presentationMode', null, payload);
|
|
600
|
-
break;
|
|
601
|
-
case 'show-find':
|
|
602
|
-
toggleButtonVisibility('viewFindButton', null, payload);
|
|
603
|
-
break;
|
|
604
|
-
case 'show-bookmark':
|
|
605
|
-
toggleButtonVisibility('viewBookmark', null, payload);
|
|
606
|
-
break;
|
|
607
|
-
case 'show-openfile':
|
|
608
|
-
toggleButtonVisibility('openFile', 'secondaryOpenFile', payload);
|
|
609
|
-
break;
|
|
610
|
-
case 'show-annotations':
|
|
611
|
-
toggleElementVisibilityById('editorModeButtons', payload);
|
|
612
|
-
break;
|
|
613
|
-
|
|
614
|
-
case 'set-toolbar-density': {
|
|
615
|
-
// Apply compact/comfortable density by toggling a class on #toolbarContainer
|
|
616
|
-
const container = document.getElementById('toolbarContainer');
|
|
617
|
-
if (container) {
|
|
618
|
-
container.classList.remove('density-default','density-compact','density-comfortable');
|
|
619
|
-
const density = typeof payload === 'string' ? payload : 'default';
|
|
620
|
-
container.classList.add(`density-${density}`);
|
|
621
|
-
}
|
|
622
|
-
break;
|
|
623
|
-
}
|
|
624
|
-
case 'set-sidebar-width': {
|
|
625
|
-
// Use CSS variable only (CSP-safe) - width applied via ng2-customization.css
|
|
626
|
-
const outer = document.getElementById('outerContainer');
|
|
627
|
-
if (outer && typeof payload === 'string' && payload.trim() !== '') {
|
|
628
|
-
outer.style.setProperty('--sidebar-width', payload);
|
|
629
|
-
}
|
|
630
|
-
break;
|
|
631
|
-
}
|
|
632
|
-
case 'set-toolbar-position': {
|
|
633
|
-
const outer = document.getElementById('outerContainer');
|
|
634
|
-
if (outer) {
|
|
635
|
-
outer.classList.remove('toolbar-bottom');
|
|
636
|
-
if (payload === 'bottom') outer.classList.add('toolbar-bottom');
|
|
637
|
-
}
|
|
638
|
-
break;
|
|
639
|
-
}
|
|
640
|
-
case 'set-sidebar-position': {
|
|
641
|
-
const outer = document.getElementById('outerContainer');
|
|
642
|
-
if (outer) {
|
|
643
|
-
outer.classList.remove('sidebar-right');
|
|
644
|
-
if (payload === 'right') outer.classList.add('sidebar-right');
|
|
645
|
-
}
|
|
646
|
-
break;
|
|
647
|
-
}
|
|
648
|
-
case 'set-responsive-breakpoint': {
|
|
649
|
-
const value = typeof payload === 'number' ? `${payload}px` : `${payload}`;
|
|
650
|
-
const outer = document.getElementById('outerContainer');
|
|
651
|
-
if (outer && value) {
|
|
652
|
-
outer.style.setProperty('--ng2-responsive-breakpoint', value);
|
|
653
|
-
}
|
|
654
|
-
break;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
case 'show-toolbar-left':
|
|
658
|
-
toggleToolbarSectionVisibility('toolbarViewerLeft', payload);
|
|
659
|
-
break;
|
|
660
|
-
case 'show-toolbar-middle':
|
|
661
|
-
toggleToolbarSectionVisibility('toolbarViewerMiddle', payload);
|
|
662
|
-
break;
|
|
663
|
-
case 'show-toolbar-right':
|
|
664
|
-
toggleToolbarSectionVisibility('toolbarViewerRight', payload);
|
|
665
|
-
break;
|
|
666
|
-
case 'show-secondary-toolbar-toggle':
|
|
667
|
-
toggleElementVisibilityById('secondaryToolbarToggle', payload);
|
|
668
|
-
break;
|
|
669
|
-
case 'show-sidebar':
|
|
670
|
-
toggleElementVisibilityById('sidebarContainer', payload);
|
|
671
|
-
break;
|
|
672
|
-
case 'show-sidebar-left':
|
|
673
|
-
toggleElementVisibilityById('toolbarSidebarLeft', payload);
|
|
674
|
-
break;
|
|
675
|
-
case 'show-sidebar-right':
|
|
676
|
-
toggleElementVisibilityById('toolbarSidebarRight', payload);
|
|
677
|
-
break;
|
|
678
|
-
|
|
679
|
-
// Mode controls
|
|
680
|
-
case 'set-zoom':
|
|
681
|
-
updateZoom(payload);
|
|
682
|
-
break;
|
|
683
|
-
case 'set-cursor':
|
|
684
|
-
updateCursor(payload);
|
|
685
|
-
break;
|
|
686
|
-
case 'set-scroll':
|
|
687
|
-
ControlUtils.updateModeViaEventBus('switchscrollmode', payload,
|
|
688
|
-
{ 'VERTICAL': 0, 'V': 0, 'HORIZONTAL': 1, 'H': 1, 'WRAPPED': 2, 'W': 2, 'PAGE': 3, 'P': 3 },
|
|
689
|
-
'scroll mode');
|
|
690
|
-
ControlUtils.updatePropertyDirectly('pdfViewer.scrollMode',
|
|
691
|
-
({ 'VERTICAL': 0, 'V': 0, 'HORIZONTAL': 1, 'H': 1, 'WRAPPED': 2, 'W': 2, 'PAGE': 3, 'P': 3 })[payload?.toUpperCase()] || 0,
|
|
692
|
-
'scroll mode');
|
|
693
|
-
break;
|
|
694
|
-
case 'set-spread':
|
|
695
|
-
ControlUtils.updateModeViaEventBus('switchspreadmode', payload,
|
|
696
|
-
{ 'NONE': 0, 'N': 0, 'ODD': 1, 'O': 1, 'EVEN': 2, 'E': 2 },
|
|
697
|
-
'spread mode');
|
|
698
|
-
ControlUtils.updatePropertyDirectly('pdfViewer.spreadMode',
|
|
699
|
-
({ 'NONE': 0, 'N': 0, 'ODD': 1, 'O': 1, 'EVEN': 2, 'E': 2 })[payload?.toUpperCase()] || 0,
|
|
700
|
-
'spread mode');
|
|
701
|
-
break;
|
|
702
|
-
|
|
703
|
-
// Navigation controls
|
|
704
|
-
case 'set-page':
|
|
705
|
-
// Universal Dispatcher guarantees PDFViewerApplication.initialized at readiness level 5
|
|
706
|
-
const pageNumber = parseInt(payload, 10);
|
|
707
|
-
if (pageNumber > 0 && pageNumber <= PDFViewerApplication.pagesCount) {
|
|
708
|
-
PDFViewerApplication.page = pageNumber;
|
|
709
|
-
} else {
|
|
710
|
-
log(`Invalid page number: ${payload}`, 'warn');
|
|
711
|
-
}
|
|
712
|
-
break;
|
|
713
|
-
case 'set-rotation':
|
|
714
|
-
// Universal Dispatcher guarantees PDFViewerApplication.initialized at readiness level 5
|
|
715
|
-
const rotation = parseInt(payload, 10);
|
|
716
|
-
if ([0, 90, 180, 270].includes(rotation)) {
|
|
717
|
-
// Set rotation on pdfViewer to trigger proper refresh
|
|
718
|
-
PDFViewerApplication.pdfViewer.pagesRotation = rotation;
|
|
719
|
-
// Note: No need to send state change notification here -
|
|
720
|
-
// PDF.js will fire 'rotationchanging' event which we handle separately
|
|
721
|
-
} else {
|
|
722
|
-
log(`Invalid rotation: ${payload}`, 'warn');
|
|
723
|
-
}
|
|
724
|
-
break;
|
|
725
|
-
case 'go-to-last-page':
|
|
726
|
-
if (payload === true) {
|
|
727
|
-
// Universal Dispatcher guarantees PDFViewerApplication.initialized at readiness level 5
|
|
728
|
-
const lastPage = PDFViewerApplication.pagesCount;
|
|
729
|
-
PDFViewerApplication.page = lastPage;
|
|
730
|
-
}
|
|
731
|
-
break;
|
|
732
|
-
case 'go-to-named-dest':
|
|
733
|
-
// Validate that payload is not empty or null before processing
|
|
734
|
-
if (!payload || typeof payload !== 'string' || payload.trim() === '') {
|
|
735
|
-
log(`Skipping invalid named destination: "${payload}"`);
|
|
736
|
-
break;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// Universal Dispatcher guarantees PDFViewerApplication.pdfLinkService at readiness level 5
|
|
740
|
-
PDFViewerApplication.pdfLinkService.goToDestination(payload);
|
|
741
|
-
break;
|
|
742
|
-
case 'update-page-mode':
|
|
743
|
-
// Universal Dispatcher guarantees PDFViewerApplication.eventBus at readiness level 4
|
|
744
|
-
const mode = payload ? payload.toLowerCase() : 'none';
|
|
745
|
-
PDFViewerApplication.eventBus.dispatch('pagemode', { mode });
|
|
746
|
-
break;
|
|
747
|
-
|
|
748
|
-
// Configuration actions
|
|
749
|
-
case 'set-download-filename':
|
|
750
|
-
setDownloadFilename(payload);
|
|
751
|
-
break;
|
|
752
|
-
|
|
753
|
-
// Auto actions - Universal Dispatcher guarantees eventBus availability at readiness level 5
|
|
754
|
-
case 'trigger-download':
|
|
755
|
-
if (payload === true) {
|
|
756
|
-
PDFViewerApplication.eventBus.dispatch('download');
|
|
757
|
-
}
|
|
758
|
-
break;
|
|
759
|
-
case 'trigger-print':
|
|
760
|
-
if (payload === true) {
|
|
761
|
-
PDFViewerApplication.eventBus.dispatch('print');
|
|
762
|
-
}
|
|
763
|
-
break;
|
|
764
|
-
case 'trigger-rotate-cw':
|
|
765
|
-
if (payload === true) {
|
|
766
|
-
PDFViewerApplication.eventBus.dispatch('rotatecw');
|
|
767
|
-
}
|
|
768
|
-
break;
|
|
769
|
-
case 'trigger-rotate-ccw':
|
|
770
|
-
if (payload === true) {
|
|
771
|
-
PDFViewerApplication.eventBus.dispatch('rotateccw');
|
|
772
|
-
}
|
|
773
|
-
break;
|
|
774
|
-
|
|
775
|
-
// Error handling
|
|
776
|
-
case 'set-error-message':
|
|
777
|
-
setErrorMessage(payload);
|
|
778
|
-
break;
|
|
779
|
-
case 'set-error-override':
|
|
780
|
-
setErrorOverride(payload);
|
|
781
|
-
break;
|
|
782
|
-
case 'set-error-append':
|
|
783
|
-
setErrorAppend(payload);
|
|
784
|
-
break;
|
|
785
|
-
|
|
786
|
-
// CSS zoom
|
|
787
|
-
case 'set-css-zoom':
|
|
788
|
-
setCssZoom(payload);
|
|
789
|
-
break;
|
|
790
|
-
|
|
791
|
-
// Event enablement
|
|
792
|
-
case 'enable-before-print':
|
|
793
|
-
enableBeforePrint(payload);
|
|
794
|
-
break;
|
|
795
|
-
case 'enable-after-print':
|
|
796
|
-
enableAfterPrint(payload);
|
|
797
|
-
break;
|
|
798
|
-
case 'enable-pages-loaded':
|
|
799
|
-
enablePagesLoaded(payload);
|
|
800
|
-
break;
|
|
801
|
-
case 'enable-page-change':
|
|
802
|
-
enablePageChange(payload);
|
|
803
|
-
break;
|
|
804
|
-
|
|
805
|
-
// New high-value event enablement
|
|
806
|
-
case 'enable-document-error':
|
|
807
|
-
enableDocumentError(payload);
|
|
808
|
-
break;
|
|
809
|
-
case 'enable-document-init':
|
|
810
|
-
enableDocumentInit(payload);
|
|
811
|
-
break;
|
|
812
|
-
case 'enable-pages-init':
|
|
813
|
-
enablePagesInit(payload);
|
|
814
|
-
break;
|
|
815
|
-
case 'enable-presentation-mode-changed':
|
|
816
|
-
enablePresentationModeChanged(payload);
|
|
817
|
-
break;
|
|
818
|
-
case 'enable-open-file':
|
|
819
|
-
enableOpenFile(payload);
|
|
820
|
-
break;
|
|
821
|
-
case 'enable-find':
|
|
822
|
-
enableFind(payload);
|
|
823
|
-
break;
|
|
824
|
-
case 'enable-update-find-matches-count':
|
|
825
|
-
enableUpdateFindMatchesCount(payload);
|
|
826
|
-
break;
|
|
827
|
-
case 'enable-metadata-loaded':
|
|
828
|
-
enableMetadataLoaded(payload);
|
|
829
|
-
break;
|
|
830
|
-
case 'enable-outline-loaded':
|
|
831
|
-
enableOutlineLoaded(payload);
|
|
832
|
-
break;
|
|
833
|
-
case 'enable-page-rendered':
|
|
834
|
-
enablePageRendered(payload);
|
|
835
|
-
break;
|
|
836
|
-
|
|
837
|
-
// New high-value events
|
|
838
|
-
case 'enable-annotation-layer-rendered':
|
|
839
|
-
enableAnnotationLayerRendered(payload);
|
|
840
|
-
break;
|
|
841
|
-
case 'enable-bookmark-click':
|
|
842
|
-
enableBookmarkClick(payload);
|
|
843
|
-
break;
|
|
844
|
-
case 'enable-idle':
|
|
845
|
-
enableIdle(payload);
|
|
846
|
-
break;
|
|
847
|
-
|
|
848
|
-
// Theme & Visual Customization Actions
|
|
849
|
-
case 'set-theme':
|
|
850
|
-
setTheme(payload);
|
|
851
|
-
break;
|
|
852
|
-
case 'set-primary-color':
|
|
853
|
-
setPrimaryColor(payload);
|
|
854
|
-
break;
|
|
855
|
-
case 'set-background-color':
|
|
856
|
-
setBackgroundColor(payload);
|
|
857
|
-
break;
|
|
858
|
-
case 'set-page-border-color':
|
|
859
|
-
setPageBorderColor(payload);
|
|
860
|
-
break;
|
|
861
|
-
case 'set-page-spacing':
|
|
862
|
-
setPageSpacing(payload.margin, payload.spreadMargin, payload.border);
|
|
863
|
-
break;
|
|
864
|
-
case 'set-toolbar-color':
|
|
865
|
-
setToolbarColor(payload);
|
|
866
|
-
break;
|
|
867
|
-
case 'set-text-color':
|
|
868
|
-
setTextColor(payload);
|
|
869
|
-
break;
|
|
870
|
-
case 'set-border-radius':
|
|
871
|
-
setBorderRadius(payload);
|
|
872
|
-
break;
|
|
873
|
-
case 'set-custom-css':
|
|
874
|
-
// Handle both old format (string) and new format (object with nonce)
|
|
875
|
-
if (typeof payload === 'string') {
|
|
876
|
-
setCustomCSS(payload, null);
|
|
877
|
-
} else if (payload && typeof payload === 'object') {
|
|
878
|
-
setCustomCSS(payload.css, payload.nonce);
|
|
879
|
-
}
|
|
880
|
-
break;
|
|
881
|
-
|
|
882
|
-
case 'set-diagnostic-logs':
|
|
883
|
-
setDiagnosticLogs(payload);
|
|
884
|
-
break;
|
|
885
|
-
case 'set-url-validation':
|
|
886
|
-
urlValidationEnabled = payload === true;
|
|
887
|
-
log(`URL validation ${urlValidationEnabled ? 'enabled' : 'disabled'}`);
|
|
888
|
-
break;
|
|
889
|
-
|
|
890
|
-
default:
|
|
891
|
-
log(`Unknown action: ${action}`, 'warn');
|
|
892
|
-
}
|
|
893
|
-
} catch (error) {
|
|
894
|
-
log(`Error in updateControl for action ${action}: ${error.message}`, 'error');
|
|
895
|
-
throw error;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
// #endregion
|
|
899
|
-
|
|
900
|
-
// #region Button Visibility Functions
|
|
901
|
-
function updateDownloadButton(visible) {
|
|
902
|
-
const button = document.getElementById('downloadButton');
|
|
903
|
-
const secondaryButton = document.getElementById('secondaryDownload');
|
|
904
|
-
if (button) {
|
|
905
|
-
button.classList.toggle('hidden', !visible);
|
|
906
|
-
}
|
|
907
|
-
if (secondaryButton) {
|
|
908
|
-
secondaryButton.classList.toggle('hidden', !visible);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function updatePrintButton(visible) {
|
|
913
|
-
const button = document.getElementById('printButton');
|
|
914
|
-
const secondaryButton = document.getElementById('secondaryPrint');
|
|
915
|
-
if (button) {
|
|
916
|
-
button.classList.toggle('hidden', !visible);
|
|
917
|
-
}
|
|
918
|
-
if (secondaryButton) {
|
|
919
|
-
secondaryButton.classList.toggle('hidden', !visible);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
function updateFullScreenButton(visible) {
|
|
924
|
-
const button = document.getElementById('presentationMode');
|
|
925
|
-
if (button) {
|
|
926
|
-
button.classList.toggle('hidden', !visible);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
function updateFindButton(visible) {
|
|
931
|
-
const button = document.getElementById('viewFindButton');
|
|
932
|
-
if (button) {
|
|
933
|
-
button.classList.toggle('hidden', !visible);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function updateBookmarkButton(visible) {
|
|
938
|
-
const button = document.getElementById('viewBookmark');
|
|
939
|
-
if (button) {
|
|
940
|
-
button.classList.toggle('hidden', !visible);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
function updateOpenFileButton(visible) {
|
|
945
|
-
const button = document.getElementById('openFile');
|
|
946
|
-
const secondaryButton = document.getElementById('secondaryOpenFile');
|
|
947
|
-
if (button) {
|
|
948
|
-
button.classList.toggle('hidden', !visible);
|
|
949
|
-
}
|
|
950
|
-
if (secondaryButton) {
|
|
951
|
-
secondaryButton.classList.toggle('hidden', !visible);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
function updateAnnotationsButton(visible) {
|
|
955
|
-
// Handle annotations button visibility
|
|
956
|
-
// This might need to be implemented based on the specific annotation system
|
|
957
|
-
log(`Annotations button visibility set to: ${visible}`);
|
|
958
|
-
}
|
|
959
|
-
// #endregion
|
|
960
|
-
|
|
961
|
-
// #region Mode Control Functions
|
|
962
|
-
function updateZoom(zoom) {
|
|
963
|
-
const app = PDFViewerApplication;
|
|
964
|
-
if (!app || !app.pdfViewer || !app.eventBus) {
|
|
965
|
-
log('PDFViewerApplication, pdfViewer, or eventBus not ready for zoom update', 'warn');
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
// Mark zoom command as active to prevent infinite loop
|
|
970
|
-
ZoomCommandTracker.markZoomCommandStart();
|
|
971
|
-
|
|
972
|
-
try {
|
|
973
|
-
// Acceptable values: "auto", "page-fit", "page-width", "page-actual", "page-height", or a number/string number like "1.25"
|
|
974
|
-
const validStringZooms = ['auto', 'page-actual', 'page-fit', 'page-width', 'page-height'];
|
|
975
|
-
|
|
976
|
-
// Check if it's a valid string zoom
|
|
977
|
-
if (validStringZooms.includes(zoom)) {
|
|
978
|
-
// Dispatch the scalechanged event exactly like the UI does
|
|
979
|
-
app.eventBus.dispatch("scalechanged", {
|
|
980
|
-
source: app.toolbar, // Use toolbar as source, just like the UI does
|
|
981
|
-
value: zoom
|
|
982
|
-
});
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
// Check if it's a valid numeric zoom (including decimal strings like "1.25")
|
|
987
|
-
const numericZoom = Number(zoom);
|
|
988
|
-
if (!isNaN(numericZoom) && numericZoom > 0) {
|
|
989
|
-
// Dispatch the scalechanged event exactly like the UI does
|
|
990
|
-
app.eventBus.dispatch("scalechanged", {
|
|
991
|
-
source: app.toolbar, // Use toolbar as source, just like the UI does
|
|
992
|
-
value: zoom
|
|
993
|
-
});
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// If we get here, it's invalid
|
|
998
|
-
log(`Invalid zoom value: ${zoom}`, 'warn');
|
|
999
|
-
} finally {
|
|
1000
|
-
// Always clear the command flag, even if there's an error
|
|
1001
|
-
ZoomCommandTracker.markZoomCommandEnd();
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
function updateCursor(cursor) {
|
|
1006
|
-
// Universal Dispatcher guarantees PDFViewerApplication availability at readiness level 4
|
|
1007
|
-
const app = PDFViewerApplication;
|
|
1008
|
-
|
|
1009
|
-
try {
|
|
1010
|
-
const cursorTool = cursor ? cursor.toUpperCase() : 'SELECT';
|
|
1011
|
-
|
|
1012
|
-
let toolId = 0; // Default to SELECT
|
|
1013
|
-
switch (cursorTool) {
|
|
1014
|
-
case 'HAND':
|
|
1015
|
-
case 'H':
|
|
1016
|
-
toolId = 1; // HAND
|
|
1017
|
-
break;
|
|
1018
|
-
case 'SELECT':
|
|
1019
|
-
case 'S':
|
|
1020
|
-
toolId = 0; // SELECT
|
|
1021
|
-
break;
|
|
1022
|
-
case 'ZOOM':
|
|
1023
|
-
case 'Z':
|
|
1024
|
-
toolId = 2; // ZOOM
|
|
1025
|
-
break;
|
|
1026
|
-
default:
|
|
1027
|
-
log(`Unknown cursor tool: ${cursorTool}, defaulting to SELECT`, 'warn');
|
|
1028
|
-
toolId = 0;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
// Update cursor using event bus dispatch (PDF.js v4.x)
|
|
1032
|
-
if (app.eventBus && typeof app.eventBus.dispatch === 'function') {
|
|
1033
|
-
app.eventBus.dispatch('switchcursortool', {
|
|
1034
|
-
tool: toolId
|
|
1035
|
-
});
|
|
1036
|
-
} else {
|
|
1037
|
-
log('EventBus not available for cursor update', 'warn');
|
|
1038
|
-
}
|
|
1039
|
-
} catch (error) {
|
|
1040
|
-
log(`Error updating cursor: ${error.message}`, 'error');
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
function updateScroll(scroll) {
|
|
1045
|
-
// Universal Dispatcher guarantees PDFViewerApplication availability at readiness level 4
|
|
1046
|
-
|
|
1047
|
-
try {
|
|
1048
|
-
const scrollMode = scroll ? scroll.toUpperCase() : 'VERTICAL';
|
|
1049
|
-
log(`Attempting to update scroll mode to: ${scrollMode}`);
|
|
1050
|
-
|
|
1051
|
-
if (PDFViewerApplication.eventBus) {
|
|
1052
|
-
// Use PDF.js v4.x event bus dispatch for scroll mode switching
|
|
1053
|
-
switch (scrollMode) {
|
|
1054
|
-
case 'VERTICAL':
|
|
1055
|
-
case 'V':
|
|
1056
|
-
log('Dispatching switchscrollmode with VERTICAL mode');
|
|
1057
|
-
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1058
|
-
mode: 0 // ScrollMode.VERTICAL
|
|
1059
|
-
});
|
|
1060
|
-
break;
|
|
1061
|
-
case 'HORIZONTAL':
|
|
1062
|
-
case 'H':
|
|
1063
|
-
log('Dispatching switchscrollmode with HORIZONTAL mode');
|
|
1064
|
-
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1065
|
-
mode: 1 // ScrollMode.HORIZONTAL
|
|
1066
|
-
});
|
|
1067
|
-
break;
|
|
1068
|
-
case 'WRAPPED':
|
|
1069
|
-
case 'W':
|
|
1070
|
-
log('Dispatching switchscrollmode with WRAPPED mode');
|
|
1071
|
-
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1072
|
-
mode: 2 // ScrollMode.WRAPPED
|
|
1073
|
-
});
|
|
1074
|
-
break;
|
|
1075
|
-
case 'PAGE':
|
|
1076
|
-
case 'P':
|
|
1077
|
-
log('Dispatching switchscrollmode with PAGE mode');
|
|
1078
|
-
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1079
|
-
mode: 3 // ScrollMode.PAGE
|
|
1080
|
-
});
|
|
1081
|
-
break;
|
|
1082
|
-
default:
|
|
1083
|
-
log(`Unknown scroll mode: ${scrollMode}, defaulting to VERTICAL`, 'warn');
|
|
1084
|
-
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1085
|
-
mode: 0
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
} else {
|
|
1089
|
-
// Fallback to direct property setting if event bus not available
|
|
1090
|
-
if (PDFViewerApplication.pdfViewer) {
|
|
1091
|
-
switch (scrollMode) {
|
|
1092
|
-
case 'VERTICAL':
|
|
1093
|
-
case 'V':
|
|
1094
|
-
PDFViewerApplication.pdfViewer.scrollMode = 0;
|
|
1095
|
-
break;
|
|
1096
|
-
case 'HORIZONTAL':
|
|
1097
|
-
case 'H':
|
|
1098
|
-
PDFViewerApplication.pdfViewer.scrollMode = 1;
|
|
1099
|
-
break;
|
|
1100
|
-
case 'WRAPPED':
|
|
1101
|
-
case 'W':
|
|
1102
|
-
PDFViewerApplication.pdfViewer.scrollMode = 2;
|
|
1103
|
-
break;
|
|
1104
|
-
case 'PAGE':
|
|
1105
|
-
case 'P':
|
|
1106
|
-
PDFViewerApplication.pdfViewer.scrollMode = 3;
|
|
1107
|
-
break;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
log(`Scroll mode update completed for: ${scrollMode}`);
|
|
1113
|
-
} catch (error) {
|
|
1114
|
-
log(`Error updating scroll mode: ${error.message}`, 'error');
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
function updateSpread(spread) {
|
|
1119
|
-
// Universal Dispatcher guarantees PDFViewerApplication availability at readiness level 4
|
|
1120
|
-
|
|
1121
|
-
try {
|
|
1122
|
-
const spreadMode = spread ? spread.toUpperCase() : 'NONE';
|
|
1123
|
-
log(`Attempting to update spread mode to: ${spreadMode}`);
|
|
1124
|
-
|
|
1125
|
-
if (PDFViewerApplication.eventBus) {
|
|
1126
|
-
// Use PDF.js v4.x event bus dispatch for spread mode switching
|
|
1127
|
-
switch (spreadMode) {
|
|
1128
|
-
case 'NONE':
|
|
1129
|
-
case 'N':
|
|
1130
|
-
log('Dispatching switchspreadmode with NONE mode');
|
|
1131
|
-
PDFViewerApplication.eventBus.dispatch('switchspreadmode', {
|
|
1132
|
-
mode: 0 // SpreadMode.NONE
|
|
1133
|
-
});
|
|
1134
|
-
break;
|
|
1135
|
-
case 'ODD':
|
|
1136
|
-
case 'O':
|
|
1137
|
-
log('Dispatching switchspreadmode with ODD mode');
|
|
1138
|
-
PDFViewerApplication.eventBus.dispatch('switchspreadmode', {
|
|
1139
|
-
mode: 1 // SpreadMode.ODD
|
|
1140
|
-
});
|
|
1141
|
-
break;
|
|
1142
|
-
case 'EVEN':
|
|
1143
|
-
case 'E':
|
|
1144
|
-
log('Dispatching switchspreadmode with EVEN mode');
|
|
1145
|
-
PDFViewerApplication.eventBus.dispatch('switchspreadmode', {
|
|
1146
|
-
mode: 2 // SpreadMode.EVEN
|
|
1147
|
-
});
|
|
1148
|
-
break;
|
|
1149
|
-
default:
|
|
1150
|
-
log(`Unknown spread mode: ${spreadMode}, defaulting to NONE`, 'warn');
|
|
1151
|
-
PDFViewerApplication.eventBus.dispatch('switchspreadmode', {
|
|
1152
|
-
mode: 0
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
} else {
|
|
1156
|
-
// Fallback to direct property setting if event bus not available
|
|
1157
|
-
if (PDFViewerApplication.pdfViewer) {
|
|
1158
|
-
switch (spreadMode) {
|
|
1159
|
-
case 'NONE':
|
|
1160
|
-
case 'N':
|
|
1161
|
-
PDFViewerApplication.pdfViewer.spreadMode = 0;
|
|
1162
|
-
break;
|
|
1163
|
-
case 'ODD':
|
|
1164
|
-
case 'O':
|
|
1165
|
-
PDFViewerApplication.pdfViewer.spreadMode = 1;
|
|
1166
|
-
break;
|
|
1167
|
-
case 'EVEN':
|
|
1168
|
-
case 'E':
|
|
1169
|
-
PDFViewerApplication.pdfViewer.spreadMode = 2;
|
|
1170
|
-
break;
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
log(`Spread mode update completed for: ${spreadMode}`);
|
|
1176
|
-
} catch (error) {
|
|
1177
|
-
log(`Error updating spread mode: ${error.message}`, 'error');
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
// #endregion
|
|
1181
|
-
|
|
1182
|
-
// Note: Legacy navigation and auto-action functions removed.
|
|
1183
|
-
// All functionality now handled through the main updateControl switch statement
|
|
1184
|
-
// which trusts the Universal Dispatcher's readiness guarantees.
|
|
1185
|
-
|
|
1186
|
-
// #region Error Handling Functions
|
|
1187
|
-
// Error state is managed internally and communicated via PostMessage
|
|
1188
|
-
// No need for global window variables - events drive the behavior
|
|
1189
|
-
function setErrorMessage(message) {
|
|
1190
|
-
// Error messages are sent directly via PostMessage when errors occur
|
|
1191
|
-
// No need to store in global variables
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
function setErrorOverride(override) {
|
|
1195
|
-
// Error override is handled by the Angular component
|
|
1196
|
-
// No need to store in global variables
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
function setErrorAppend(append) {
|
|
1200
|
-
// Error append is handled by the Angular component
|
|
1201
|
-
// No need to store in global variables
|
|
1202
|
-
}
|
|
1203
|
-
// #endregion
|
|
1204
|
-
|
|
1205
|
-
// #region CSS Zoom Functions
|
|
1206
|
-
|
|
1207
|
-
function setCssZoom(useCssZoom) {
|
|
1208
|
-
try {
|
|
1209
|
-
if (PDFViewerApplication && PDFViewerApplication.pdfViewer) {
|
|
1210
|
-
// Set CSS zoom mode
|
|
1211
|
-
PDFViewerApplication.pdfViewer.useOnlyCssZoom = useCssZoom === true;
|
|
1212
|
-
} else {
|
|
1213
|
-
// CSS zoom will be set when viewer becomes ready via readiness-based dispatch
|
|
1214
|
-
// No need to store in global variables
|
|
1215
|
-
}
|
|
1216
|
-
} catch (error) {
|
|
1217
|
-
log(`Error setting CSS zoom: ${error.message}`, 'error');
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
function setDownloadFilename(filename) {
|
|
1222
|
-
try {
|
|
1223
|
-
if (filename && PDFViewerApplication) {
|
|
1224
|
-
// Ensure filename ends with .pdf if not already present
|
|
1225
|
-
const processedFilename = filename.endsWith('.pdf') ? filename : `${filename}.pdf`;
|
|
1226
|
-
|
|
1227
|
-
// Set the content disposition filename that PDF.js uses for downloads
|
|
1228
|
-
PDFViewerApplication._contentDispositionFilename = processedFilename;
|
|
1229
|
-
} else {
|
|
1230
|
-
log('Cannot set download filename - invalid filename or PDFViewerApplication not available', 'warn');
|
|
1231
|
-
}
|
|
1232
|
-
} catch (error) {
|
|
1233
|
-
log(`Error setting download filename: ${error.message}`, 'error');
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
// #endregion
|
|
1237
|
-
|
|
1238
|
-
// #region Event Configuration Functions
|
|
1239
|
-
// Event enablement state stored locally (not in global window variables)
|
|
1240
|
-
// This follows v5-upgrade.md principles while maintaining functionality
|
|
1241
|
-
|
|
1242
|
-
// Local state for event enablement
|
|
1243
|
-
const eventEnablement = {
|
|
1244
|
-
beforePrint: false,
|
|
1245
|
-
afterPrint: false,
|
|
1246
|
-
pagesLoaded: false,
|
|
1247
|
-
pageChange: false,
|
|
1248
|
-
documentError: false,
|
|
1249
|
-
documentInit: false,
|
|
1250
|
-
pagesInit: false,
|
|
1251
|
-
presentationModeChanged: false,
|
|
1252
|
-
openFile: false,
|
|
1253
|
-
find: false,
|
|
1254
|
-
updateFindMatchesCount: false,
|
|
1255
|
-
metadataLoaded: false,
|
|
1256
|
-
outlineLoaded: false,
|
|
1257
|
-
pageRendered: false,
|
|
1258
|
-
annotationLayerRendered: false,
|
|
1259
|
-
bookmarkClick: false,
|
|
1260
|
-
idle: false
|
|
1261
|
-
};
|
|
1262
|
-
|
|
1263
|
-
function enableBeforePrint(enable) { eventEnablement.beforePrint = enable === true; }
|
|
1264
|
-
function enableAfterPrint(enable) { eventEnablement.afterPrint = enable === true; }
|
|
1265
|
-
function enablePagesLoaded(enable) { eventEnablement.pagesLoaded = enable === true; }
|
|
1266
|
-
function enablePageChange(enable) { eventEnablement.pageChange = enable === true; }
|
|
1267
|
-
function enableDocumentError(enable) { eventEnablement.documentError = enable === true; }
|
|
1268
|
-
function enableDocumentInit(enable) { eventEnablement.documentInit = enable === true; }
|
|
1269
|
-
function enablePagesInit(enable) { eventEnablement.pagesInit = enable === true; }
|
|
1270
|
-
function enablePresentationModeChanged(enable) { eventEnablement.presentationModeChanged = enable === true; }
|
|
1271
|
-
function enableOpenFile(enable) { eventEnablement.openFile = enable === true; }
|
|
1272
|
-
function enableFind(enable) { eventEnablement.find = enable === true; }
|
|
1273
|
-
function enableUpdateFindMatchesCount(enable) { eventEnablement.updateFindMatchesCount = enable === true; }
|
|
1274
|
-
function enableMetadataLoaded(enable) { eventEnablement.metadataLoaded = enable === true; }
|
|
1275
|
-
function enableOutlineLoaded(enable) { eventEnablement.outlineLoaded = enable === true; }
|
|
1276
|
-
function enablePageRendered(enable) { eventEnablement.pageRendered = enable === true; }
|
|
1277
|
-
function enableAnnotationLayerRendered(enable) { eventEnablement.annotationLayerRendered = enable === true; }
|
|
1278
|
-
function enableBookmarkClick(enable) { eventEnablement.bookmarkClick = enable === true; }
|
|
1279
|
-
function enableIdle(enable) { eventEnablement.idle = enable === true; }
|
|
1280
|
-
// #endregion
|
|
1281
|
-
|
|
1282
|
-
// #region Theme & Visual Customization Functions
|
|
1283
|
-
|
|
1284
|
-
// Theme management variables
|
|
1285
|
-
let currentTheme = 'light';
|
|
1286
|
-
|
|
1287
|
-
function setTheme(theme) {
|
|
1288
|
-
currentTheme = theme || 'light';
|
|
1289
|
-
|
|
1290
|
-
// Apply theme-specific CSS classes (CSP-safe)
|
|
1291
|
-
const body = document.body;
|
|
1292
|
-
if (body) {
|
|
1293
|
-
// Remove ALL theme classes including active states
|
|
1294
|
-
body.classList.remove('ng2-theme-light', 'ng2-theme-dark', 'ng2-theme-auto',
|
|
1295
|
-
'ng2-theme-dark-active', 'ng2-theme-light-active');
|
|
1296
|
-
|
|
1297
|
-
// Add new theme class
|
|
1298
|
-
body.classList.add(`ng2-theme-${currentTheme}`);
|
|
1299
|
-
|
|
1300
|
-
// Apply auto theme detection
|
|
1301
|
-
if (currentTheme === 'auto') {
|
|
1302
|
-
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
1303
|
-
const activeClass = prefersDark ? 'ng2-theme-dark-active' : 'ng2-theme-light-active';
|
|
1304
|
-
body.classList.add(activeClass);
|
|
1305
|
-
|
|
1306
|
-
// Listen for theme changes
|
|
1307
|
-
if (!window.ng2ThemeMediaListener) {
|
|
1308
|
-
const mediaListener = window.matchMedia('(prefers-color-scheme: dark)');
|
|
1309
|
-
mediaListener.addListener((e) => {
|
|
1310
|
-
if (currentTheme === 'auto') {
|
|
1311
|
-
body.classList.remove('ng2-theme-dark-active', 'ng2-theme-light-active');
|
|
1312
|
-
const newActiveClass = e.matches ? 'ng2-theme-dark-active' : 'ng2-theme-light-active';
|
|
1313
|
-
body.classList.add(newActiveClass);
|
|
1314
|
-
}
|
|
1315
|
-
});
|
|
1316
|
-
window.ng2ThemeMediaListener = mediaListener;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
// Force style recalculation
|
|
1321
|
-
body.offsetHeight; // Trigger reflow
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
// Note: Theme styles now loaded from external ng2-customization.css (CSP-safe)
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
function setPrimaryColor(color) {
|
|
1328
|
-
setCSSVariable('--ng2-primary-color', color);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
function setBackgroundColor(color) {
|
|
1332
|
-
setCSSVariable('--ng2-background-color', color);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
function setPageSpacing(margin, spreadMargin, border) {
|
|
1336
|
-
if (margin !== undefined) {
|
|
1337
|
-
setCSSVariable('--page-margin', margin);
|
|
1338
|
-
}
|
|
1339
|
-
if (spreadMargin !== undefined) {
|
|
1340
|
-
setCSSVariable('--spreadHorizontalWrapped-margin-LR', spreadMargin);
|
|
1341
|
-
}
|
|
1342
|
-
if (border !== undefined) {
|
|
1343
|
-
setCSSVariable('--page-border', border);
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
function setPageBorderColor(color) {
|
|
1348
|
-
setCSSVariable('--ng2-page-border-color', color);
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
function setToolbarColor(color) {
|
|
1352
|
-
setCSSVariable('--ng2-toolbar-color', color);
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
function setTextColor(color) {
|
|
1356
|
-
setCSSVariable('--ng2-text-color', color);
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
function setBorderRadius(radius) {
|
|
1360
|
-
setCSSVariable('--ng2-border-radius', radius);
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
function setCustomCSS(css, nonce) {
|
|
1364
|
-
// Remove existing custom CSS
|
|
1365
|
-
const existingStyle = document.getElementById('ng2-custom-css');
|
|
1366
|
-
if (existingStyle) {
|
|
1367
|
-
existingStyle.remove();
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
// Apply new custom CSS with optional nonce for CSP support
|
|
1371
|
-
if (css) {
|
|
1372
|
-
const style = document.createElement('style');
|
|
1373
|
-
style.id = 'ng2-custom-css';
|
|
1374
|
-
style.textContent = css;
|
|
1375
|
-
|
|
1376
|
-
// Add nonce if provided (CSP support)
|
|
1377
|
-
if (nonce) {
|
|
1378
|
-
style.setAttribute('nonce', nonce);
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
document.head.appendChild(style);
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
// Helper function to set CSS custom properties
|
|
1386
|
-
function setCSSVariable(property, value) {
|
|
1387
|
-
if (value) {
|
|
1388
|
-
document.documentElement.style.setProperty(property, value);
|
|
1389
|
-
// Also set on body for better compatibility
|
|
1390
|
-
document.body.style.setProperty(property, value);
|
|
1391
|
-
} else {
|
|
1392
|
-
document.documentElement.style.removeProperty(property);
|
|
1393
|
-
document.body.style.removeProperty(property);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
// Note: Theme styles now loaded from external ng2-customization.css (CSP-safe)
|
|
1398
|
-
|
|
1399
|
-
// #endregion
|
|
1400
|
-
|
|
1401
|
-
// #region Error Event Listeners (Early Setup)
|
|
1402
|
-
let errorListenersSetup = false; // Flag to prevent multiple setups
|
|
1403
|
-
|
|
1404
|
-
function setupErrorEventListeners() {
|
|
1405
|
-
// Prevent multiple setups
|
|
1406
|
-
if (errorListenersSetup) {
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
const app = PDFViewerApplication;
|
|
1411
|
-
if (!app || !app.eventBus) {
|
|
1412
|
-
log('Cannot setup error listeners: EventBus not available', 'warn');
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
// Listen for document errors (including file origin errors)
|
|
1417
|
-
app.eventBus.on('documenterror', (event) => {
|
|
1418
|
-
log(`🔴 DOCUMENT ERROR EVENT FIRED: ${event.message}`, 'error');
|
|
1419
|
-
// Always hide loading spinner on document error
|
|
1420
|
-
sendStateChangeNotification('loading', false, 'system');
|
|
1421
|
-
|
|
1422
|
-
// Send error state notification for custom error display
|
|
1423
|
-
const errorMessage = event.message || 'An error occurred while loading the PDF.';
|
|
1424
|
-
sendStateChangeNotification('error', errorMessage, 'system');
|
|
1425
|
-
|
|
1426
|
-
// Send document error events only if enabled
|
|
1427
|
-
if (eventEnablement.documentError) {
|
|
1428
|
-
log(`Document error event received: ${event.message}`);
|
|
1429
|
-
const errorData = {
|
|
1430
|
-
message: event.message || 'Unknown document error',
|
|
1431
|
-
source: event.source ? 'PDFViewerApplication' : 'unknown',
|
|
1432
|
-
name: event.name || 'DocumentError'
|
|
1433
|
-
};
|
|
1434
|
-
sendEventNotification('documentError', errorData);
|
|
1435
|
-
}
|
|
1436
|
-
});
|
|
1437
|
-
|
|
1438
|
-
// Listen for other potential error events
|
|
1439
|
-
app.eventBus.on('loaderror', (event) => {
|
|
1440
|
-
log(`🔴 LOAD ERROR EVENT FIRED: ${event.message}`, 'error');
|
|
1441
|
-
// Hide loading spinner on load error
|
|
1442
|
-
sendStateChangeNotification('loading', false, 'system');
|
|
1443
|
-
});
|
|
1444
|
-
|
|
1445
|
-
app.eventBus.on('error', (event) => {
|
|
1446
|
-
log(`🔴 GENERIC ERROR EVENT FIRED: ${event.message}`, 'error');
|
|
1447
|
-
// Hide loading spinner on any error
|
|
1448
|
-
sendStateChangeNotification('loading', false, 'system');
|
|
1449
|
-
});
|
|
1450
|
-
|
|
1451
|
-
errorListenersSetup = true;
|
|
1452
|
-
log('Error event listeners set up successfully', 'info');
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
// #region Bidirectional Event Listeners
|
|
1456
|
-
let bidirectionalListenersSetup = false; // Flag to prevent multiple setups
|
|
1457
|
-
|
|
1458
|
-
function setupBidirectionalEventListeners() {
|
|
1459
|
-
// Prevent multiple setups
|
|
1460
|
-
if (bidirectionalListenersSetup) {
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
// Add global error handler for iframe-level errors
|
|
1465
|
-
window.addEventListener('error', (event) => {
|
|
1466
|
-
log(`🔴 GLOBAL ERROR: ${event.message}`, 'error');
|
|
1467
|
-
// Hide loading spinner on any global error
|
|
1468
|
-
sendStateChangeNotification('loading', false, 'system');
|
|
1469
|
-
});
|
|
1470
|
-
|
|
1471
|
-
window.addEventListener('unhandledrejection', (event) => {
|
|
1472
|
-
log(`🔴 UNHANDLED PROMISE REJECTION: ${event.reason}`, 'error');
|
|
1473
|
-
// Hide loading spinner on unhandled promise rejection
|
|
1474
|
-
sendStateChangeNotification('loading', false, 'system');
|
|
1475
|
-
});
|
|
1476
|
-
|
|
1477
|
-
bidirectionalListenersSetup = true;
|
|
1478
|
-
const app = PDFViewerApplication;
|
|
1479
|
-
if (!app || !app.eventBus) {
|
|
1480
|
-
log('Cannot setup bidirectional listeners: EventBus not available', 'warn');
|
|
1481
|
-
return;
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
// Test sending a state change notification immediately
|
|
1486
|
-
sendStateChangeNotification('test', 'initial-setup', 'system');
|
|
1487
|
-
|
|
1488
|
-
// Cursor tool changes are handled via switchcursortool event listener only
|
|
1489
|
-
|
|
1490
|
-
// Note: Scroll and spread mode changes are handled via event listeners
|
|
1491
|
-
// No method interception needed for PDF.js v5.3.93
|
|
1492
|
-
if (app.pdfViewer) {
|
|
1493
|
-
|
|
1494
|
-
// Monitor scroll mode changes - setScrollMode doesn't exist in v5.3.93
|
|
1495
|
-
// The scroll mode changes are handled via event listeners instead
|
|
1496
|
-
|
|
1497
|
-
// Spread mode changes are handled via switchspreadmode event listener only
|
|
1498
|
-
|
|
1499
|
-
// Monitor zoom changes via currentScale property
|
|
1500
|
-
let lastKnownScale = app.pdfViewer.currentScale;
|
|
1501
|
-
const originalSetCurrentScale = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(app.pdfViewer), 'currentScale');
|
|
1502
|
-
if (originalSetCurrentScale && originalSetCurrentScale.set) {
|
|
1503
|
-
Object.defineProperty(app.pdfViewer, 'currentScale', {
|
|
1504
|
-
get: originalSetCurrentScale.get,
|
|
1505
|
-
set: function(scale) {
|
|
1506
|
-
const result = originalSetCurrentScale.set.call(this, scale);
|
|
1507
|
-
|
|
1508
|
-
if (scale !== lastKnownScale) {
|
|
1509
|
-
lastKnownScale = scale;
|
|
1510
|
-
|
|
1511
|
-
// Use enhanced zoom transformation
|
|
1512
|
-
const zoomValue = transformZoomFromViewer(scale, app);
|
|
1513
|
-
|
|
1514
|
-
log(`Zoom changed via currentScale to: ${zoomValue} (scale: ${scale}, currentScaleValue: ${app.pdfViewer.currentScaleValue})`);
|
|
1515
|
-
sendStateChangeNotification('zoom', zoomValue, 'user');
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
return result;
|
|
1519
|
-
},
|
|
1520
|
-
configurable: true,
|
|
1521
|
-
enumerable: true
|
|
1522
|
-
});
|
|
1523
|
-
} else {
|
|
1524
|
-
log('pdfViewer.currentScale setter not found', 'warn');
|
|
1525
|
-
}
|
|
1526
|
-
} else {
|
|
1527
|
-
log('pdfViewer not available', 'warn');
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
// Listen for sidebar/page mode changes
|
|
1531
|
-
if (app.pdfSidebar) {
|
|
1532
|
-
|
|
1533
|
-
// Monitor sidebar view changes
|
|
1534
|
-
const originalSetView = app.pdfSidebar.setView;
|
|
1535
|
-
if (originalSetView) {
|
|
1536
|
-
app.pdfSidebar.setView = function(view) {
|
|
1537
|
-
const result = originalSetView.apply(this, arguments);
|
|
1538
|
-
|
|
1539
|
-
// Map PDF.js sidebar view numbers to our page mode names
|
|
1540
|
-
const pageModeMap = {
|
|
1541
|
-
0: 'none', // SidebarView.NONE
|
|
1542
|
-
1: 'thumbs', // SidebarView.THUMBS
|
|
1543
|
-
2: 'bookmarks', // SidebarView.OUTLINE
|
|
1544
|
-
3: 'attachments', // SidebarView.ATTACHMENTS
|
|
1545
|
-
4: 'layers' // SidebarView.LAYERS
|
|
1546
|
-
};
|
|
1547
|
-
|
|
1548
|
-
const pageModeName = pageModeMap[view] || 'none';
|
|
1549
|
-
log(`Page mode changed to: ${pageModeName} (view: ${view})`);
|
|
1550
|
-
sendStateChangeNotification('pageMode', pageModeName, 'user');
|
|
1551
|
-
|
|
1552
|
-
return result;
|
|
1553
|
-
};
|
|
1554
|
-
} else {
|
|
1555
|
-
}
|
|
1556
|
-
} else {
|
|
1557
|
-
log('pdfSidebar not available', 'warn');
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
// Listen for additional PDF.js events that might indicate property changes
|
|
1561
|
-
if (app.eventBus) {
|
|
1562
|
-
|
|
1563
|
-
// Listen for scale changing events with intelligent user vs programmatic detection
|
|
1564
|
-
app.eventBus.on('scalechanging', (event) => {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
// Skip notification if this is from our own zoom command
|
|
1568
|
-
if (ZoomCommandTracker.isZoomCommandActive()) {
|
|
1569
|
-
log('Skipping zoom notification - programmatic change from our command');
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
// Use event context to determine if this is a user-initiated change
|
|
1574
|
-
let isUserInitiated = false;
|
|
1575
|
-
let zoomValue;
|
|
1576
|
-
|
|
1577
|
-
if (event.presetValue && typeof event.presetValue === 'string') {
|
|
1578
|
-
// User clicked zoom buttons (Page Fit, Page Width, Auto, etc.)
|
|
1579
|
-
|
|
1580
|
-
isUserInitiated = true;
|
|
1581
|
-
zoomValue = event.presetValue;
|
|
1582
|
-
} else if (typeof event.scale === 'number' && !event.presetValue) {
|
|
1583
|
-
// Could be mouse wheel (user) or other programmatic change
|
|
1584
|
-
// For now, assume it's user-initiated (mouse wheel, etc.)
|
|
1585
|
-
// Future enhancement: could add more sophisticated detection
|
|
1586
|
-
log('Assuming user-initiated scale change (mouse wheel, etc.)');
|
|
1587
|
-
isUserInitiated = true;
|
|
1588
|
-
zoomValue = transformZoomFromViewer(event.scale, app);
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
if (isUserInitiated) {
|
|
1592
|
-
|
|
1593
|
-
sendStateChangeNotification('zoom', zoomValue, 'user');
|
|
1594
|
-
} else {
|
|
1595
|
-
log('Skipping zoom notification - not identified as user-initiated');
|
|
1596
|
-
}
|
|
1597
|
-
});
|
|
1598
|
-
|
|
1599
|
-
// Listen for other relevant events
|
|
1600
|
-
app.eventBus.on('switchscrollmode', (event) => {
|
|
1601
|
-
|
|
1602
|
-
const scrollMap = {
|
|
1603
|
-
0: 'vertical', // ScrollMode.VERTICAL
|
|
1604
|
-
1: 'horizontal', // ScrollMode.HORIZONTAL
|
|
1605
|
-
2: 'wrapped', // ScrollMode.WRAPPED
|
|
1606
|
-
3: 'page' // ScrollMode.PAGE
|
|
1607
|
-
};
|
|
1608
|
-
|
|
1609
|
-
const scrollName = scrollMap[event.mode] || 'vertical';
|
|
1610
|
-
log(`Scroll mode changed via event to: ${scrollName}`);
|
|
1611
|
-
sendStateChangeNotification('scroll', scrollName, 'user');
|
|
1612
|
-
});
|
|
1613
|
-
|
|
1614
|
-
app.eventBus.on('switchspreadmode', (event) => {
|
|
1615
|
-
|
|
1616
|
-
const spreadMap = {
|
|
1617
|
-
0: 'none', // SpreadMode.NONE
|
|
1618
|
-
1: 'odd', // SpreadMode.ODD
|
|
1619
|
-
2: 'even' // SpreadMode.EVEN
|
|
1620
|
-
};
|
|
1621
|
-
|
|
1622
|
-
const spreadName = spreadMap[event.mode] || 'none';
|
|
1623
|
-
log(`Spread mode changed via event to: ${spreadName}`);
|
|
1624
|
-
sendStateChangeNotification('spread', spreadName, 'user');
|
|
1625
|
-
});
|
|
1626
|
-
|
|
1627
|
-
app.eventBus.on('switchcursortool', (event) => {
|
|
1628
|
-
|
|
1629
|
-
const cursorMap = {
|
|
1630
|
-
0: 'select', // CursorTool.SELECT
|
|
1631
|
-
1: 'hand', // CursorTool.HAND
|
|
1632
|
-
2: 'zoom' // CursorTool.ZOOM
|
|
1633
|
-
};
|
|
1634
|
-
|
|
1635
|
-
const cursorName = cursorMap[event.tool] || 'select';
|
|
1636
|
-
log(`Cursor changed via event to: ${cursorName}`);
|
|
1637
|
-
sendStateChangeNotification('cursor', cursorName, 'user');
|
|
1638
|
-
});
|
|
1639
|
-
|
|
1640
|
-
app.eventBus.on('sidebarviewchanged', (event) => {
|
|
1641
|
-
|
|
1642
|
-
const pageModeMap = {
|
|
1643
|
-
0: 'none', // SidebarView.NONE
|
|
1644
|
-
1: 'thumbs', // SidebarView.THUMBS
|
|
1645
|
-
2: 'bookmarks', // SidebarView.OUTLINE
|
|
1646
|
-
3: 'attachments', // SidebarView.ATTACHMENTS
|
|
1647
|
-
4: 'layers' // SidebarView.LAYERS
|
|
1648
|
-
};
|
|
1649
|
-
|
|
1650
|
-
const pageModeName = pageModeMap[event.view] || 'none';
|
|
1651
|
-
log(`Page mode changed via event to: ${pageModeName}`);
|
|
1652
|
-
sendStateChangeNotification('pageMode', pageModeName, 'user');
|
|
1653
|
-
});
|
|
1654
|
-
|
|
1655
|
-
// Listen for rotation changes (when user rotates via UI or programmatically)
|
|
1656
|
-
app.eventBus.on('rotationchanging', (event) => {
|
|
1657
|
-
log(`Rotation changed via event to: ${event.pagesRotation}`);
|
|
1658
|
-
sendStateChangeNotification('rotation', event.pagesRotation, 'user');
|
|
1659
|
-
});
|
|
1660
|
-
|
|
1661
|
-
// Note: Error event listeners are set up earlier in setupErrorEventListeners()
|
|
1662
|
-
|
|
1663
|
-
app.eventBus.on('documentinit', (event) => {
|
|
1664
|
-
// Send document init events only if enabled
|
|
1665
|
-
if (eventEnablement.documentInit) {
|
|
1666
|
-
sendEventNotification('documentInit', null);
|
|
1667
|
-
}
|
|
1668
|
-
});
|
|
1669
|
-
|
|
1670
|
-
app.eventBus.on('pagesinit', (event) => {
|
|
1671
|
-
// Send pages init events only if enabled
|
|
1672
|
-
if (eventEnablement.pagesInit) {
|
|
1673
|
-
const pageCount = app.pagesCount || 0;
|
|
1674
|
-
const pagesData = {
|
|
1675
|
-
pagesCount: pageCount
|
|
1676
|
-
};
|
|
1677
|
-
sendEventNotification('pagesInit', pagesData);
|
|
1678
|
-
}
|
|
1679
|
-
});
|
|
1680
|
-
|
|
1681
|
-
app.eventBus.on('presentationmodechanged', (event) => {
|
|
1682
|
-
// Send presentation mode events only if enabled
|
|
1683
|
-
if (eventEnablement.presentationModeChanged) {
|
|
1684
|
-
// Map PDF.js PresentationModeState to boolean
|
|
1685
|
-
// FULLSCREEN = 3, CHANGING = 2, NORMAL = 1, UNKNOWN = 0
|
|
1686
|
-
const isActive = event.state === 3; // PresentationModeState.FULLSCREEN
|
|
1687
|
-
const isChanging = event.state === 2; // PresentationModeState.CHANGING
|
|
1688
|
-
const presentationData = {
|
|
1689
|
-
active: isActive,
|
|
1690
|
-
switchInProgress: isChanging
|
|
1691
|
-
};
|
|
1692
|
-
sendEventNotification('presentationModeChanged', presentationData);
|
|
1693
|
-
}
|
|
1694
|
-
});
|
|
1695
|
-
|
|
1696
|
-
app.eventBus.on('fileinputchange', (event) => {
|
|
1697
|
-
// Send open file events only if enabled
|
|
1698
|
-
if (eventEnablement.openFile) {
|
|
1699
|
-
sendEventNotification('openFile', null);
|
|
1700
|
-
}
|
|
1701
|
-
});
|
|
1702
|
-
|
|
1703
|
-
app.eventBus.on('find', (event) => {
|
|
1704
|
-
// Send find events only if enabled
|
|
1705
|
-
if (eventEnablement.find) {
|
|
1706
|
-
const findData = {
|
|
1707
|
-
query: event.query || '',
|
|
1708
|
-
phraseSearch: false, // Not available in PDF.js v5.3.93
|
|
1709
|
-
caseSensitive: event.caseSensitive || false,
|
|
1710
|
-
entireWord: event.entireWord || false,
|
|
1711
|
-
highlightAll: event.highlightAll || false,
|
|
1712
|
-
findPrevious: event.findPrevious || false
|
|
1713
|
-
};
|
|
1714
|
-
sendEventNotification('find', findData);
|
|
1715
|
-
}
|
|
1716
|
-
});
|
|
1717
|
-
|
|
1718
|
-
app.eventBus.on('updatefindmatchescount', (event) => {
|
|
1719
|
-
// Send find matches count events only if enabled
|
|
1720
|
-
if (eventEnablement.updateFindMatchesCount) {
|
|
1721
|
-
const countData = {
|
|
1722
|
-
current: event.matchesCount?.current || 0,
|
|
1723
|
-
total: event.matchesCount?.total || 0
|
|
1724
|
-
};
|
|
1725
|
-
sendEventNotification('updateFindMatchesCount', countData);
|
|
1726
|
-
}
|
|
1727
|
-
});
|
|
1728
|
-
|
|
1729
|
-
app.eventBus.on('metadataloaded', (event) => {
|
|
1730
|
-
// Send metadata loaded events only if enabled
|
|
1731
|
-
if (eventEnablement.metadataLoaded) {
|
|
1732
|
-
const info = app.documentInfo;
|
|
1733
|
-
const metadataData = {
|
|
1734
|
-
title: info?.Title,
|
|
1735
|
-
author: info?.Author,
|
|
1736
|
-
subject: info?.Subject,
|
|
1737
|
-
keywords: info?.Keywords,
|
|
1738
|
-
creator: info?.Creator,
|
|
1739
|
-
producer: info?.Producer,
|
|
1740
|
-
creationDate: info?.CreationDate,
|
|
1741
|
-
modificationDate: info?.ModDate,
|
|
1742
|
-
pdfFormatVersion: info?.PDFFormatVersion,
|
|
1743
|
-
isLinearized: info?.IsLinearized,
|
|
1744
|
-
isAcroFormPresent: info?.IsAcroFormPresent,
|
|
1745
|
-
isXFAPresent: info?.IsXFAPresent,
|
|
1746
|
-
isCollectionPresent: info?.IsCollectionPresent
|
|
1747
|
-
};
|
|
1748
|
-
sendEventNotification('metadataLoaded', metadataData);
|
|
1749
|
-
}
|
|
1750
|
-
});
|
|
1751
|
-
|
|
1752
|
-
app.eventBus.on('outlineloaded', (event) => {
|
|
1753
|
-
// Send outline loaded events only if enabled
|
|
1754
|
-
if (eventEnablement.outlineLoaded) {
|
|
1755
|
-
const outlineData = {
|
|
1756
|
-
items: [], // Outline items are not available in the event, but consumers can check hasOutline
|
|
1757
|
-
hasOutline: (event.outlineCount || 0) > 0
|
|
1758
|
-
};
|
|
1759
|
-
sendEventNotification('outlineLoaded', outlineData);
|
|
1760
|
-
}
|
|
1761
|
-
});
|
|
1762
|
-
|
|
1763
|
-
app.eventBus.on('pagerendered', (event) => {
|
|
1764
|
-
// Send page rendered events only if enabled
|
|
1765
|
-
if (eventEnablement.pageRendered) {
|
|
1766
|
-
const renderData = {
|
|
1767
|
-
pageNumber: event.pageNumber || 1,
|
|
1768
|
-
// Don't include source as it contains canvas element that can't be cloned
|
|
1769
|
-
timestamp: Date.now()
|
|
1770
|
-
};
|
|
1771
|
-
sendEventNotification('pageRendered', renderData);
|
|
1772
|
-
}
|
|
1773
|
-
});
|
|
1774
|
-
|
|
1775
|
-
// Loading state integration for overlay control
|
|
1776
|
-
// Event-driven only; idempotent notifications, no flags or polling
|
|
1777
|
-
app.eventBus.on('documentinit', () => sendStateChangeNotification('loading', true, 'system'));
|
|
1778
|
-
app.eventBus.on('pagesinit', () => sendStateChangeNotification('loading', true, 'system'));
|
|
1779
|
-
app.eventBus.on('pagerendered', () => sendStateChangeNotification('loading', false, 'system'));
|
|
1780
|
-
app.eventBus.on('pagesloaded', () => sendStateChangeNotification('loading', false, 'system'));
|
|
1781
|
-
|
|
1782
|
-
// New high-value events
|
|
1783
|
-
|
|
1784
|
-
// Annotation Layer Rendered - Native PDF.js event
|
|
1785
|
-
app.eventBus.on('annotationlayerrendered', (event) => {
|
|
1786
|
-
// Send annotation layer rendered events only if enabled
|
|
1787
|
-
if (eventEnablement.annotationLayerRendered) {
|
|
1788
|
-
const renderData = {
|
|
1789
|
-
pageNumber: event.pageNumber || 1,
|
|
1790
|
-
error: event.error || null,
|
|
1791
|
-
timestamp: performance.now()
|
|
1792
|
-
};
|
|
1793
|
-
sendEventNotification('annotationLayerRendered', renderData);
|
|
1794
|
-
}
|
|
1795
|
-
});
|
|
1796
|
-
|
|
1797
|
-
// Reset idle timer when PDF document changes (event-driven approach)
|
|
1798
|
-
// Set up idle timer only if enabled
|
|
1799
|
-
if (eventEnablement.idle) {
|
|
1800
|
-
app.eventBus.on('documentloaded', () => {
|
|
1801
|
-
if (typeof resetIdleTimer === 'function') {
|
|
1802
|
-
resetIdleTimer();
|
|
1803
|
-
log('Idle timer reset on document load');
|
|
1804
|
-
}
|
|
1805
|
-
});
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
// Initialize custom event implementations
|
|
1810
|
-
initializeCustomEvents(app);
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
// Initialize custom event implementations
|
|
1816
|
-
function initializeCustomEvents(app) {
|
|
1817
|
-
setupIdleDetection();
|
|
1818
|
-
interceptOutlineClicks(app);
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
// Idle Detection - Custom activity tracking
|
|
1822
|
-
let idleTimer = null;
|
|
1823
|
-
let idleListenersSetup = false;
|
|
1824
|
-
let idleCleanupListeners = []; // Track listeners for cleanup
|
|
1825
|
-
const IDLE_TIMEOUT = 30000; // 30 seconds default
|
|
1826
|
-
|
|
1827
|
-
function resetIdleTimer() {
|
|
1828
|
-
if (idleTimer) {
|
|
1829
|
-
clearTimeout(idleTimer);
|
|
1830
|
-
idleTimer = null;
|
|
1831
|
-
}
|
|
1832
|
-
// Set up idle timer only if enabled
|
|
1833
|
-
if (eventEnablement.idle) {
|
|
1834
|
-
idleTimer = setTimeout(() => {
|
|
1835
|
-
sendEventNotification('idle', null);
|
|
1836
|
-
}, IDLE_TIMEOUT);
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
function cleanupIdleDetection() {
|
|
1841
|
-
// Clear timer
|
|
1842
|
-
if (idleTimer) {
|
|
1843
|
-
clearTimeout(idleTimer);
|
|
1844
|
-
idleTimer = null;
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
// Remove all tracked event listeners
|
|
1848
|
-
idleCleanupListeners.forEach(({ element, event, handler, options }) => {
|
|
1849
|
-
element.removeEventListener(event, handler, options);
|
|
1850
|
-
});
|
|
1851
|
-
idleCleanupListeners = [];
|
|
1852
|
-
idleListenersSetup = false;
|
|
1853
|
-
|
|
1854
|
-
log('Idle detection cleanup completed');
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
function setupIdleDetection() {
|
|
1858
|
-
// Prevent multiple setups
|
|
1859
|
-
if (idleListenersSetup) {
|
|
1860
|
-
log('Idle detection already setup, skipping');
|
|
1861
|
-
return;
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
idleListenersSetup = true;
|
|
1865
|
-
|
|
1866
|
-
// Helper to add and track event listeners
|
|
1867
|
-
const addTrackedListener = (element, event, handler, options) => {
|
|
1868
|
-
element.addEventListener(event, handler, options);
|
|
1869
|
-
idleCleanupListeners.push({ element, event, handler, options });
|
|
1870
|
-
};
|
|
1871
|
-
|
|
1872
|
-
// Track mouse activity
|
|
1873
|
-
addTrackedListener(document, 'mousemove', resetIdleTimer, { passive: true });
|
|
1874
|
-
addTrackedListener(document, 'mousedown', resetIdleTimer, { passive: true });
|
|
1875
|
-
addTrackedListener(document, 'click', resetIdleTimer, { passive: true });
|
|
1876
|
-
|
|
1877
|
-
// Track keyboard activity
|
|
1878
|
-
addTrackedListener(document, 'keydown', resetIdleTimer, { passive: true });
|
|
1879
|
-
addTrackedListener(document, 'keypress', resetIdleTimer, { passive: true });
|
|
1880
|
-
|
|
1881
|
-
// Track scroll activity
|
|
1882
|
-
addTrackedListener(document, 'scroll', resetIdleTimer, { passive: true });
|
|
1883
|
-
addTrackedListener(document, 'wheel', resetIdleTimer, { passive: true });
|
|
1884
|
-
|
|
1885
|
-
// Track touch activity
|
|
1886
|
-
addTrackedListener(document, 'touchstart', resetIdleTimer, { passive: true });
|
|
1887
|
-
addTrackedListener(document, 'touchmove', resetIdleTimer, { passive: true });
|
|
1888
|
-
|
|
1889
|
-
// Start initial timer
|
|
1890
|
-
resetIdleTimer();
|
|
1891
|
-
|
|
1892
|
-
// Cleanup on document unload
|
|
1893
|
-
const unloadHandler = () => {
|
|
1894
|
-
cleanupIdleDetection();
|
|
1895
|
-
};
|
|
1896
|
-
addTrackedListener(window, 'beforeunload', unloadHandler, { passive: true });
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
// Bookmark Click - Intercept outline item clicks
|
|
1902
|
-
let bookmarkInterceptionSetup = false;
|
|
1903
|
-
|
|
1904
|
-
function interceptOutlineClicks(app) {
|
|
1905
|
-
// Prevent multiple setups
|
|
1906
|
-
if (bookmarkInterceptionSetup) {
|
|
1907
|
-
log('Bookmark click interception already setup, skipping');
|
|
1908
|
-
return;
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
// Check if PDF outline viewer is available immediately
|
|
1912
|
-
if (app && app.pdfOutlineViewer && app.pdfOutlineViewer._bindLink) {
|
|
1913
|
-
const originalBindLink = app.pdfOutlineViewer._bindLink;
|
|
1914
|
-
|
|
1915
|
-
app.pdfOutlineViewer._bindLink = function(element, params) {
|
|
1916
|
-
// Call original bind method first
|
|
1917
|
-
originalBindLink.call(this, element, params);
|
|
1918
|
-
|
|
1919
|
-
// Intercept bookmark clicks only if enabled
|
|
1920
|
-
if (eventEnablement.bookmarkClick) {
|
|
1921
|
-
const originalOnClick = element.onclick;
|
|
1922
|
-
element.onclick = function(evt) {
|
|
1923
|
-
// Extract bookmark data
|
|
1924
|
-
const bookmarkData = {
|
|
1925
|
-
title: element.textContent?.trim() || 'Unknown',
|
|
1926
|
-
dest: params.dest || null,
|
|
1927
|
-
action: params.action || undefined,
|
|
1928
|
-
url: params.url || undefined,
|
|
1929
|
-
pageNumber: undefined, // Will be resolved by linkService
|
|
1930
|
-
isCurrentItem: element.classList.contains('currentTreeItem')
|
|
1931
|
-
};
|
|
1932
|
-
|
|
1933
|
-
log(`Bookmark clicked: ${bookmarkData.title}`);
|
|
1934
|
-
sendEventNotification('bookmarkClick', bookmarkData);
|
|
1935
|
-
|
|
1936
|
-
// Call original handler to preserve navigation functionality
|
|
1937
|
-
if (originalOnClick) {
|
|
1938
|
-
return originalOnClick.call(this, evt);
|
|
1939
|
-
}
|
|
1940
|
-
return false;
|
|
1941
|
-
};
|
|
1942
|
-
}
|
|
1943
|
-
};
|
|
1944
|
-
|
|
1945
|
-
bookmarkInterceptionSetup = true;
|
|
1946
|
-
|
|
1947
|
-
} else {
|
|
1948
|
-
// If outline viewer is not available yet, defer setup using event-driven approach
|
|
1949
|
-
// Hook into outlineloaded event to retry when outline becomes available
|
|
1950
|
-
if (app && app.eventBus) {
|
|
1951
|
-
const outlineLoadedHandler = () => {
|
|
1952
|
-
if (!bookmarkInterceptionSetup) {
|
|
1953
|
-
interceptOutlineClicks(app); // Retry when outline is loaded
|
|
1954
|
-
}
|
|
1955
|
-
};
|
|
1956
|
-
app.eventBus.on('outlineloaded', outlineLoadedHandler);
|
|
1957
|
-
log('Bookmark click interception deferred until outline loads');
|
|
1958
|
-
} else {
|
|
1959
|
-
log('Unable to setup bookmark click interception - event bus not available', 'warn');
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
// #endregion
|
|
1964
|
-
|
|
1965
|
-
// Start the initialization process
|
|
1966
|
-
waitForViewer();
|
|
1
|
+
(function() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// This will be wrapped in diagnostic logs check after diagnosticLogs is set
|
|
5
|
+
|
|
6
|
+
// #region URL Security Validation
|
|
7
|
+
let originalFileUrl = null;
|
|
8
|
+
let urlValidationEnabled = true;
|
|
9
|
+
|
|
10
|
+
function initializeUrlValidation() {
|
|
11
|
+
// Get the initial file URL from query parameters
|
|
12
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
13
|
+
const currentFileUrl = urlParams.get('file');
|
|
14
|
+
|
|
15
|
+
// Check if we already have a stored original URL
|
|
16
|
+
const storedOriginalUrl = sessionStorage.getItem('ng2-pdfjs-viewer-original-file-url');
|
|
17
|
+
|
|
18
|
+
if (storedOriginalUrl) {
|
|
19
|
+
// Use the stored original URL (from previous page load)
|
|
20
|
+
originalFileUrl = storedOriginalUrl;
|
|
21
|
+
} else if (currentFileUrl) {
|
|
22
|
+
// Store the current URL as the original (first time loading)
|
|
23
|
+
originalFileUrl = currentFileUrl;
|
|
24
|
+
sessionStorage.setItem('ng2-pdfjs-viewer-original-file-url', originalFileUrl);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateCurrentUrl() {
|
|
29
|
+
if (!urlValidationEnabled || !originalFileUrl) {
|
|
30
|
+
return true; // Skip validation if disabled or no original URL
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const currentFileParam = new URLSearchParams(window.location.search).get('file');
|
|
34
|
+
|
|
35
|
+
// Compare the file parameter specifically
|
|
36
|
+
if (currentFileParam !== originalFileUrl) {
|
|
37
|
+
console.error('🚨 SECURITY ALERT: File URL has been modified!');
|
|
38
|
+
console.error('Original:', originalFileUrl);
|
|
39
|
+
console.error('Current:', currentFileParam);
|
|
40
|
+
|
|
41
|
+
// Prevent loading and show error
|
|
42
|
+
showSecurityError('Unauthorized file access detected. The file URL has been tampered with.');
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function showSecurityError(message) {
|
|
50
|
+
// Simple console warning (less intrusive)
|
|
51
|
+
console.warn('🚨 Security Warning:', message);
|
|
52
|
+
|
|
53
|
+
// Send notification to parent window for Angular template handling
|
|
54
|
+
if (window.parent && window.parent !== window) {
|
|
55
|
+
window.parent.postMessage({
|
|
56
|
+
type: 'ng2-pdfjs-viewer-security-warning',
|
|
57
|
+
message: message,
|
|
58
|
+
originalUrl: originalFileUrl,
|
|
59
|
+
currentUrl: new URLSearchParams(window.location.search).get('file')
|
|
60
|
+
}, '*');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Initialize URL validation on page load
|
|
65
|
+
initializeUrlValidation();
|
|
66
|
+
|
|
67
|
+
// Add validation check before PDF.js loads
|
|
68
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
69
|
+
if (!validateCurrentUrl()) {
|
|
70
|
+
// Prevent PDF.js from loading if URL validation fails
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Validate on page load
|
|
76
|
+
window.addEventListener('load', () => {
|
|
77
|
+
validateCurrentUrl();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Also validate on hash change (in case user tries to modify URL)
|
|
81
|
+
window.addEventListener('hashchange', () => {
|
|
82
|
+
validateCurrentUrl();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Validate on popstate (back/forward navigation)
|
|
86
|
+
window.addEventListener('popstate', () => {
|
|
87
|
+
validateCurrentUrl();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Run validation immediately (in case page is already loaded)
|
|
91
|
+
validateCurrentUrl();
|
|
92
|
+
// #endregion
|
|
93
|
+
|
|
94
|
+
// Override the webviewerloaded event to dispatch to parent window
|
|
95
|
+
// This allows us to capture the event outside the iframe and set locale before PDF.js initializes
|
|
96
|
+
const originalDispatchEvent = document.dispatchEvent;
|
|
97
|
+
document.dispatchEvent = function(event) {
|
|
98
|
+
if (event.type === 'webviewerloaded') {
|
|
99
|
+
// Dispatch to parent window so it can be captured outside the iframe
|
|
100
|
+
if (window.parent && window.parent !== window) {
|
|
101
|
+
window.parent.document.dispatchEvent(event);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return originalDispatchEvent.call(this, event);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// #region Constants and Configuration
|
|
108
|
+
// Command tracking for zoom to prevent infinite loops
|
|
109
|
+
const ZoomCommandTracker = {
|
|
110
|
+
activeZoomCommand: false,
|
|
111
|
+
|
|
112
|
+
markZoomCommandStart() {
|
|
113
|
+
this.activeZoomCommand = true;
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
markZoomCommandEnd() {
|
|
117
|
+
this.activeZoomCommand = false;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
isZoomCommandActive() {
|
|
121
|
+
return this.activeZoomCommand;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Readiness state management
|
|
126
|
+
const ViewerReadiness = {
|
|
127
|
+
NOT_LOADED: 0,
|
|
128
|
+
VIEWER_LOADED: 1, // PDFViewerApplication exists
|
|
129
|
+
VIEWER_INITIALIZED: 2, // PDFViewerApplication.initialized = true
|
|
130
|
+
EVENTBUS_READY: 3, // Event bus is available and ready
|
|
131
|
+
COMPONENTS_READY: 4, // All required components available
|
|
132
|
+
DOCUMENT_LOADED: 5 // PDF document fully loaded
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Pure event-driven architecture - no action categorization needed
|
|
136
|
+
// All actions are dispatched at required readiness levels by Universal Dispatcher
|
|
137
|
+
// #endregion
|
|
138
|
+
|
|
139
|
+
// #region State Management
|
|
140
|
+
let currentReadiness = ViewerReadiness.NOT_LOADED;
|
|
141
|
+
let readinessCallbacks = [];
|
|
142
|
+
let diagnosticLogs = false; // Default to false for production
|
|
143
|
+
// #endregion
|
|
144
|
+
|
|
145
|
+
// #region Utility Functions
|
|
146
|
+
function log(message, level = 'info') {
|
|
147
|
+
if (!diagnosticLogs) return; // Only log when diagnostic logs are enabled
|
|
148
|
+
|
|
149
|
+
const timestamp = new Date().toISOString();
|
|
150
|
+
const prefix = '[PostMessage]';
|
|
151
|
+
|
|
152
|
+
switch (level) {
|
|
153
|
+
case 'error':
|
|
154
|
+
console.error(`${prefix} ${timestamp} ERROR: ${message}`);
|
|
155
|
+
break;
|
|
156
|
+
case 'warn':
|
|
157
|
+
console.warn(`${prefix} ${timestamp} WARN: ${message}`);
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
console.log(`${prefix} ${timestamp} INFO: ${message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Set diagnostic logs mode
|
|
165
|
+
function setDiagnosticLogs(enabled) {
|
|
166
|
+
diagnosticLogs = enabled;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Button visibility control helper (consolidated from separate module)
|
|
170
|
+
function toggleButtonVisibility(primaryId, secondaryId, visible) {
|
|
171
|
+
const primary = document.getElementById(primaryId);
|
|
172
|
+
const secondary = secondaryId ? document.getElementById(secondaryId) : null;
|
|
173
|
+
if (primary) primary.classList.toggle('hidden', !visible);
|
|
174
|
+
if (secondary) secondary.classList.toggle('hidden', !visible);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Generic element visibility control for toolbar/sidebar groups
|
|
178
|
+
function toggleElementVisibilityById(elementId, visible) {
|
|
179
|
+
const el = document.getElementById(elementId);
|
|
180
|
+
if (!el) {
|
|
181
|
+
log(`Element not found for visibility toggle: ${elementId}`, 'warn');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
el.classList.toggle('hidden', !visible);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Toolbar section visibility control that preserves layout
|
|
188
|
+
function toggleToolbarSectionVisibility(elementId, visible) {
|
|
189
|
+
const el = document.getElementById(elementId);
|
|
190
|
+
if (!el) {
|
|
191
|
+
log(`Toolbar section not found for visibility toggle: ${elementId}`, 'warn');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Use CSS class instead of inline styles (CSP-safe)
|
|
196
|
+
el.classList.toggle('ng2-hidden-section', !visible);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Readiness state management
|
|
200
|
+
function setReadiness(readiness) {
|
|
201
|
+
if (readiness > currentReadiness) {
|
|
202
|
+
log(`Readiness state changed: ${currentReadiness} → ${readiness}`);
|
|
203
|
+
currentReadiness = readiness;
|
|
204
|
+
|
|
205
|
+
// Execute callbacks for this readiness level
|
|
206
|
+
readinessCallbacks.forEach(callback => {
|
|
207
|
+
if (callback.readiness <= readiness && !callback.executed) {
|
|
208
|
+
callback.executed = true;
|
|
209
|
+
callback.callback();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function onReadiness(readiness, callback) {
|
|
216
|
+
if (currentReadiness >= readiness) {
|
|
217
|
+
callback();
|
|
218
|
+
} else {
|
|
219
|
+
readinessCallbacks.push({ readiness, callback, executed: false });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Note: Density styles now loaded from external ng2-customization.css (CSP-safe)
|
|
224
|
+
|
|
225
|
+
// Enhanced zoom transformation function
|
|
226
|
+
function transformZoomFromViewer(scale, app) {
|
|
227
|
+
if (typeof scale === 'string') {
|
|
228
|
+
return scale;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (app?.pdfViewer) {
|
|
232
|
+
const viewer = app.pdfViewer;
|
|
233
|
+
|
|
234
|
+
// Check for special zoom modes
|
|
235
|
+
const specialModes = ['page-fit', 'page-width', 'page-actual', 'auto'];
|
|
236
|
+
if (specialModes.includes(viewer.currentScaleValue)) {
|
|
237
|
+
return viewer.currentScaleValue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check predefined zoom levels
|
|
241
|
+
const predefinedZooms = {
|
|
242
|
+
0.5: '0.5', 0.75: '0.75', 1: '1', 1.25: '1.25',
|
|
243
|
+
1.5: '1.5', 2: '2', 3: '3', 4: '4'
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
for (const [zoomLevel, zoomValue] of Object.entries(predefinedZooms)) {
|
|
247
|
+
if (Math.abs(scale - parseFloat(zoomLevel)) < 0.01) {
|
|
248
|
+
return zoomValue;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return typeof scale === 'number' ? `${Math.round(scale * 100)}%` : 'auto';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Send state change notifications to Angular component
|
|
257
|
+
function sendStateChangeNotification(property, value, source = 'user') {
|
|
258
|
+
try {
|
|
259
|
+
const message = {
|
|
260
|
+
type: 'state-change',
|
|
261
|
+
property: property,
|
|
262
|
+
value: value,
|
|
263
|
+
source: source,
|
|
264
|
+
timestamp: Date.now()
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
window.parent.postMessage(message, '*');
|
|
268
|
+
} catch (error) {
|
|
269
|
+
log(`Failed to send state change notification: ${error.message}`, 'error');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Send event notifications to Angular component
|
|
274
|
+
function sendEventNotification(eventName, eventData) {
|
|
275
|
+
try {
|
|
276
|
+
const message = {
|
|
277
|
+
type: 'event-notification',
|
|
278
|
+
eventName: eventName,
|
|
279
|
+
eventData: eventData,
|
|
280
|
+
timestamp: Date.now()
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
window.parent.postMessage(message, '*');
|
|
284
|
+
} catch (error) {
|
|
285
|
+
log(`Failed to send event notification: ${error.message}`, 'error');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function sendResponse(id, response) {
|
|
290
|
+
if (id && window.parent) {
|
|
291
|
+
window.parent.postMessage({
|
|
292
|
+
type: 'control-response',
|
|
293
|
+
id: id,
|
|
294
|
+
...response,
|
|
295
|
+
timestamp: Date.now()
|
|
296
|
+
}, '*');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function getState() {
|
|
301
|
+
const app = PDFViewerApplication;
|
|
302
|
+
if (!app) {
|
|
303
|
+
return { ready: false };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
ready: app.initialized,
|
|
308
|
+
page: app.page,
|
|
309
|
+
pagesCount: app.pagesCount,
|
|
310
|
+
currentScale: app.pdfViewer ? app.pdfViewer.currentScale : null,
|
|
311
|
+
currentScaleValue: app.pdfViewer ? app.pdfViewer.currentScaleValue : null,
|
|
312
|
+
pagesRotation: app.pdfViewer ? app.pdfViewer.pagesRotation : null,
|
|
313
|
+
scrollMode: app.pdfViewer ? app.pdfViewer.scrollMode : null,
|
|
314
|
+
spreadMode: app.pdfViewer ? app.pdfViewer.spreadMode : null
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Common control update utilities
|
|
319
|
+
const ControlUtils = {
|
|
320
|
+
// Common button visibility update pattern
|
|
321
|
+
updateButtonVisibility: function(buttonId, secondaryId, visible) {
|
|
322
|
+
const button = document.getElementById(buttonId);
|
|
323
|
+
const secondaryButton = secondaryId ? document.getElementById(secondaryId) : null;
|
|
324
|
+
|
|
325
|
+
if (button) {
|
|
326
|
+
button.classList.toggle('hidden', !visible);
|
|
327
|
+
}
|
|
328
|
+
if (secondaryButton) {
|
|
329
|
+
secondaryButton.classList.toggle('hidden', !visible);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
// Common mode update pattern with event bus
|
|
335
|
+
updateModeViaEventBus: function(eventName, modeValue, modeMap, propertyName) {
|
|
336
|
+
const app = PDFViewerApplication;
|
|
337
|
+
if (!app?.eventBus) {
|
|
338
|
+
log('EventBus not available for mode update', 'error');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const upperMode = typeof modeValue === 'string' ? modeValue.toUpperCase() : modeValue;
|
|
344
|
+
const mode = modeMap[upperMode];
|
|
345
|
+
|
|
346
|
+
if (mode !== undefined) {
|
|
347
|
+
app.eventBus.dispatch(eventName, { mode });
|
|
348
|
+
|
|
349
|
+
// NOTE: State change notifications are sent by the bidirectional event listeners
|
|
350
|
+
// when the actual mode change occurs in PDF.js, not immediately when we dispatch
|
|
351
|
+
// This prevents infinite loops and ensures we only notify on actual changes
|
|
352
|
+
} else {
|
|
353
|
+
log(`Unknown ${propertyName} mode: ${modeValue}`, 'warn');
|
|
354
|
+
}
|
|
355
|
+
} catch (error) {
|
|
356
|
+
log(`Error updating ${propertyName} mode: ${error.message}`, 'error');
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
// Common direct property update pattern
|
|
361
|
+
updatePropertyDirectly: function(propertyPath, value, propertyName) {
|
|
362
|
+
const app = PDFViewerApplication;
|
|
363
|
+
if (!app?.pdfViewer) {
|
|
364
|
+
log(`PDFViewer not available for ${propertyName} update`, 'warn');
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
// Navigate to nested property
|
|
370
|
+
const pathParts = propertyPath.split('.');
|
|
371
|
+
let target = app;
|
|
372
|
+
|
|
373
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
374
|
+
target = target[pathParts[i]];
|
|
375
|
+
if (!target) {
|
|
376
|
+
log(`Property path ${propertyPath} not found for ${propertyName}`, 'warn');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
target[pathParts[pathParts.length - 1]] = value;
|
|
382
|
+
} catch (error) {
|
|
383
|
+
log(`Direct ${propertyName} update failed: ${error.message}`, 'warn');
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
// Common validation pattern
|
|
388
|
+
validateAndExecute: function(condition, action, errorMessage) {
|
|
389
|
+
if (condition) {
|
|
390
|
+
action();
|
|
391
|
+
} else {
|
|
392
|
+
log(errorMessage, 'warn');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
// #endregion
|
|
397
|
+
|
|
398
|
+
// #region Initialization and Setup
|
|
399
|
+
|
|
400
|
+
// Enhanced readiness checking with proper async initialization handling
|
|
401
|
+
function checkViewerReadiness() {
|
|
402
|
+
const app = PDFViewerApplication;
|
|
403
|
+
|
|
404
|
+
if (!app) {
|
|
405
|
+
setReadiness(ViewerReadiness.NOT_LOADED);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
setReadiness(ViewerReadiness.VIEWER_LOADED);
|
|
410
|
+
|
|
411
|
+
// Handle async initialization properly
|
|
412
|
+
if (app.initializedPromise) {
|
|
413
|
+
app.initializedPromise.then(() => {
|
|
414
|
+
setReadiness(ViewerReadiness.VIEWER_INITIALIZED);
|
|
415
|
+
|
|
416
|
+
// Check if event bus is ready
|
|
417
|
+
if (app.eventBus && typeof app.eventBus.dispatch === 'function') {
|
|
418
|
+
setReadiness(ViewerReadiness.EVENTBUS_READY);
|
|
419
|
+
|
|
420
|
+
// Check if key components are available
|
|
421
|
+
if (app.pdfViewer && app.pdfCursorTools) {
|
|
422
|
+
setReadiness(ViewerReadiness.COMPONENTS_READY);
|
|
423
|
+
|
|
424
|
+
// CSS zoom setting is now handled via PostMessage configuration
|
|
425
|
+
// No need to check global window variables
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}).catch(error => {
|
|
429
|
+
log(`PDFViewerApplication initialization failed: ${error.message}`, 'error');
|
|
430
|
+
});
|
|
431
|
+
} else if (app.initialized) {
|
|
432
|
+
// Fallback for synchronous check (though this should be async)
|
|
433
|
+
setReadiness(ViewerReadiness.VIEWER_INITIALIZED);
|
|
434
|
+
|
|
435
|
+
// Check if event bus is ready
|
|
436
|
+
if (app.eventBus && typeof app.eventBus.dispatch === 'function') {
|
|
437
|
+
setReadiness(ViewerReadiness.EVENTBUS_READY);
|
|
438
|
+
|
|
439
|
+
// Check if key components are available
|
|
440
|
+
if (app.pdfViewer && app.pdfCursorTools) {
|
|
441
|
+
setReadiness(ViewerReadiness.COMPONENTS_READY);
|
|
442
|
+
|
|
443
|
+
// CSS zoom setting is now handled via PostMessage configuration
|
|
444
|
+
// No need to check global window variables
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Wait for PDF.js viewer to be ready with event-driven approach
|
|
451
|
+
function waitForViewer() {
|
|
452
|
+
// Use MutationObserver to detect when PDFViewerApplication becomes available
|
|
453
|
+
const observer = new MutationObserver((mutations, obs) => {
|
|
454
|
+
if (typeof PDFViewerApplication !== 'undefined') {
|
|
455
|
+
obs.disconnect(); // Stop observing
|
|
456
|
+
checkViewerReadiness();
|
|
457
|
+
|
|
458
|
+
// Set up event-driven readiness monitoring
|
|
459
|
+
setupEventDrivenReadinessMonitoring();
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Start observing DOM changes
|
|
464
|
+
observer.observe(document, {
|
|
465
|
+
childList: true,
|
|
466
|
+
subtree: true
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Also check immediately in case it's already available
|
|
470
|
+
if (typeof PDFViewerApplication !== 'undefined') {
|
|
471
|
+
log('PDFViewerApplication already available, starting readiness check');
|
|
472
|
+
observer.disconnect();
|
|
473
|
+
checkViewerReadiness();
|
|
474
|
+
setupEventDrivenReadinessMonitoring();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Set up event-driven readiness monitoring
|
|
479
|
+
function setupEventDrivenReadinessMonitoring() {
|
|
480
|
+
// Use a custom event system for readiness changes
|
|
481
|
+
const readinessEventTarget = new EventTarget();
|
|
482
|
+
|
|
483
|
+
// Override setReadiness to emit events
|
|
484
|
+
const originalSetReadiness = setReadiness;
|
|
485
|
+
setReadiness = function(readiness) {
|
|
486
|
+
originalSetReadiness(readiness);
|
|
487
|
+
|
|
488
|
+
// Emit custom event for readiness change
|
|
489
|
+
readinessEventTarget.dispatchEvent(new CustomEvent('readiness-change', {
|
|
490
|
+
detail: { readiness, previousReadiness: currentReadiness }
|
|
491
|
+
}));
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// Listen for readiness changes
|
|
495
|
+
readinessEventTarget.addEventListener('readiness-change', (event) => {
|
|
496
|
+
const { readiness } = event.detail;
|
|
497
|
+
|
|
498
|
+
if (readiness >= ViewerReadiness.EVENTBUS_READY) {
|
|
499
|
+
|
|
500
|
+
initializePostMessageAPI();
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Check initial readiness
|
|
505
|
+
if (currentReadiness >= ViewerReadiness.EVENTBUS_READY) {
|
|
506
|
+
|
|
507
|
+
initializePostMessageAPI();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function initializePostMessageAPI() {
|
|
512
|
+
// Add message listener
|
|
513
|
+
window.addEventListener('message', handleControlMessage);
|
|
514
|
+
|
|
515
|
+
// Expose API for external access
|
|
516
|
+
window.Ng2PdfJsViewerAPI = {
|
|
517
|
+
updateControl: updateControl,
|
|
518
|
+
getState: getState,
|
|
519
|
+
isReady: () => currentReadiness >= ViewerReadiness.EVENTBUS_READY,
|
|
520
|
+
getReadiness: () => currentReadiness
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// Notify parent that PostMessage API is ready
|
|
524
|
+
window.parent.postMessage({
|
|
525
|
+
type: 'postmessage-ready',
|
|
526
|
+
timestamp: Date.now(),
|
|
527
|
+
readiness: currentReadiness
|
|
528
|
+
}, '*');
|
|
529
|
+
|
|
530
|
+
// Set up event listeners for readiness changes
|
|
531
|
+
setupReadinessEventListeners();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function setupReadinessEventListeners() {
|
|
535
|
+
const app = PDFViewerApplication;
|
|
536
|
+
if (!app || !app.eventBus) return;
|
|
537
|
+
|
|
538
|
+
// Set up error event listeners immediately (before document loads)
|
|
539
|
+
setupErrorEventListeners();
|
|
540
|
+
|
|
541
|
+
// Listen for document loaded event
|
|
542
|
+
app.eventBus.on('documentloaded', () => {
|
|
543
|
+
setReadiness(ViewerReadiness.DOCUMENT_LOADED);
|
|
544
|
+
|
|
545
|
+
// Set up bidirectional event listeners once document is loaded
|
|
546
|
+
setupBidirectionalEventListeners();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Listen for pages loaded event
|
|
550
|
+
app.eventBus.on('pagesloaded', () => {
|
|
551
|
+
// Ensure we're at least at components ready level
|
|
552
|
+
if (currentReadiness < ViewerReadiness.COMPONENTS_READY) {
|
|
553
|
+
setReadiness(ViewerReadiness.COMPONENTS_READY);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
// #endregion
|
|
558
|
+
|
|
559
|
+
// #region Message Handling
|
|
560
|
+
function handleControlMessage(event) {
|
|
561
|
+
const { type, action, payload, id } = event.data;
|
|
562
|
+
|
|
563
|
+
if (type === 'control-update') {
|
|
564
|
+
try {
|
|
565
|
+
|
|
566
|
+
// Universal Dispatcher has already verified readiness - just execute
|
|
567
|
+
updateControl(action, payload);
|
|
568
|
+
sendResponse(id, { success: true, action, payload });
|
|
569
|
+
} catch (error) {
|
|
570
|
+
const errorMsg = `Error processing ${action}: ${error.message}`;
|
|
571
|
+
log(errorMsg, 'error');
|
|
572
|
+
sendResponse(id, { success: false, error: errorMsg });
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// #endregion
|
|
577
|
+
|
|
578
|
+
// #region Control Update Functions
|
|
579
|
+
function updateControl(action, payload) {
|
|
580
|
+
const app = PDFViewerApplication;
|
|
581
|
+
|
|
582
|
+
// Validate that PDFViewerApplication is available
|
|
583
|
+
if (!app) {
|
|
584
|
+
log('PDFViewerApplication not available', 'error');
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
// Note: Density styles loaded from external ng2-customization.css (CSP-safe)
|
|
590
|
+
switch (action) {
|
|
591
|
+
// Button visibility controls (using consolidated helper function)
|
|
592
|
+
case 'show-download':
|
|
593
|
+
toggleButtonVisibility('downloadButton', 'secondaryDownload', payload);
|
|
594
|
+
break;
|
|
595
|
+
case 'show-print':
|
|
596
|
+
toggleButtonVisibility('printButton', 'secondaryPrint', payload);
|
|
597
|
+
break;
|
|
598
|
+
case 'show-fullscreen':
|
|
599
|
+
toggleButtonVisibility('presentationMode', null, payload);
|
|
600
|
+
break;
|
|
601
|
+
case 'show-find':
|
|
602
|
+
toggleButtonVisibility('viewFindButton', null, payload);
|
|
603
|
+
break;
|
|
604
|
+
case 'show-bookmark':
|
|
605
|
+
toggleButtonVisibility('viewBookmark', null, payload);
|
|
606
|
+
break;
|
|
607
|
+
case 'show-openfile':
|
|
608
|
+
toggleButtonVisibility('openFile', 'secondaryOpenFile', payload);
|
|
609
|
+
break;
|
|
610
|
+
case 'show-annotations':
|
|
611
|
+
toggleElementVisibilityById('editorModeButtons', payload);
|
|
612
|
+
break;
|
|
613
|
+
|
|
614
|
+
case 'set-toolbar-density': {
|
|
615
|
+
// Apply compact/comfortable density by toggling a class on #toolbarContainer
|
|
616
|
+
const container = document.getElementById('toolbarContainer');
|
|
617
|
+
if (container) {
|
|
618
|
+
container.classList.remove('density-default','density-compact','density-comfortable');
|
|
619
|
+
const density = typeof payload === 'string' ? payload : 'default';
|
|
620
|
+
container.classList.add(`density-${density}`);
|
|
621
|
+
}
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
case 'set-sidebar-width': {
|
|
625
|
+
// Use CSS variable only (CSP-safe) - width applied via ng2-customization.css
|
|
626
|
+
const outer = document.getElementById('outerContainer');
|
|
627
|
+
if (outer && typeof payload === 'string' && payload.trim() !== '') {
|
|
628
|
+
outer.style.setProperty('--sidebar-width', payload);
|
|
629
|
+
}
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
case 'set-toolbar-position': {
|
|
633
|
+
const outer = document.getElementById('outerContainer');
|
|
634
|
+
if (outer) {
|
|
635
|
+
outer.classList.remove('toolbar-bottom');
|
|
636
|
+
if (payload === 'bottom') outer.classList.add('toolbar-bottom');
|
|
637
|
+
}
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
case 'set-sidebar-position': {
|
|
641
|
+
const outer = document.getElementById('outerContainer');
|
|
642
|
+
if (outer) {
|
|
643
|
+
outer.classList.remove('sidebar-right');
|
|
644
|
+
if (payload === 'right') outer.classList.add('sidebar-right');
|
|
645
|
+
}
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
case 'set-responsive-breakpoint': {
|
|
649
|
+
const value = typeof payload === 'number' ? `${payload}px` : `${payload}`;
|
|
650
|
+
const outer = document.getElementById('outerContainer');
|
|
651
|
+
if (outer && value) {
|
|
652
|
+
outer.style.setProperty('--ng2-responsive-breakpoint', value);
|
|
653
|
+
}
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
case 'show-toolbar-left':
|
|
658
|
+
toggleToolbarSectionVisibility('toolbarViewerLeft', payload);
|
|
659
|
+
break;
|
|
660
|
+
case 'show-toolbar-middle':
|
|
661
|
+
toggleToolbarSectionVisibility('toolbarViewerMiddle', payload);
|
|
662
|
+
break;
|
|
663
|
+
case 'show-toolbar-right':
|
|
664
|
+
toggleToolbarSectionVisibility('toolbarViewerRight', payload);
|
|
665
|
+
break;
|
|
666
|
+
case 'show-secondary-toolbar-toggle':
|
|
667
|
+
toggleElementVisibilityById('secondaryToolbarToggle', payload);
|
|
668
|
+
break;
|
|
669
|
+
case 'show-sidebar':
|
|
670
|
+
toggleElementVisibilityById('sidebarContainer', payload);
|
|
671
|
+
break;
|
|
672
|
+
case 'show-sidebar-left':
|
|
673
|
+
toggleElementVisibilityById('toolbarSidebarLeft', payload);
|
|
674
|
+
break;
|
|
675
|
+
case 'show-sidebar-right':
|
|
676
|
+
toggleElementVisibilityById('toolbarSidebarRight', payload);
|
|
677
|
+
break;
|
|
678
|
+
|
|
679
|
+
// Mode controls
|
|
680
|
+
case 'set-zoom':
|
|
681
|
+
updateZoom(payload);
|
|
682
|
+
break;
|
|
683
|
+
case 'set-cursor':
|
|
684
|
+
updateCursor(payload);
|
|
685
|
+
break;
|
|
686
|
+
case 'set-scroll':
|
|
687
|
+
ControlUtils.updateModeViaEventBus('switchscrollmode', payload,
|
|
688
|
+
{ 'VERTICAL': 0, 'V': 0, 'HORIZONTAL': 1, 'H': 1, 'WRAPPED': 2, 'W': 2, 'PAGE': 3, 'P': 3 },
|
|
689
|
+
'scroll mode');
|
|
690
|
+
ControlUtils.updatePropertyDirectly('pdfViewer.scrollMode',
|
|
691
|
+
({ 'VERTICAL': 0, 'V': 0, 'HORIZONTAL': 1, 'H': 1, 'WRAPPED': 2, 'W': 2, 'PAGE': 3, 'P': 3 })[payload?.toUpperCase()] || 0,
|
|
692
|
+
'scroll mode');
|
|
693
|
+
break;
|
|
694
|
+
case 'set-spread':
|
|
695
|
+
ControlUtils.updateModeViaEventBus('switchspreadmode', payload,
|
|
696
|
+
{ 'NONE': 0, 'N': 0, 'ODD': 1, 'O': 1, 'EVEN': 2, 'E': 2 },
|
|
697
|
+
'spread mode');
|
|
698
|
+
ControlUtils.updatePropertyDirectly('pdfViewer.spreadMode',
|
|
699
|
+
({ 'NONE': 0, 'N': 0, 'ODD': 1, 'O': 1, 'EVEN': 2, 'E': 2 })[payload?.toUpperCase()] || 0,
|
|
700
|
+
'spread mode');
|
|
701
|
+
break;
|
|
702
|
+
|
|
703
|
+
// Navigation controls
|
|
704
|
+
case 'set-page':
|
|
705
|
+
// Universal Dispatcher guarantees PDFViewerApplication.initialized at readiness level 5
|
|
706
|
+
const pageNumber = parseInt(payload, 10);
|
|
707
|
+
if (pageNumber > 0 && pageNumber <= PDFViewerApplication.pagesCount) {
|
|
708
|
+
PDFViewerApplication.page = pageNumber;
|
|
709
|
+
} else {
|
|
710
|
+
log(`Invalid page number: ${payload}`, 'warn');
|
|
711
|
+
}
|
|
712
|
+
break;
|
|
713
|
+
case 'set-rotation':
|
|
714
|
+
// Universal Dispatcher guarantees PDFViewerApplication.initialized at readiness level 5
|
|
715
|
+
const rotation = parseInt(payload, 10);
|
|
716
|
+
if ([0, 90, 180, 270].includes(rotation)) {
|
|
717
|
+
// Set rotation on pdfViewer to trigger proper refresh
|
|
718
|
+
PDFViewerApplication.pdfViewer.pagesRotation = rotation;
|
|
719
|
+
// Note: No need to send state change notification here -
|
|
720
|
+
// PDF.js will fire 'rotationchanging' event which we handle separately
|
|
721
|
+
} else {
|
|
722
|
+
log(`Invalid rotation: ${payload}`, 'warn');
|
|
723
|
+
}
|
|
724
|
+
break;
|
|
725
|
+
case 'go-to-last-page':
|
|
726
|
+
if (payload === true) {
|
|
727
|
+
// Universal Dispatcher guarantees PDFViewerApplication.initialized at readiness level 5
|
|
728
|
+
const lastPage = PDFViewerApplication.pagesCount;
|
|
729
|
+
PDFViewerApplication.page = lastPage;
|
|
730
|
+
}
|
|
731
|
+
break;
|
|
732
|
+
case 'go-to-named-dest':
|
|
733
|
+
// Validate that payload is not empty or null before processing
|
|
734
|
+
if (!payload || typeof payload !== 'string' || payload.trim() === '') {
|
|
735
|
+
log(`Skipping invalid named destination: "${payload}"`);
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Universal Dispatcher guarantees PDFViewerApplication.pdfLinkService at readiness level 5
|
|
740
|
+
PDFViewerApplication.pdfLinkService.goToDestination(payload);
|
|
741
|
+
break;
|
|
742
|
+
case 'update-page-mode':
|
|
743
|
+
// Universal Dispatcher guarantees PDFViewerApplication.eventBus at readiness level 4
|
|
744
|
+
const mode = payload ? payload.toLowerCase() : 'none';
|
|
745
|
+
PDFViewerApplication.eventBus.dispatch('pagemode', { mode });
|
|
746
|
+
break;
|
|
747
|
+
|
|
748
|
+
// Configuration actions
|
|
749
|
+
case 'set-download-filename':
|
|
750
|
+
setDownloadFilename(payload);
|
|
751
|
+
break;
|
|
752
|
+
|
|
753
|
+
// Auto actions - Universal Dispatcher guarantees eventBus availability at readiness level 5
|
|
754
|
+
case 'trigger-download':
|
|
755
|
+
if (payload === true) {
|
|
756
|
+
PDFViewerApplication.eventBus.dispatch('download');
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
case 'trigger-print':
|
|
760
|
+
if (payload === true) {
|
|
761
|
+
PDFViewerApplication.eventBus.dispatch('print');
|
|
762
|
+
}
|
|
763
|
+
break;
|
|
764
|
+
case 'trigger-rotate-cw':
|
|
765
|
+
if (payload === true) {
|
|
766
|
+
PDFViewerApplication.eventBus.dispatch('rotatecw');
|
|
767
|
+
}
|
|
768
|
+
break;
|
|
769
|
+
case 'trigger-rotate-ccw':
|
|
770
|
+
if (payload === true) {
|
|
771
|
+
PDFViewerApplication.eventBus.dispatch('rotateccw');
|
|
772
|
+
}
|
|
773
|
+
break;
|
|
774
|
+
|
|
775
|
+
// Error handling
|
|
776
|
+
case 'set-error-message':
|
|
777
|
+
setErrorMessage(payload);
|
|
778
|
+
break;
|
|
779
|
+
case 'set-error-override':
|
|
780
|
+
setErrorOverride(payload);
|
|
781
|
+
break;
|
|
782
|
+
case 'set-error-append':
|
|
783
|
+
setErrorAppend(payload);
|
|
784
|
+
break;
|
|
785
|
+
|
|
786
|
+
// CSS zoom
|
|
787
|
+
case 'set-css-zoom':
|
|
788
|
+
setCssZoom(payload);
|
|
789
|
+
break;
|
|
790
|
+
|
|
791
|
+
// Event enablement
|
|
792
|
+
case 'enable-before-print':
|
|
793
|
+
enableBeforePrint(payload);
|
|
794
|
+
break;
|
|
795
|
+
case 'enable-after-print':
|
|
796
|
+
enableAfterPrint(payload);
|
|
797
|
+
break;
|
|
798
|
+
case 'enable-pages-loaded':
|
|
799
|
+
enablePagesLoaded(payload);
|
|
800
|
+
break;
|
|
801
|
+
case 'enable-page-change':
|
|
802
|
+
enablePageChange(payload);
|
|
803
|
+
break;
|
|
804
|
+
|
|
805
|
+
// New high-value event enablement
|
|
806
|
+
case 'enable-document-error':
|
|
807
|
+
enableDocumentError(payload);
|
|
808
|
+
break;
|
|
809
|
+
case 'enable-document-init':
|
|
810
|
+
enableDocumentInit(payload);
|
|
811
|
+
break;
|
|
812
|
+
case 'enable-pages-init':
|
|
813
|
+
enablePagesInit(payload);
|
|
814
|
+
break;
|
|
815
|
+
case 'enable-presentation-mode-changed':
|
|
816
|
+
enablePresentationModeChanged(payload);
|
|
817
|
+
break;
|
|
818
|
+
case 'enable-open-file':
|
|
819
|
+
enableOpenFile(payload);
|
|
820
|
+
break;
|
|
821
|
+
case 'enable-find':
|
|
822
|
+
enableFind(payload);
|
|
823
|
+
break;
|
|
824
|
+
case 'enable-update-find-matches-count':
|
|
825
|
+
enableUpdateFindMatchesCount(payload);
|
|
826
|
+
break;
|
|
827
|
+
case 'enable-metadata-loaded':
|
|
828
|
+
enableMetadataLoaded(payload);
|
|
829
|
+
break;
|
|
830
|
+
case 'enable-outline-loaded':
|
|
831
|
+
enableOutlineLoaded(payload);
|
|
832
|
+
break;
|
|
833
|
+
case 'enable-page-rendered':
|
|
834
|
+
enablePageRendered(payload);
|
|
835
|
+
break;
|
|
836
|
+
|
|
837
|
+
// New high-value events
|
|
838
|
+
case 'enable-annotation-layer-rendered':
|
|
839
|
+
enableAnnotationLayerRendered(payload);
|
|
840
|
+
break;
|
|
841
|
+
case 'enable-bookmark-click':
|
|
842
|
+
enableBookmarkClick(payload);
|
|
843
|
+
break;
|
|
844
|
+
case 'enable-idle':
|
|
845
|
+
enableIdle(payload);
|
|
846
|
+
break;
|
|
847
|
+
|
|
848
|
+
// Theme & Visual Customization Actions
|
|
849
|
+
case 'set-theme':
|
|
850
|
+
setTheme(payload);
|
|
851
|
+
break;
|
|
852
|
+
case 'set-primary-color':
|
|
853
|
+
setPrimaryColor(payload);
|
|
854
|
+
break;
|
|
855
|
+
case 'set-background-color':
|
|
856
|
+
setBackgroundColor(payload);
|
|
857
|
+
break;
|
|
858
|
+
case 'set-page-border-color':
|
|
859
|
+
setPageBorderColor(payload);
|
|
860
|
+
break;
|
|
861
|
+
case 'set-page-spacing':
|
|
862
|
+
setPageSpacing(payload.margin, payload.spreadMargin, payload.border);
|
|
863
|
+
break;
|
|
864
|
+
case 'set-toolbar-color':
|
|
865
|
+
setToolbarColor(payload);
|
|
866
|
+
break;
|
|
867
|
+
case 'set-text-color':
|
|
868
|
+
setTextColor(payload);
|
|
869
|
+
break;
|
|
870
|
+
case 'set-border-radius':
|
|
871
|
+
setBorderRadius(payload);
|
|
872
|
+
break;
|
|
873
|
+
case 'set-custom-css':
|
|
874
|
+
// Handle both old format (string) and new format (object with nonce)
|
|
875
|
+
if (typeof payload === 'string') {
|
|
876
|
+
setCustomCSS(payload, null);
|
|
877
|
+
} else if (payload && typeof payload === 'object') {
|
|
878
|
+
setCustomCSS(payload.css, payload.nonce);
|
|
879
|
+
}
|
|
880
|
+
break;
|
|
881
|
+
|
|
882
|
+
case 'set-diagnostic-logs':
|
|
883
|
+
setDiagnosticLogs(payload);
|
|
884
|
+
break;
|
|
885
|
+
case 'set-url-validation':
|
|
886
|
+
urlValidationEnabled = payload === true;
|
|
887
|
+
log(`URL validation ${urlValidationEnabled ? 'enabled' : 'disabled'}`);
|
|
888
|
+
break;
|
|
889
|
+
|
|
890
|
+
default:
|
|
891
|
+
log(`Unknown action: ${action}`, 'warn');
|
|
892
|
+
}
|
|
893
|
+
} catch (error) {
|
|
894
|
+
log(`Error in updateControl for action ${action}: ${error.message}`, 'error');
|
|
895
|
+
throw error;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
// #endregion
|
|
899
|
+
|
|
900
|
+
// #region Button Visibility Functions
|
|
901
|
+
function updateDownloadButton(visible) {
|
|
902
|
+
const button = document.getElementById('downloadButton');
|
|
903
|
+
const secondaryButton = document.getElementById('secondaryDownload');
|
|
904
|
+
if (button) {
|
|
905
|
+
button.classList.toggle('hidden', !visible);
|
|
906
|
+
}
|
|
907
|
+
if (secondaryButton) {
|
|
908
|
+
secondaryButton.classList.toggle('hidden', !visible);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function updatePrintButton(visible) {
|
|
913
|
+
const button = document.getElementById('printButton');
|
|
914
|
+
const secondaryButton = document.getElementById('secondaryPrint');
|
|
915
|
+
if (button) {
|
|
916
|
+
button.classList.toggle('hidden', !visible);
|
|
917
|
+
}
|
|
918
|
+
if (secondaryButton) {
|
|
919
|
+
secondaryButton.classList.toggle('hidden', !visible);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function updateFullScreenButton(visible) {
|
|
924
|
+
const button = document.getElementById('presentationMode');
|
|
925
|
+
if (button) {
|
|
926
|
+
button.classList.toggle('hidden', !visible);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function updateFindButton(visible) {
|
|
931
|
+
const button = document.getElementById('viewFindButton');
|
|
932
|
+
if (button) {
|
|
933
|
+
button.classList.toggle('hidden', !visible);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function updateBookmarkButton(visible) {
|
|
938
|
+
const button = document.getElementById('viewBookmark');
|
|
939
|
+
if (button) {
|
|
940
|
+
button.classList.toggle('hidden', !visible);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function updateOpenFileButton(visible) {
|
|
945
|
+
const button = document.getElementById('openFile');
|
|
946
|
+
const secondaryButton = document.getElementById('secondaryOpenFile');
|
|
947
|
+
if (button) {
|
|
948
|
+
button.classList.toggle('hidden', !visible);
|
|
949
|
+
}
|
|
950
|
+
if (secondaryButton) {
|
|
951
|
+
secondaryButton.classList.toggle('hidden', !visible);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function updateAnnotationsButton(visible) {
|
|
955
|
+
// Handle annotations button visibility
|
|
956
|
+
// This might need to be implemented based on the specific annotation system
|
|
957
|
+
log(`Annotations button visibility set to: ${visible}`);
|
|
958
|
+
}
|
|
959
|
+
// #endregion
|
|
960
|
+
|
|
961
|
+
// #region Mode Control Functions
|
|
962
|
+
function updateZoom(zoom) {
|
|
963
|
+
const app = PDFViewerApplication;
|
|
964
|
+
if (!app || !app.pdfViewer || !app.eventBus) {
|
|
965
|
+
log('PDFViewerApplication, pdfViewer, or eventBus not ready for zoom update', 'warn');
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Mark zoom command as active to prevent infinite loop
|
|
970
|
+
ZoomCommandTracker.markZoomCommandStart();
|
|
971
|
+
|
|
972
|
+
try {
|
|
973
|
+
// Acceptable values: "auto", "page-fit", "page-width", "page-actual", "page-height", or a number/string number like "1.25"
|
|
974
|
+
const validStringZooms = ['auto', 'page-actual', 'page-fit', 'page-width', 'page-height'];
|
|
975
|
+
|
|
976
|
+
// Check if it's a valid string zoom
|
|
977
|
+
if (validStringZooms.includes(zoom)) {
|
|
978
|
+
// Dispatch the scalechanged event exactly like the UI does
|
|
979
|
+
app.eventBus.dispatch("scalechanged", {
|
|
980
|
+
source: app.toolbar, // Use toolbar as source, just like the UI does
|
|
981
|
+
value: zoom
|
|
982
|
+
});
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Check if it's a valid numeric zoom (including decimal strings like "1.25")
|
|
987
|
+
const numericZoom = Number(zoom);
|
|
988
|
+
if (!isNaN(numericZoom) && numericZoom > 0) {
|
|
989
|
+
// Dispatch the scalechanged event exactly like the UI does
|
|
990
|
+
app.eventBus.dispatch("scalechanged", {
|
|
991
|
+
source: app.toolbar, // Use toolbar as source, just like the UI does
|
|
992
|
+
value: zoom
|
|
993
|
+
});
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// If we get here, it's invalid
|
|
998
|
+
log(`Invalid zoom value: ${zoom}`, 'warn');
|
|
999
|
+
} finally {
|
|
1000
|
+
// Always clear the command flag, even if there's an error
|
|
1001
|
+
ZoomCommandTracker.markZoomCommandEnd();
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function updateCursor(cursor) {
|
|
1006
|
+
// Universal Dispatcher guarantees PDFViewerApplication availability at readiness level 4
|
|
1007
|
+
const app = PDFViewerApplication;
|
|
1008
|
+
|
|
1009
|
+
try {
|
|
1010
|
+
const cursorTool = cursor ? cursor.toUpperCase() : 'SELECT';
|
|
1011
|
+
|
|
1012
|
+
let toolId = 0; // Default to SELECT
|
|
1013
|
+
switch (cursorTool) {
|
|
1014
|
+
case 'HAND':
|
|
1015
|
+
case 'H':
|
|
1016
|
+
toolId = 1; // HAND
|
|
1017
|
+
break;
|
|
1018
|
+
case 'SELECT':
|
|
1019
|
+
case 'S':
|
|
1020
|
+
toolId = 0; // SELECT
|
|
1021
|
+
break;
|
|
1022
|
+
case 'ZOOM':
|
|
1023
|
+
case 'Z':
|
|
1024
|
+
toolId = 2; // ZOOM
|
|
1025
|
+
break;
|
|
1026
|
+
default:
|
|
1027
|
+
log(`Unknown cursor tool: ${cursorTool}, defaulting to SELECT`, 'warn');
|
|
1028
|
+
toolId = 0;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Update cursor using event bus dispatch (PDF.js v4.x)
|
|
1032
|
+
if (app.eventBus && typeof app.eventBus.dispatch === 'function') {
|
|
1033
|
+
app.eventBus.dispatch('switchcursortool', {
|
|
1034
|
+
tool: toolId
|
|
1035
|
+
});
|
|
1036
|
+
} else {
|
|
1037
|
+
log('EventBus not available for cursor update', 'warn');
|
|
1038
|
+
}
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
log(`Error updating cursor: ${error.message}`, 'error');
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function updateScroll(scroll) {
|
|
1045
|
+
// Universal Dispatcher guarantees PDFViewerApplication availability at readiness level 4
|
|
1046
|
+
|
|
1047
|
+
try {
|
|
1048
|
+
const scrollMode = scroll ? scroll.toUpperCase() : 'VERTICAL';
|
|
1049
|
+
log(`Attempting to update scroll mode to: ${scrollMode}`);
|
|
1050
|
+
|
|
1051
|
+
if (PDFViewerApplication.eventBus) {
|
|
1052
|
+
// Use PDF.js v4.x event bus dispatch for scroll mode switching
|
|
1053
|
+
switch (scrollMode) {
|
|
1054
|
+
case 'VERTICAL':
|
|
1055
|
+
case 'V':
|
|
1056
|
+
log('Dispatching switchscrollmode with VERTICAL mode');
|
|
1057
|
+
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1058
|
+
mode: 0 // ScrollMode.VERTICAL
|
|
1059
|
+
});
|
|
1060
|
+
break;
|
|
1061
|
+
case 'HORIZONTAL':
|
|
1062
|
+
case 'H':
|
|
1063
|
+
log('Dispatching switchscrollmode with HORIZONTAL mode');
|
|
1064
|
+
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1065
|
+
mode: 1 // ScrollMode.HORIZONTAL
|
|
1066
|
+
});
|
|
1067
|
+
break;
|
|
1068
|
+
case 'WRAPPED':
|
|
1069
|
+
case 'W':
|
|
1070
|
+
log('Dispatching switchscrollmode with WRAPPED mode');
|
|
1071
|
+
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1072
|
+
mode: 2 // ScrollMode.WRAPPED
|
|
1073
|
+
});
|
|
1074
|
+
break;
|
|
1075
|
+
case 'PAGE':
|
|
1076
|
+
case 'P':
|
|
1077
|
+
log('Dispatching switchscrollmode with PAGE mode');
|
|
1078
|
+
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1079
|
+
mode: 3 // ScrollMode.PAGE
|
|
1080
|
+
});
|
|
1081
|
+
break;
|
|
1082
|
+
default:
|
|
1083
|
+
log(`Unknown scroll mode: ${scrollMode}, defaulting to VERTICAL`, 'warn');
|
|
1084
|
+
PDFViewerApplication.eventBus.dispatch('switchscrollmode', {
|
|
1085
|
+
mode: 0
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
} else {
|
|
1089
|
+
// Fallback to direct property setting if event bus not available
|
|
1090
|
+
if (PDFViewerApplication.pdfViewer) {
|
|
1091
|
+
switch (scrollMode) {
|
|
1092
|
+
case 'VERTICAL':
|
|
1093
|
+
case 'V':
|
|
1094
|
+
PDFViewerApplication.pdfViewer.scrollMode = 0;
|
|
1095
|
+
break;
|
|
1096
|
+
case 'HORIZONTAL':
|
|
1097
|
+
case 'H':
|
|
1098
|
+
PDFViewerApplication.pdfViewer.scrollMode = 1;
|
|
1099
|
+
break;
|
|
1100
|
+
case 'WRAPPED':
|
|
1101
|
+
case 'W':
|
|
1102
|
+
PDFViewerApplication.pdfViewer.scrollMode = 2;
|
|
1103
|
+
break;
|
|
1104
|
+
case 'PAGE':
|
|
1105
|
+
case 'P':
|
|
1106
|
+
PDFViewerApplication.pdfViewer.scrollMode = 3;
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
log(`Scroll mode update completed for: ${scrollMode}`);
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
log(`Error updating scroll mode: ${error.message}`, 'error');
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function updateSpread(spread) {
|
|
1119
|
+
// Universal Dispatcher guarantees PDFViewerApplication availability at readiness level 4
|
|
1120
|
+
|
|
1121
|
+
try {
|
|
1122
|
+
const spreadMode = spread ? spread.toUpperCase() : 'NONE';
|
|
1123
|
+
log(`Attempting to update spread mode to: ${spreadMode}`);
|
|
1124
|
+
|
|
1125
|
+
if (PDFViewerApplication.eventBus) {
|
|
1126
|
+
// Use PDF.js v4.x event bus dispatch for spread mode switching
|
|
1127
|
+
switch (spreadMode) {
|
|
1128
|
+
case 'NONE':
|
|
1129
|
+
case 'N':
|
|
1130
|
+
log('Dispatching switchspreadmode with NONE mode');
|
|
1131
|
+
PDFViewerApplication.eventBus.dispatch('switchspreadmode', {
|
|
1132
|
+
mode: 0 // SpreadMode.NONE
|
|
1133
|
+
});
|
|
1134
|
+
break;
|
|
1135
|
+
case 'ODD':
|
|
1136
|
+
case 'O':
|
|
1137
|
+
log('Dispatching switchspreadmode with ODD mode');
|
|
1138
|
+
PDFViewerApplication.eventBus.dispatch('switchspreadmode', {
|
|
1139
|
+
mode: 1 // SpreadMode.ODD
|
|
1140
|
+
});
|
|
1141
|
+
break;
|
|
1142
|
+
case 'EVEN':
|
|
1143
|
+
case 'E':
|
|
1144
|
+
log('Dispatching switchspreadmode with EVEN mode');
|
|
1145
|
+
PDFViewerApplication.eventBus.dispatch('switchspreadmode', {
|
|
1146
|
+
mode: 2 // SpreadMode.EVEN
|
|
1147
|
+
});
|
|
1148
|
+
break;
|
|
1149
|
+
default:
|
|
1150
|
+
log(`Unknown spread mode: ${spreadMode}, defaulting to NONE`, 'warn');
|
|
1151
|
+
PDFViewerApplication.eventBus.dispatch('switchspreadmode', {
|
|
1152
|
+
mode: 0
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
} else {
|
|
1156
|
+
// Fallback to direct property setting if event bus not available
|
|
1157
|
+
if (PDFViewerApplication.pdfViewer) {
|
|
1158
|
+
switch (spreadMode) {
|
|
1159
|
+
case 'NONE':
|
|
1160
|
+
case 'N':
|
|
1161
|
+
PDFViewerApplication.pdfViewer.spreadMode = 0;
|
|
1162
|
+
break;
|
|
1163
|
+
case 'ODD':
|
|
1164
|
+
case 'O':
|
|
1165
|
+
PDFViewerApplication.pdfViewer.spreadMode = 1;
|
|
1166
|
+
break;
|
|
1167
|
+
case 'EVEN':
|
|
1168
|
+
case 'E':
|
|
1169
|
+
PDFViewerApplication.pdfViewer.spreadMode = 2;
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
log(`Spread mode update completed for: ${spreadMode}`);
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
log(`Error updating spread mode: ${error.message}`, 'error');
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
// #endregion
|
|
1181
|
+
|
|
1182
|
+
// Note: Legacy navigation and auto-action functions removed.
|
|
1183
|
+
// All functionality now handled through the main updateControl switch statement
|
|
1184
|
+
// which trusts the Universal Dispatcher's readiness guarantees.
|
|
1185
|
+
|
|
1186
|
+
// #region Error Handling Functions
|
|
1187
|
+
// Error state is managed internally and communicated via PostMessage
|
|
1188
|
+
// No need for global window variables - events drive the behavior
|
|
1189
|
+
function setErrorMessage(message) {
|
|
1190
|
+
// Error messages are sent directly via PostMessage when errors occur
|
|
1191
|
+
// No need to store in global variables
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function setErrorOverride(override) {
|
|
1195
|
+
// Error override is handled by the Angular component
|
|
1196
|
+
// No need to store in global variables
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function setErrorAppend(append) {
|
|
1200
|
+
// Error append is handled by the Angular component
|
|
1201
|
+
// No need to store in global variables
|
|
1202
|
+
}
|
|
1203
|
+
// #endregion
|
|
1204
|
+
|
|
1205
|
+
// #region CSS Zoom Functions
|
|
1206
|
+
|
|
1207
|
+
function setCssZoom(useCssZoom) {
|
|
1208
|
+
try {
|
|
1209
|
+
if (PDFViewerApplication && PDFViewerApplication.pdfViewer) {
|
|
1210
|
+
// Set CSS zoom mode
|
|
1211
|
+
PDFViewerApplication.pdfViewer.useOnlyCssZoom = useCssZoom === true;
|
|
1212
|
+
} else {
|
|
1213
|
+
// CSS zoom will be set when viewer becomes ready via readiness-based dispatch
|
|
1214
|
+
// No need to store in global variables
|
|
1215
|
+
}
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
log(`Error setting CSS zoom: ${error.message}`, 'error');
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function setDownloadFilename(filename) {
|
|
1222
|
+
try {
|
|
1223
|
+
if (filename && PDFViewerApplication) {
|
|
1224
|
+
// Ensure filename ends with .pdf if not already present
|
|
1225
|
+
const processedFilename = filename.endsWith('.pdf') ? filename : `${filename}.pdf`;
|
|
1226
|
+
|
|
1227
|
+
// Set the content disposition filename that PDF.js uses for downloads
|
|
1228
|
+
PDFViewerApplication._contentDispositionFilename = processedFilename;
|
|
1229
|
+
} else {
|
|
1230
|
+
log('Cannot set download filename - invalid filename or PDFViewerApplication not available', 'warn');
|
|
1231
|
+
}
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
log(`Error setting download filename: ${error.message}`, 'error');
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
// #endregion
|
|
1237
|
+
|
|
1238
|
+
// #region Event Configuration Functions
|
|
1239
|
+
// Event enablement state stored locally (not in global window variables)
|
|
1240
|
+
// This follows v5-upgrade.md principles while maintaining functionality
|
|
1241
|
+
|
|
1242
|
+
// Local state for event enablement
|
|
1243
|
+
const eventEnablement = {
|
|
1244
|
+
beforePrint: false,
|
|
1245
|
+
afterPrint: false,
|
|
1246
|
+
pagesLoaded: false,
|
|
1247
|
+
pageChange: false,
|
|
1248
|
+
documentError: false,
|
|
1249
|
+
documentInit: false,
|
|
1250
|
+
pagesInit: false,
|
|
1251
|
+
presentationModeChanged: false,
|
|
1252
|
+
openFile: false,
|
|
1253
|
+
find: false,
|
|
1254
|
+
updateFindMatchesCount: false,
|
|
1255
|
+
metadataLoaded: false,
|
|
1256
|
+
outlineLoaded: false,
|
|
1257
|
+
pageRendered: false,
|
|
1258
|
+
annotationLayerRendered: false,
|
|
1259
|
+
bookmarkClick: false,
|
|
1260
|
+
idle: false
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
function enableBeforePrint(enable) { eventEnablement.beforePrint = enable === true; }
|
|
1264
|
+
function enableAfterPrint(enable) { eventEnablement.afterPrint = enable === true; }
|
|
1265
|
+
function enablePagesLoaded(enable) { eventEnablement.pagesLoaded = enable === true; }
|
|
1266
|
+
function enablePageChange(enable) { eventEnablement.pageChange = enable === true; }
|
|
1267
|
+
function enableDocumentError(enable) { eventEnablement.documentError = enable === true; }
|
|
1268
|
+
function enableDocumentInit(enable) { eventEnablement.documentInit = enable === true; }
|
|
1269
|
+
function enablePagesInit(enable) { eventEnablement.pagesInit = enable === true; }
|
|
1270
|
+
function enablePresentationModeChanged(enable) { eventEnablement.presentationModeChanged = enable === true; }
|
|
1271
|
+
function enableOpenFile(enable) { eventEnablement.openFile = enable === true; }
|
|
1272
|
+
function enableFind(enable) { eventEnablement.find = enable === true; }
|
|
1273
|
+
function enableUpdateFindMatchesCount(enable) { eventEnablement.updateFindMatchesCount = enable === true; }
|
|
1274
|
+
function enableMetadataLoaded(enable) { eventEnablement.metadataLoaded = enable === true; }
|
|
1275
|
+
function enableOutlineLoaded(enable) { eventEnablement.outlineLoaded = enable === true; }
|
|
1276
|
+
function enablePageRendered(enable) { eventEnablement.pageRendered = enable === true; }
|
|
1277
|
+
function enableAnnotationLayerRendered(enable) { eventEnablement.annotationLayerRendered = enable === true; }
|
|
1278
|
+
function enableBookmarkClick(enable) { eventEnablement.bookmarkClick = enable === true; }
|
|
1279
|
+
function enableIdle(enable) { eventEnablement.idle = enable === true; }
|
|
1280
|
+
// #endregion
|
|
1281
|
+
|
|
1282
|
+
// #region Theme & Visual Customization Functions
|
|
1283
|
+
|
|
1284
|
+
// Theme management variables
|
|
1285
|
+
let currentTheme = 'light';
|
|
1286
|
+
|
|
1287
|
+
function setTheme(theme) {
|
|
1288
|
+
currentTheme = theme || 'light';
|
|
1289
|
+
|
|
1290
|
+
// Apply theme-specific CSS classes (CSP-safe)
|
|
1291
|
+
const body = document.body;
|
|
1292
|
+
if (body) {
|
|
1293
|
+
// Remove ALL theme classes including active states
|
|
1294
|
+
body.classList.remove('ng2-theme-light', 'ng2-theme-dark', 'ng2-theme-auto',
|
|
1295
|
+
'ng2-theme-dark-active', 'ng2-theme-light-active');
|
|
1296
|
+
|
|
1297
|
+
// Add new theme class
|
|
1298
|
+
body.classList.add(`ng2-theme-${currentTheme}`);
|
|
1299
|
+
|
|
1300
|
+
// Apply auto theme detection
|
|
1301
|
+
if (currentTheme === 'auto') {
|
|
1302
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
1303
|
+
const activeClass = prefersDark ? 'ng2-theme-dark-active' : 'ng2-theme-light-active';
|
|
1304
|
+
body.classList.add(activeClass);
|
|
1305
|
+
|
|
1306
|
+
// Listen for theme changes
|
|
1307
|
+
if (!window.ng2ThemeMediaListener) {
|
|
1308
|
+
const mediaListener = window.matchMedia('(prefers-color-scheme: dark)');
|
|
1309
|
+
mediaListener.addListener((e) => {
|
|
1310
|
+
if (currentTheme === 'auto') {
|
|
1311
|
+
body.classList.remove('ng2-theme-dark-active', 'ng2-theme-light-active');
|
|
1312
|
+
const newActiveClass = e.matches ? 'ng2-theme-dark-active' : 'ng2-theme-light-active';
|
|
1313
|
+
body.classList.add(newActiveClass);
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
window.ng2ThemeMediaListener = mediaListener;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Force style recalculation
|
|
1321
|
+
body.offsetHeight; // Trigger reflow
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// Note: Theme styles now loaded from external ng2-customization.css (CSP-safe)
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
function setPrimaryColor(color) {
|
|
1328
|
+
setCSSVariable('--ng2-primary-color', color);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function setBackgroundColor(color) {
|
|
1332
|
+
setCSSVariable('--ng2-background-color', color);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function setPageSpacing(margin, spreadMargin, border) {
|
|
1336
|
+
if (margin !== undefined) {
|
|
1337
|
+
setCSSVariable('--page-margin', margin);
|
|
1338
|
+
}
|
|
1339
|
+
if (spreadMargin !== undefined) {
|
|
1340
|
+
setCSSVariable('--spreadHorizontalWrapped-margin-LR', spreadMargin);
|
|
1341
|
+
}
|
|
1342
|
+
if (border !== undefined) {
|
|
1343
|
+
setCSSVariable('--page-border', border);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function setPageBorderColor(color) {
|
|
1348
|
+
setCSSVariable('--ng2-page-border-color', color);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function setToolbarColor(color) {
|
|
1352
|
+
setCSSVariable('--ng2-toolbar-color', color);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function setTextColor(color) {
|
|
1356
|
+
setCSSVariable('--ng2-text-color', color);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function setBorderRadius(radius) {
|
|
1360
|
+
setCSSVariable('--ng2-border-radius', radius);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function setCustomCSS(css, nonce) {
|
|
1364
|
+
// Remove existing custom CSS
|
|
1365
|
+
const existingStyle = document.getElementById('ng2-custom-css');
|
|
1366
|
+
if (existingStyle) {
|
|
1367
|
+
existingStyle.remove();
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// Apply new custom CSS with optional nonce for CSP support
|
|
1371
|
+
if (css) {
|
|
1372
|
+
const style = document.createElement('style');
|
|
1373
|
+
style.id = 'ng2-custom-css';
|
|
1374
|
+
style.textContent = css;
|
|
1375
|
+
|
|
1376
|
+
// Add nonce if provided (CSP support)
|
|
1377
|
+
if (nonce) {
|
|
1378
|
+
style.setAttribute('nonce', nonce);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
document.head.appendChild(style);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Helper function to set CSS custom properties
|
|
1386
|
+
function setCSSVariable(property, value) {
|
|
1387
|
+
if (value) {
|
|
1388
|
+
document.documentElement.style.setProperty(property, value);
|
|
1389
|
+
// Also set on body for better compatibility
|
|
1390
|
+
document.body.style.setProperty(property, value);
|
|
1391
|
+
} else {
|
|
1392
|
+
document.documentElement.style.removeProperty(property);
|
|
1393
|
+
document.body.style.removeProperty(property);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Note: Theme styles now loaded from external ng2-customization.css (CSP-safe)
|
|
1398
|
+
|
|
1399
|
+
// #endregion
|
|
1400
|
+
|
|
1401
|
+
// #region Error Event Listeners (Early Setup)
|
|
1402
|
+
let errorListenersSetup = false; // Flag to prevent multiple setups
|
|
1403
|
+
|
|
1404
|
+
function setupErrorEventListeners() {
|
|
1405
|
+
// Prevent multiple setups
|
|
1406
|
+
if (errorListenersSetup) {
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
const app = PDFViewerApplication;
|
|
1411
|
+
if (!app || !app.eventBus) {
|
|
1412
|
+
log('Cannot setup error listeners: EventBus not available', 'warn');
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// Listen for document errors (including file origin errors)
|
|
1417
|
+
app.eventBus.on('documenterror', (event) => {
|
|
1418
|
+
log(`🔴 DOCUMENT ERROR EVENT FIRED: ${event.message}`, 'error');
|
|
1419
|
+
// Always hide loading spinner on document error
|
|
1420
|
+
sendStateChangeNotification('loading', false, 'system');
|
|
1421
|
+
|
|
1422
|
+
// Send error state notification for custom error display
|
|
1423
|
+
const errorMessage = event.message || 'An error occurred while loading the PDF.';
|
|
1424
|
+
sendStateChangeNotification('error', errorMessage, 'system');
|
|
1425
|
+
|
|
1426
|
+
// Send document error events only if enabled
|
|
1427
|
+
if (eventEnablement.documentError) {
|
|
1428
|
+
log(`Document error event received: ${event.message}`);
|
|
1429
|
+
const errorData = {
|
|
1430
|
+
message: event.message || 'Unknown document error',
|
|
1431
|
+
source: event.source ? 'PDFViewerApplication' : 'unknown',
|
|
1432
|
+
name: event.name || 'DocumentError'
|
|
1433
|
+
};
|
|
1434
|
+
sendEventNotification('documentError', errorData);
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
// Listen for other potential error events
|
|
1439
|
+
app.eventBus.on('loaderror', (event) => {
|
|
1440
|
+
log(`🔴 LOAD ERROR EVENT FIRED: ${event.message}`, 'error');
|
|
1441
|
+
// Hide loading spinner on load error
|
|
1442
|
+
sendStateChangeNotification('loading', false, 'system');
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
app.eventBus.on('error', (event) => {
|
|
1446
|
+
log(`🔴 GENERIC ERROR EVENT FIRED: ${event.message}`, 'error');
|
|
1447
|
+
// Hide loading spinner on any error
|
|
1448
|
+
sendStateChangeNotification('loading', false, 'system');
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
errorListenersSetup = true;
|
|
1452
|
+
log('Error event listeners set up successfully', 'info');
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// #region Bidirectional Event Listeners
|
|
1456
|
+
let bidirectionalListenersSetup = false; // Flag to prevent multiple setups
|
|
1457
|
+
|
|
1458
|
+
function setupBidirectionalEventListeners() {
|
|
1459
|
+
// Prevent multiple setups
|
|
1460
|
+
if (bidirectionalListenersSetup) {
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// Add global error handler for iframe-level errors
|
|
1465
|
+
window.addEventListener('error', (event) => {
|
|
1466
|
+
log(`🔴 GLOBAL ERROR: ${event.message}`, 'error');
|
|
1467
|
+
// Hide loading spinner on any global error
|
|
1468
|
+
sendStateChangeNotification('loading', false, 'system');
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
1472
|
+
log(`🔴 UNHANDLED PROMISE REJECTION: ${event.reason}`, 'error');
|
|
1473
|
+
// Hide loading spinner on unhandled promise rejection
|
|
1474
|
+
sendStateChangeNotification('loading', false, 'system');
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
bidirectionalListenersSetup = true;
|
|
1478
|
+
const app = PDFViewerApplication;
|
|
1479
|
+
if (!app || !app.eventBus) {
|
|
1480
|
+
log('Cannot setup bidirectional listeners: EventBus not available', 'warn');
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
// Test sending a state change notification immediately
|
|
1486
|
+
sendStateChangeNotification('test', 'initial-setup', 'system');
|
|
1487
|
+
|
|
1488
|
+
// Cursor tool changes are handled via switchcursortool event listener only
|
|
1489
|
+
|
|
1490
|
+
// Note: Scroll and spread mode changes are handled via event listeners
|
|
1491
|
+
// No method interception needed for PDF.js v5.3.93
|
|
1492
|
+
if (app.pdfViewer) {
|
|
1493
|
+
|
|
1494
|
+
// Monitor scroll mode changes - setScrollMode doesn't exist in v5.3.93
|
|
1495
|
+
// The scroll mode changes are handled via event listeners instead
|
|
1496
|
+
|
|
1497
|
+
// Spread mode changes are handled via switchspreadmode event listener only
|
|
1498
|
+
|
|
1499
|
+
// Monitor zoom changes via currentScale property
|
|
1500
|
+
let lastKnownScale = app.pdfViewer.currentScale;
|
|
1501
|
+
const originalSetCurrentScale = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(app.pdfViewer), 'currentScale');
|
|
1502
|
+
if (originalSetCurrentScale && originalSetCurrentScale.set) {
|
|
1503
|
+
Object.defineProperty(app.pdfViewer, 'currentScale', {
|
|
1504
|
+
get: originalSetCurrentScale.get,
|
|
1505
|
+
set: function(scale) {
|
|
1506
|
+
const result = originalSetCurrentScale.set.call(this, scale);
|
|
1507
|
+
|
|
1508
|
+
if (scale !== lastKnownScale) {
|
|
1509
|
+
lastKnownScale = scale;
|
|
1510
|
+
|
|
1511
|
+
// Use enhanced zoom transformation
|
|
1512
|
+
const zoomValue = transformZoomFromViewer(scale, app);
|
|
1513
|
+
|
|
1514
|
+
log(`Zoom changed via currentScale to: ${zoomValue} (scale: ${scale}, currentScaleValue: ${app.pdfViewer.currentScaleValue})`);
|
|
1515
|
+
sendStateChangeNotification('zoom', zoomValue, 'user');
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
return result;
|
|
1519
|
+
},
|
|
1520
|
+
configurable: true,
|
|
1521
|
+
enumerable: true
|
|
1522
|
+
});
|
|
1523
|
+
} else {
|
|
1524
|
+
log('pdfViewer.currentScale setter not found', 'warn');
|
|
1525
|
+
}
|
|
1526
|
+
} else {
|
|
1527
|
+
log('pdfViewer not available', 'warn');
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// Listen for sidebar/page mode changes
|
|
1531
|
+
if (app.pdfSidebar) {
|
|
1532
|
+
|
|
1533
|
+
// Monitor sidebar view changes
|
|
1534
|
+
const originalSetView = app.pdfSidebar.setView;
|
|
1535
|
+
if (originalSetView) {
|
|
1536
|
+
app.pdfSidebar.setView = function(view) {
|
|
1537
|
+
const result = originalSetView.apply(this, arguments);
|
|
1538
|
+
|
|
1539
|
+
// Map PDF.js sidebar view numbers to our page mode names
|
|
1540
|
+
const pageModeMap = {
|
|
1541
|
+
0: 'none', // SidebarView.NONE
|
|
1542
|
+
1: 'thumbs', // SidebarView.THUMBS
|
|
1543
|
+
2: 'bookmarks', // SidebarView.OUTLINE
|
|
1544
|
+
3: 'attachments', // SidebarView.ATTACHMENTS
|
|
1545
|
+
4: 'layers' // SidebarView.LAYERS
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
const pageModeName = pageModeMap[view] || 'none';
|
|
1549
|
+
log(`Page mode changed to: ${pageModeName} (view: ${view})`);
|
|
1550
|
+
sendStateChangeNotification('pageMode', pageModeName, 'user');
|
|
1551
|
+
|
|
1552
|
+
return result;
|
|
1553
|
+
};
|
|
1554
|
+
} else {
|
|
1555
|
+
}
|
|
1556
|
+
} else {
|
|
1557
|
+
log('pdfSidebar not available', 'warn');
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// Listen for additional PDF.js events that might indicate property changes
|
|
1561
|
+
if (app.eventBus) {
|
|
1562
|
+
|
|
1563
|
+
// Listen for scale changing events with intelligent user vs programmatic detection
|
|
1564
|
+
app.eventBus.on('scalechanging', (event) => {
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
// Skip notification if this is from our own zoom command
|
|
1568
|
+
if (ZoomCommandTracker.isZoomCommandActive()) {
|
|
1569
|
+
log('Skipping zoom notification - programmatic change from our command');
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// Use event context to determine if this is a user-initiated change
|
|
1574
|
+
let isUserInitiated = false;
|
|
1575
|
+
let zoomValue;
|
|
1576
|
+
|
|
1577
|
+
if (event.presetValue && typeof event.presetValue === 'string') {
|
|
1578
|
+
// User clicked zoom buttons (Page Fit, Page Width, Auto, etc.)
|
|
1579
|
+
|
|
1580
|
+
isUserInitiated = true;
|
|
1581
|
+
zoomValue = event.presetValue;
|
|
1582
|
+
} else if (typeof event.scale === 'number' && !event.presetValue) {
|
|
1583
|
+
// Could be mouse wheel (user) or other programmatic change
|
|
1584
|
+
// For now, assume it's user-initiated (mouse wheel, etc.)
|
|
1585
|
+
// Future enhancement: could add more sophisticated detection
|
|
1586
|
+
log('Assuming user-initiated scale change (mouse wheel, etc.)');
|
|
1587
|
+
isUserInitiated = true;
|
|
1588
|
+
zoomValue = transformZoomFromViewer(event.scale, app);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
if (isUserInitiated) {
|
|
1592
|
+
|
|
1593
|
+
sendStateChangeNotification('zoom', zoomValue, 'user');
|
|
1594
|
+
} else {
|
|
1595
|
+
log('Skipping zoom notification - not identified as user-initiated');
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
// Listen for other relevant events
|
|
1600
|
+
app.eventBus.on('switchscrollmode', (event) => {
|
|
1601
|
+
|
|
1602
|
+
const scrollMap = {
|
|
1603
|
+
0: 'vertical', // ScrollMode.VERTICAL
|
|
1604
|
+
1: 'horizontal', // ScrollMode.HORIZONTAL
|
|
1605
|
+
2: 'wrapped', // ScrollMode.WRAPPED
|
|
1606
|
+
3: 'page' // ScrollMode.PAGE
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
const scrollName = scrollMap[event.mode] || 'vertical';
|
|
1610
|
+
log(`Scroll mode changed via event to: ${scrollName}`);
|
|
1611
|
+
sendStateChangeNotification('scroll', scrollName, 'user');
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
app.eventBus.on('switchspreadmode', (event) => {
|
|
1615
|
+
|
|
1616
|
+
const spreadMap = {
|
|
1617
|
+
0: 'none', // SpreadMode.NONE
|
|
1618
|
+
1: 'odd', // SpreadMode.ODD
|
|
1619
|
+
2: 'even' // SpreadMode.EVEN
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
const spreadName = spreadMap[event.mode] || 'none';
|
|
1623
|
+
log(`Spread mode changed via event to: ${spreadName}`);
|
|
1624
|
+
sendStateChangeNotification('spread', spreadName, 'user');
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
app.eventBus.on('switchcursortool', (event) => {
|
|
1628
|
+
|
|
1629
|
+
const cursorMap = {
|
|
1630
|
+
0: 'select', // CursorTool.SELECT
|
|
1631
|
+
1: 'hand', // CursorTool.HAND
|
|
1632
|
+
2: 'zoom' // CursorTool.ZOOM
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1635
|
+
const cursorName = cursorMap[event.tool] || 'select';
|
|
1636
|
+
log(`Cursor changed via event to: ${cursorName}`);
|
|
1637
|
+
sendStateChangeNotification('cursor', cursorName, 'user');
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
app.eventBus.on('sidebarviewchanged', (event) => {
|
|
1641
|
+
|
|
1642
|
+
const pageModeMap = {
|
|
1643
|
+
0: 'none', // SidebarView.NONE
|
|
1644
|
+
1: 'thumbs', // SidebarView.THUMBS
|
|
1645
|
+
2: 'bookmarks', // SidebarView.OUTLINE
|
|
1646
|
+
3: 'attachments', // SidebarView.ATTACHMENTS
|
|
1647
|
+
4: 'layers' // SidebarView.LAYERS
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
const pageModeName = pageModeMap[event.view] || 'none';
|
|
1651
|
+
log(`Page mode changed via event to: ${pageModeName}`);
|
|
1652
|
+
sendStateChangeNotification('pageMode', pageModeName, 'user');
|
|
1653
|
+
});
|
|
1654
|
+
|
|
1655
|
+
// Listen for rotation changes (when user rotates via UI or programmatically)
|
|
1656
|
+
app.eventBus.on('rotationchanging', (event) => {
|
|
1657
|
+
log(`Rotation changed via event to: ${event.pagesRotation}`);
|
|
1658
|
+
sendStateChangeNotification('rotation', event.pagesRotation, 'user');
|
|
1659
|
+
});
|
|
1660
|
+
|
|
1661
|
+
// Note: Error event listeners are set up earlier in setupErrorEventListeners()
|
|
1662
|
+
|
|
1663
|
+
app.eventBus.on('documentinit', (event) => {
|
|
1664
|
+
// Send document init events only if enabled
|
|
1665
|
+
if (eventEnablement.documentInit) {
|
|
1666
|
+
sendEventNotification('documentInit', null);
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
app.eventBus.on('pagesinit', (event) => {
|
|
1671
|
+
// Send pages init events only if enabled
|
|
1672
|
+
if (eventEnablement.pagesInit) {
|
|
1673
|
+
const pageCount = app.pagesCount || 0;
|
|
1674
|
+
const pagesData = {
|
|
1675
|
+
pagesCount: pageCount
|
|
1676
|
+
};
|
|
1677
|
+
sendEventNotification('pagesInit', pagesData);
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
app.eventBus.on('presentationmodechanged', (event) => {
|
|
1682
|
+
// Send presentation mode events only if enabled
|
|
1683
|
+
if (eventEnablement.presentationModeChanged) {
|
|
1684
|
+
// Map PDF.js PresentationModeState to boolean
|
|
1685
|
+
// FULLSCREEN = 3, CHANGING = 2, NORMAL = 1, UNKNOWN = 0
|
|
1686
|
+
const isActive = event.state === 3; // PresentationModeState.FULLSCREEN
|
|
1687
|
+
const isChanging = event.state === 2; // PresentationModeState.CHANGING
|
|
1688
|
+
const presentationData = {
|
|
1689
|
+
active: isActive,
|
|
1690
|
+
switchInProgress: isChanging
|
|
1691
|
+
};
|
|
1692
|
+
sendEventNotification('presentationModeChanged', presentationData);
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
app.eventBus.on('fileinputchange', (event) => {
|
|
1697
|
+
// Send open file events only if enabled
|
|
1698
|
+
if (eventEnablement.openFile) {
|
|
1699
|
+
sendEventNotification('openFile', null);
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
app.eventBus.on('find', (event) => {
|
|
1704
|
+
// Send find events only if enabled
|
|
1705
|
+
if (eventEnablement.find) {
|
|
1706
|
+
const findData = {
|
|
1707
|
+
query: event.query || '',
|
|
1708
|
+
phraseSearch: false, // Not available in PDF.js v5.3.93
|
|
1709
|
+
caseSensitive: event.caseSensitive || false,
|
|
1710
|
+
entireWord: event.entireWord || false,
|
|
1711
|
+
highlightAll: event.highlightAll || false,
|
|
1712
|
+
findPrevious: event.findPrevious || false
|
|
1713
|
+
};
|
|
1714
|
+
sendEventNotification('find', findData);
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
app.eventBus.on('updatefindmatchescount', (event) => {
|
|
1719
|
+
// Send find matches count events only if enabled
|
|
1720
|
+
if (eventEnablement.updateFindMatchesCount) {
|
|
1721
|
+
const countData = {
|
|
1722
|
+
current: event.matchesCount?.current || 0,
|
|
1723
|
+
total: event.matchesCount?.total || 0
|
|
1724
|
+
};
|
|
1725
|
+
sendEventNotification('updateFindMatchesCount', countData);
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
app.eventBus.on('metadataloaded', (event) => {
|
|
1730
|
+
// Send metadata loaded events only if enabled
|
|
1731
|
+
if (eventEnablement.metadataLoaded) {
|
|
1732
|
+
const info = app.documentInfo;
|
|
1733
|
+
const metadataData = {
|
|
1734
|
+
title: info?.Title,
|
|
1735
|
+
author: info?.Author,
|
|
1736
|
+
subject: info?.Subject,
|
|
1737
|
+
keywords: info?.Keywords,
|
|
1738
|
+
creator: info?.Creator,
|
|
1739
|
+
producer: info?.Producer,
|
|
1740
|
+
creationDate: info?.CreationDate,
|
|
1741
|
+
modificationDate: info?.ModDate,
|
|
1742
|
+
pdfFormatVersion: info?.PDFFormatVersion,
|
|
1743
|
+
isLinearized: info?.IsLinearized,
|
|
1744
|
+
isAcroFormPresent: info?.IsAcroFormPresent,
|
|
1745
|
+
isXFAPresent: info?.IsXFAPresent,
|
|
1746
|
+
isCollectionPresent: info?.IsCollectionPresent
|
|
1747
|
+
};
|
|
1748
|
+
sendEventNotification('metadataLoaded', metadataData);
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
app.eventBus.on('outlineloaded', (event) => {
|
|
1753
|
+
// Send outline loaded events only if enabled
|
|
1754
|
+
if (eventEnablement.outlineLoaded) {
|
|
1755
|
+
const outlineData = {
|
|
1756
|
+
items: [], // Outline items are not available in the event, but consumers can check hasOutline
|
|
1757
|
+
hasOutline: (event.outlineCount || 0) > 0
|
|
1758
|
+
};
|
|
1759
|
+
sendEventNotification('outlineLoaded', outlineData);
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
|
|
1763
|
+
app.eventBus.on('pagerendered', (event) => {
|
|
1764
|
+
// Send page rendered events only if enabled
|
|
1765
|
+
if (eventEnablement.pageRendered) {
|
|
1766
|
+
const renderData = {
|
|
1767
|
+
pageNumber: event.pageNumber || 1,
|
|
1768
|
+
// Don't include source as it contains canvas element that can't be cloned
|
|
1769
|
+
timestamp: Date.now()
|
|
1770
|
+
};
|
|
1771
|
+
sendEventNotification('pageRendered', renderData);
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
// Loading state integration for overlay control
|
|
1776
|
+
// Event-driven only; idempotent notifications, no flags or polling
|
|
1777
|
+
app.eventBus.on('documentinit', () => sendStateChangeNotification('loading', true, 'system'));
|
|
1778
|
+
app.eventBus.on('pagesinit', () => sendStateChangeNotification('loading', true, 'system'));
|
|
1779
|
+
app.eventBus.on('pagerendered', () => sendStateChangeNotification('loading', false, 'system'));
|
|
1780
|
+
app.eventBus.on('pagesloaded', () => sendStateChangeNotification('loading', false, 'system'));
|
|
1781
|
+
|
|
1782
|
+
// New high-value events
|
|
1783
|
+
|
|
1784
|
+
// Annotation Layer Rendered - Native PDF.js event
|
|
1785
|
+
app.eventBus.on('annotationlayerrendered', (event) => {
|
|
1786
|
+
// Send annotation layer rendered events only if enabled
|
|
1787
|
+
if (eventEnablement.annotationLayerRendered) {
|
|
1788
|
+
const renderData = {
|
|
1789
|
+
pageNumber: event.pageNumber || 1,
|
|
1790
|
+
error: event.error || null,
|
|
1791
|
+
timestamp: performance.now()
|
|
1792
|
+
};
|
|
1793
|
+
sendEventNotification('annotationLayerRendered', renderData);
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
// Reset idle timer when PDF document changes (event-driven approach)
|
|
1798
|
+
// Set up idle timer only if enabled
|
|
1799
|
+
if (eventEnablement.idle) {
|
|
1800
|
+
app.eventBus.on('documentloaded', () => {
|
|
1801
|
+
if (typeof resetIdleTimer === 'function') {
|
|
1802
|
+
resetIdleTimer();
|
|
1803
|
+
log('Idle timer reset on document load');
|
|
1804
|
+
}
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// Initialize custom event implementations
|
|
1810
|
+
initializeCustomEvents(app);
|
|
1811
|
+
|
|
1812
|
+
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// Initialize custom event implementations
|
|
1816
|
+
function initializeCustomEvents(app) {
|
|
1817
|
+
setupIdleDetection();
|
|
1818
|
+
interceptOutlineClicks(app);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// Idle Detection - Custom activity tracking
|
|
1822
|
+
let idleTimer = null;
|
|
1823
|
+
let idleListenersSetup = false;
|
|
1824
|
+
let idleCleanupListeners = []; // Track listeners for cleanup
|
|
1825
|
+
const IDLE_TIMEOUT = 30000; // 30 seconds default
|
|
1826
|
+
|
|
1827
|
+
function resetIdleTimer() {
|
|
1828
|
+
if (idleTimer) {
|
|
1829
|
+
clearTimeout(idleTimer);
|
|
1830
|
+
idleTimer = null;
|
|
1831
|
+
}
|
|
1832
|
+
// Set up idle timer only if enabled
|
|
1833
|
+
if (eventEnablement.idle) {
|
|
1834
|
+
idleTimer = setTimeout(() => {
|
|
1835
|
+
sendEventNotification('idle', null);
|
|
1836
|
+
}, IDLE_TIMEOUT);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function cleanupIdleDetection() {
|
|
1841
|
+
// Clear timer
|
|
1842
|
+
if (idleTimer) {
|
|
1843
|
+
clearTimeout(idleTimer);
|
|
1844
|
+
idleTimer = null;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// Remove all tracked event listeners
|
|
1848
|
+
idleCleanupListeners.forEach(({ element, event, handler, options }) => {
|
|
1849
|
+
element.removeEventListener(event, handler, options);
|
|
1850
|
+
});
|
|
1851
|
+
idleCleanupListeners = [];
|
|
1852
|
+
idleListenersSetup = false;
|
|
1853
|
+
|
|
1854
|
+
log('Idle detection cleanup completed');
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
function setupIdleDetection() {
|
|
1858
|
+
// Prevent multiple setups
|
|
1859
|
+
if (idleListenersSetup) {
|
|
1860
|
+
log('Idle detection already setup, skipping');
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
idleListenersSetup = true;
|
|
1865
|
+
|
|
1866
|
+
// Helper to add and track event listeners
|
|
1867
|
+
const addTrackedListener = (element, event, handler, options) => {
|
|
1868
|
+
element.addEventListener(event, handler, options);
|
|
1869
|
+
idleCleanupListeners.push({ element, event, handler, options });
|
|
1870
|
+
};
|
|
1871
|
+
|
|
1872
|
+
// Track mouse activity
|
|
1873
|
+
addTrackedListener(document, 'mousemove', resetIdleTimer, { passive: true });
|
|
1874
|
+
addTrackedListener(document, 'mousedown', resetIdleTimer, { passive: true });
|
|
1875
|
+
addTrackedListener(document, 'click', resetIdleTimer, { passive: true });
|
|
1876
|
+
|
|
1877
|
+
// Track keyboard activity
|
|
1878
|
+
addTrackedListener(document, 'keydown', resetIdleTimer, { passive: true });
|
|
1879
|
+
addTrackedListener(document, 'keypress', resetIdleTimer, { passive: true });
|
|
1880
|
+
|
|
1881
|
+
// Track scroll activity
|
|
1882
|
+
addTrackedListener(document, 'scroll', resetIdleTimer, { passive: true });
|
|
1883
|
+
addTrackedListener(document, 'wheel', resetIdleTimer, { passive: true });
|
|
1884
|
+
|
|
1885
|
+
// Track touch activity
|
|
1886
|
+
addTrackedListener(document, 'touchstart', resetIdleTimer, { passive: true });
|
|
1887
|
+
addTrackedListener(document, 'touchmove', resetIdleTimer, { passive: true });
|
|
1888
|
+
|
|
1889
|
+
// Start initial timer
|
|
1890
|
+
resetIdleTimer();
|
|
1891
|
+
|
|
1892
|
+
// Cleanup on document unload
|
|
1893
|
+
const unloadHandler = () => {
|
|
1894
|
+
cleanupIdleDetection();
|
|
1895
|
+
};
|
|
1896
|
+
addTrackedListener(window, 'beforeunload', unloadHandler, { passive: true });
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
// Bookmark Click - Intercept outline item clicks
|
|
1902
|
+
let bookmarkInterceptionSetup = false;
|
|
1903
|
+
|
|
1904
|
+
function interceptOutlineClicks(app) {
|
|
1905
|
+
// Prevent multiple setups
|
|
1906
|
+
if (bookmarkInterceptionSetup) {
|
|
1907
|
+
log('Bookmark click interception already setup, skipping');
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// Check if PDF outline viewer is available immediately
|
|
1912
|
+
if (app && app.pdfOutlineViewer && app.pdfOutlineViewer._bindLink) {
|
|
1913
|
+
const originalBindLink = app.pdfOutlineViewer._bindLink;
|
|
1914
|
+
|
|
1915
|
+
app.pdfOutlineViewer._bindLink = function(element, params) {
|
|
1916
|
+
// Call original bind method first
|
|
1917
|
+
originalBindLink.call(this, element, params);
|
|
1918
|
+
|
|
1919
|
+
// Intercept bookmark clicks only if enabled
|
|
1920
|
+
if (eventEnablement.bookmarkClick) {
|
|
1921
|
+
const originalOnClick = element.onclick;
|
|
1922
|
+
element.onclick = function(evt) {
|
|
1923
|
+
// Extract bookmark data
|
|
1924
|
+
const bookmarkData = {
|
|
1925
|
+
title: element.textContent?.trim() || 'Unknown',
|
|
1926
|
+
dest: params.dest || null,
|
|
1927
|
+
action: params.action || undefined,
|
|
1928
|
+
url: params.url || undefined,
|
|
1929
|
+
pageNumber: undefined, // Will be resolved by linkService
|
|
1930
|
+
isCurrentItem: element.classList.contains('currentTreeItem')
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
log(`Bookmark clicked: ${bookmarkData.title}`);
|
|
1934
|
+
sendEventNotification('bookmarkClick', bookmarkData);
|
|
1935
|
+
|
|
1936
|
+
// Call original handler to preserve navigation functionality
|
|
1937
|
+
if (originalOnClick) {
|
|
1938
|
+
return originalOnClick.call(this, evt);
|
|
1939
|
+
}
|
|
1940
|
+
return false;
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
bookmarkInterceptionSetup = true;
|
|
1946
|
+
|
|
1947
|
+
} else {
|
|
1948
|
+
// If outline viewer is not available yet, defer setup using event-driven approach
|
|
1949
|
+
// Hook into outlineloaded event to retry when outline becomes available
|
|
1950
|
+
if (app && app.eventBus) {
|
|
1951
|
+
const outlineLoadedHandler = () => {
|
|
1952
|
+
if (!bookmarkInterceptionSetup) {
|
|
1953
|
+
interceptOutlineClicks(app); // Retry when outline is loaded
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
app.eventBus.on('outlineloaded', outlineLoadedHandler);
|
|
1957
|
+
log('Bookmark click interception deferred until outline loads');
|
|
1958
|
+
} else {
|
|
1959
|
+
log('Unable to setup bookmark click interception - event bus not available', 'warn');
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
// #endregion
|
|
1964
|
+
|
|
1965
|
+
// Start the initialization process
|
|
1966
|
+
waitForViewer();
|
|
1967
1967
|
})();
|