react-native-pdf-jsi 2.2.7 → 3.0.0

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.
@@ -0,0 +1,538 @@
1
+ /**
2
+ * BookmarkManager - Smart Bookmark Management System
3
+ * Handles bookmark CRUD operations and reading progress tracking
4
+ *
5
+ * LICENSE:
6
+ * - Basic bookmarks (CRUD): MIT License (Free)
7
+ * - Enhanced features (colors, analytics): Commercial License (Paid)
8
+ *
9
+ * @author Punith M
10
+ * @version 1.0.0
11
+ */
12
+
13
+ import AsyncStorage from '@react-native-async-storage/async-storage';
14
+ import licenseManager, { ProFeature } from '../license/LicenseManager';
15
+
16
+ const STORAGE_KEY = '@react-native-pdf-jsi/bookmarks';
17
+ const PROGRESS_KEY = '@react-native-pdf-jsi/progress';
18
+
19
+ /**
20
+ * BookmarkManager Class
21
+ */
22
+ export class BookmarkManager {
23
+ constructor() {
24
+ this.bookmarks = new Map();
25
+ this.progress = new Map();
26
+ this.initialized = false;
27
+ }
28
+
29
+ /**
30
+ * Initialize bookmark manager
31
+ * Load bookmarks and progress from AsyncStorage
32
+ */
33
+ async initialize() {
34
+ if (this.initialized) return;
35
+
36
+ try {
37
+ // Load bookmarks
38
+ const bookmarksData = await AsyncStorage.getItem(STORAGE_KEY);
39
+ if (bookmarksData) {
40
+ const parsed = JSON.parse(bookmarksData);
41
+ this.bookmarks = new Map(Object.entries(parsed));
42
+ console.log(`📚 BookmarkManager: Loaded ${this.bookmarks.size} PDF bookmarks`);
43
+ }
44
+
45
+ // Load progress
46
+ const progressData = await AsyncStorage.getItem(PROGRESS_KEY);
47
+ if (progressData) {
48
+ const parsed = JSON.parse(progressData);
49
+ this.progress = new Map(Object.entries(parsed));
50
+ console.log(`📊 BookmarkManager: Loaded progress for ${this.progress.size} PDFs`);
51
+ }
52
+
53
+ this.initialized = true;
54
+ } catch (error) {
55
+ console.error('📚 BookmarkManager: Initialization error:', error);
56
+ throw error;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Save bookmarks to AsyncStorage
62
+ */
63
+ async saveBookmarks() {
64
+ try {
65
+ const data = Object.fromEntries(this.bookmarks);
66
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(data));
67
+ console.log('📚 BookmarkManager: Bookmarks saved');
68
+ } catch (error) {
69
+ console.error('📚 BookmarkManager: Save error:', error);
70
+ throw error;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Save progress to AsyncStorage
76
+ */
77
+ async saveProgress() {
78
+ try {
79
+ const data = Object.fromEntries(this.progress);
80
+ await AsyncStorage.setItem(PROGRESS_KEY, JSON.stringify(data));
81
+ console.log('📊 BookmarkManager: Progress saved');
82
+ } catch (error) {
83
+ console.error('📊 BookmarkManager: Progress save error:', error);
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Create a bookmark
90
+ * @param {string} pdfId - PDF identifier
91
+ * @param {Object} bookmark - Bookmark data
92
+ * @returns {Promise<Object>} Created bookmark
93
+ */
94
+ async createBookmark(pdfId, bookmark) {
95
+ await this.initialize();
96
+
97
+ // Check if using Pro features (colors)
98
+ if (bookmark.color && bookmark.color !== '#000000') {
99
+ licenseManager.requirePro('Bookmark Colors');
100
+ }
101
+
102
+ const newBookmark = {
103
+ id: this.generateId(),
104
+ pdfId,
105
+ page: bookmark.page,
106
+ name: bookmark.name || `Page ${bookmark.page}`,
107
+ color: bookmark.color || '#000000', // Default to black (free), colors require Pro
108
+ notes: bookmark.notes || '',
109
+ createdAt: new Date().toISOString(),
110
+ updatedAt: new Date().toISOString()
111
+ };
112
+
113
+ // Get or create bookmarks array for this PDF
114
+ const pdfBookmarks = this.bookmarks.get(pdfId) || [];
115
+ pdfBookmarks.push(newBookmark);
116
+ this.bookmarks.set(pdfId, pdfBookmarks);
117
+
118
+ await this.saveBookmarks();
119
+
120
+ console.log(`📚 BookmarkManager: Created bookmark for ${pdfId} page ${bookmark.page}`);
121
+ return newBookmark;
122
+ }
123
+
124
+ /**
125
+ * Get all bookmarks for a PDF
126
+ * @param {string} pdfId - PDF identifier
127
+ * @returns {Promise<Array>} Array of bookmarks
128
+ */
129
+ async getBookmarks(pdfId) {
130
+ await this.initialize();
131
+
132
+ const bookmarks = this.bookmarks.get(pdfId) || [];
133
+ return bookmarks.sort((a, b) => a.page - b.page);
134
+ }
135
+
136
+ /**
137
+ * Get bookmark by ID
138
+ * @param {string} pdfId - PDF identifier
139
+ * @param {string} bookmarkId - Bookmark ID
140
+ * @returns {Promise<Object|null>} Bookmark or null
141
+ */
142
+ async getBookmark(pdfId, bookmarkId) {
143
+ await this.initialize();
144
+
145
+ const bookmarks = this.bookmarks.get(pdfId) || [];
146
+ return bookmarks.find(b => b.id === bookmarkId) || null;
147
+ }
148
+
149
+ /**
150
+ * Update a bookmark
151
+ * @param {string} pdfId - PDF identifier
152
+ * @param {string} bookmarkId - Bookmark ID
153
+ * @param {Object} updates - Fields to update
154
+ * @returns {Promise<Object>} Updated bookmark
155
+ */
156
+ async updateBookmark(pdfId, bookmarkId, updates) {
157
+ await this.initialize();
158
+
159
+ const bookmarks = this.bookmarks.get(pdfId) || [];
160
+ const index = bookmarks.findIndex(b => b.id === bookmarkId);
161
+
162
+ if (index === -1) {
163
+ throw new Error(`Bookmark ${bookmarkId} not found`);
164
+ }
165
+
166
+ const updatedBookmark = {
167
+ ...bookmarks[index],
168
+ ...updates,
169
+ updatedAt: new Date().toISOString()
170
+ };
171
+
172
+ bookmarks[index] = updatedBookmark;
173
+ this.bookmarks.set(pdfId, bookmarks);
174
+
175
+ await this.saveBookmarks();
176
+
177
+ console.log(`📚 BookmarkManager: Updated bookmark ${bookmarkId}`);
178
+ return updatedBookmark;
179
+ }
180
+
181
+ /**
182
+ * Delete a bookmark
183
+ * @param {string} pdfId - PDF identifier
184
+ * @param {string} bookmarkId - Bookmark ID
185
+ * @returns {Promise<boolean>} Success status
186
+ */
187
+ async deleteBookmark(pdfId, bookmarkId) {
188
+ await this.initialize();
189
+
190
+ const bookmarks = this.bookmarks.get(pdfId) || [];
191
+ const filtered = bookmarks.filter(b => b.id !== bookmarkId);
192
+
193
+ if (filtered.length === bookmarks.length) {
194
+ return false; // Bookmark not found
195
+ }
196
+
197
+ this.bookmarks.set(pdfId, filtered);
198
+ await this.saveBookmarks();
199
+
200
+ console.log(`📚 BookmarkManager: Deleted bookmark ${bookmarkId}`);
201
+ return true;
202
+ }
203
+
204
+ /**
205
+ * Delete all bookmarks for a PDF
206
+ * @param {string} pdfId - PDF identifier
207
+ * @returns {Promise<number>} Number of bookmarks deleted
208
+ */
209
+ async deleteAllBookmarks(pdfId) {
210
+ await this.initialize();
211
+
212
+ const bookmarks = this.bookmarks.get(pdfId) || [];
213
+ const count = bookmarks.length;
214
+
215
+ this.bookmarks.delete(pdfId);
216
+ await this.saveBookmarks();
217
+
218
+ console.log(`📚 BookmarkManager: Deleted ${count} bookmarks for ${pdfId}`);
219
+ return count;
220
+ }
221
+
222
+ /**
223
+ * Get bookmarks by page
224
+ * @param {string} pdfId - PDF identifier
225
+ * @param {number} page - Page number
226
+ * @returns {Promise<Array>} Bookmarks on that page
227
+ */
228
+ async getBookmarksOnPage(pdfId, page) {
229
+ await this.initialize();
230
+
231
+ const bookmarks = this.bookmarks.get(pdfId) || [];
232
+ return bookmarks.filter(b => b.page === page);
233
+ }
234
+
235
+ /**
236
+ * Check if page has bookmark
237
+ * @param {string} pdfId - PDF identifier
238
+ * @param {number} page - Page number
239
+ * @returns {Promise<boolean>} True if page has bookmark
240
+ */
241
+ async hasBookmarkOnPage(pdfId, page) {
242
+ const bookmarks = await this.getBookmarksOnPage(pdfId, page);
243
+ return bookmarks.length > 0;
244
+ }
245
+
246
+ // ============================================
247
+ // READING PROGRESS TRACKING
248
+ // ============================================
249
+
250
+ /**
251
+ * Update reading progress
252
+ * @param {string} pdfId - PDF identifier
253
+ * @param {Object} progressData - Progress data
254
+ */
255
+ async updateProgress(pdfId, progressData) {
256
+ await this.initialize();
257
+
258
+ // Basic progress tracking is free
259
+ // Advanced analytics require Pro
260
+ if (progressData.timeSpent || progressData.sessions) {
261
+ licenseManager.requirePro('Reading Progress Tracking');
262
+ }
263
+
264
+ const currentProgress = this.progress.get(pdfId) || {
265
+ pdfId,
266
+ currentPage: 1,
267
+ totalPages: progressData.totalPages || 0,
268
+ pagesRead: [],
269
+ timeSpent: 0,
270
+ sessions: 0,
271
+ lastRead: null,
272
+ createdAt: new Date().toISOString()
273
+ };
274
+
275
+ // Update progress
276
+ const updatedProgress = {
277
+ ...currentProgress,
278
+ currentPage: progressData.currentPage || currentProgress.currentPage,
279
+ totalPages: progressData.totalPages || currentProgress.totalPages,
280
+ lastRead: new Date().toISOString(),
281
+ updatedAt: new Date().toISOString()
282
+ };
283
+
284
+ // Track unique pages read
285
+ if (progressData.currentPage && !currentProgress.pagesRead.includes(progressData.currentPage)) {
286
+ updatedProgress.pagesRead = [...currentProgress.pagesRead, progressData.currentPage].sort((a, b) => a - b);
287
+ }
288
+
289
+ // Track time spent
290
+ if (progressData.timeSpent) {
291
+ updatedProgress.timeSpent = currentProgress.timeSpent + progressData.timeSpent;
292
+ }
293
+
294
+ // Track sessions
295
+ if (progressData.newSession) {
296
+ updatedProgress.sessions = currentProgress.sessions + 1;
297
+ }
298
+
299
+ this.progress.set(pdfId, updatedProgress);
300
+ await this.saveProgress();
301
+
302
+ return updatedProgress;
303
+ }
304
+
305
+ /**
306
+ * Get reading progress
307
+ * @param {string} pdfId - PDF identifier
308
+ * @returns {Promise<Object>} Progress data
309
+ */
310
+ async getProgress(pdfId) {
311
+ await this.initialize();
312
+
313
+ const progress = this.progress.get(pdfId);
314
+
315
+ if (!progress) {
316
+ return {
317
+ pdfId,
318
+ currentPage: 1,
319
+ totalPages: 0,
320
+ pagesRead: [],
321
+ percentage: 0,
322
+ timeSpent: 0,
323
+ sessions: 0,
324
+ lastRead: null
325
+ };
326
+ }
327
+
328
+ // Calculate percentage
329
+ const percentage = progress.totalPages > 0
330
+ ? (progress.pagesRead.length / progress.totalPages) * 100
331
+ : 0;
332
+
333
+ return {
334
+ ...progress,
335
+ percentage: Math.round(percentage)
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Get all reading progress
341
+ * @returns {Promise<Array>} Array of progress for all PDFs
342
+ */
343
+ async getAllProgress() {
344
+ await this.initialize();
345
+
346
+ const allProgress = [];
347
+ for (const [pdfId, progress] of this.progress.entries()) {
348
+ const percentage = progress.totalPages > 0
349
+ ? (progress.pagesRead.length / progress.totalPages) * 100
350
+ : 0;
351
+
352
+ allProgress.push({
353
+ ...progress,
354
+ percentage: Math.round(percentage)
355
+ });
356
+ }
357
+
358
+ return allProgress.sort((a, b) =>
359
+ new Date(b.lastRead) - new Date(a.lastRead)
360
+ );
361
+ }
362
+
363
+ /**
364
+ * Delete reading progress
365
+ * @param {string} pdfId - PDF identifier
366
+ * @returns {Promise<boolean>} Success status
367
+ */
368
+ async deleteProgress(pdfId) {
369
+ await this.initialize();
370
+
371
+ const existed = this.progress.has(pdfId);
372
+ this.progress.delete(pdfId);
373
+
374
+ if (existed) {
375
+ await this.saveProgress();
376
+ console.log(`📊 BookmarkManager: Deleted progress for ${pdfId}`);
377
+ }
378
+
379
+ return existed;
380
+ }
381
+
382
+ /**
383
+ * Get statistics for a PDF
384
+ * @param {string} pdfId - PDF identifier
385
+ * @returns {Promise<Object>} Statistics
386
+ */
387
+ async getStatistics(pdfId) {
388
+ await this.initialize();
389
+
390
+ const bookmarks = await this.getBookmarks(pdfId);
391
+ const progress = await this.getProgress(pdfId);
392
+
393
+ return {
394
+ totalBookmarks: bookmarks.length,
395
+ pagesWithBookmarks: [...new Set(bookmarks.map(b => b.page))].length,
396
+ currentPage: progress.currentPage,
397
+ totalPages: progress.totalPages,
398
+ pagesRead: progress.pagesRead.length,
399
+ percentage: progress.percentage,
400
+ timeSpent: progress.timeSpent,
401
+ sessions: progress.sessions,
402
+ lastRead: progress.lastRead,
403
+ avgTimePerPage: progress.pagesRead.length > 0
404
+ ? Math.round(progress.timeSpent / progress.pagesRead.length)
405
+ : 0
406
+ };
407
+ }
408
+
409
+ // ============================================
410
+ // UTILITY METHODS
411
+ // ============================================
412
+
413
+ /**
414
+ * Generate unique ID
415
+ * @returns {string} Unique ID
416
+ */
417
+ generateId() {
418
+ return `bookmark_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
419
+ }
420
+
421
+ /**
422
+ * Export bookmarks and progress
423
+ * @param {string} pdfId - PDF identifier (optional, exports all if not provided)
424
+ * @returns {Promise<Object>} Export data
425
+ */
426
+ async exportData(pdfId = null) {
427
+ await this.initialize();
428
+
429
+ if (pdfId) {
430
+ return {
431
+ bookmarks: await this.getBookmarks(pdfId),
432
+ progress: await this.getProgress(pdfId),
433
+ exportedAt: new Date().toISOString()
434
+ };
435
+ }
436
+
437
+ // Export all
438
+ const allBookmarks = Object.fromEntries(this.bookmarks);
439
+ const allProgress = await this.getAllProgress();
440
+
441
+ return {
442
+ bookmarks: allBookmarks,
443
+ progress: allProgress,
444
+ exportedAt: new Date().toISOString()
445
+ };
446
+ }
447
+
448
+ /**
449
+ * Import bookmarks and progress
450
+ * @param {Object} data - Import data
451
+ * @returns {Promise<Object>} Import summary
452
+ */
453
+ async importData(data) {
454
+ await this.initialize();
455
+
456
+ let bookmarksImported = 0;
457
+ let progressImported = 0;
458
+
459
+ try {
460
+ // Import bookmarks
461
+ if (data.bookmarks) {
462
+ if (typeof data.bookmarks === 'object' && !Array.isArray(data.bookmarks)) {
463
+ // Import all PDFs
464
+ for (const [pdfId, bookmarks] of Object.entries(data.bookmarks)) {
465
+ this.bookmarks.set(pdfId, bookmarks);
466
+ bookmarksImported += bookmarks.length;
467
+ }
468
+ }
469
+ await this.saveBookmarks();
470
+ }
471
+
472
+ // Import progress
473
+ if (data.progress) {
474
+ if (Array.isArray(data.progress)) {
475
+ data.progress.forEach(p => {
476
+ this.progress.set(p.pdfId, p);
477
+ progressImported++;
478
+ });
479
+ } else if (typeof data.progress === 'object') {
480
+ this.progress.set(data.progress.pdfId, data.progress);
481
+ progressImported++;
482
+ }
483
+ await this.saveProgress();
484
+ }
485
+
486
+ console.log(`📚 BookmarkManager: Imported ${bookmarksImported} bookmarks and ${progressImported} progress entries`);
487
+
488
+ return {
489
+ success: true,
490
+ bookmarksImported,
491
+ progressImported
492
+ };
493
+
494
+ } catch (error) {
495
+ console.error('📚 BookmarkManager: Import error:', error);
496
+ throw error;
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Clear all data (use with caution!)
502
+ */
503
+ async clearAll() {
504
+ this.bookmarks.clear();
505
+ this.progress.clear();
506
+ await AsyncStorage.multiRemove([STORAGE_KEY, PROGRESS_KEY]);
507
+ console.log('📚 BookmarkManager: All data cleared');
508
+ }
509
+
510
+ /**
511
+ * Get storage size estimate
512
+ * @returns {Promise<Object>} Size information
513
+ */
514
+ async getStorageSize() {
515
+ await this.initialize();
516
+
517
+ const bookmarksStr = JSON.stringify(Object.fromEntries(this.bookmarks));
518
+ const progressStr = JSON.stringify(Object.fromEntries(this.progress));
519
+
520
+ return {
521
+ bookmarks: {
522
+ count: Array.from(this.bookmarks.values()).flat().length,
523
+ sizeKB: Math.round(new Blob([bookmarksStr]).size / 1024)
524
+ },
525
+ progress: {
526
+ count: this.progress.size,
527
+ sizeKB: Math.round(new Blob([progressStr]).size / 1024)
528
+ },
529
+ totalKB: Math.round((new Blob([bookmarksStr]).size + new Blob([progressStr]).size) / 1024)
530
+ };
531
+ }
532
+ }
533
+
534
+ // Create singleton instance
535
+ const bookmarkManager = new BookmarkManager();
536
+
537
+ export default bookmarkManager;
538
+