1(function () { 2 'use strict'; 3 4 var DB_NAME = 'surewordbible_sync'; 5 var DB_VERSION = 1; 6 var dbPromise = null; 7 8 function openDb() { 9 if (dbPromise) return dbPromise; 10 dbPromise = new Promise(function (resolve, reject) { 11 var request = indexedDB.open(DB_NAME, DB_VERSION); 12 request.onupgradeneeded = function (event) { 13 var db = event.target.result; 14 if (!db.objectStoreNames.contains('meta')) { 15 db.createObjectStore('meta', { keyPath: 'key' }); 16 } 17 ['favourites', 'highlights', 'verseNotes', 'linkedVerses'].forEach(function (store) { 18 if (!db.objectStoreNames.contains(store)) { 19 db.createObjectStore(store, { keyPath: 'bibleId' }); 20 } 21 }); 22 }; 23 request.onsuccess = function () { resolve(request.result); }; 24 request.onerror = function () { reject(request.error); }; 25 }); 26 return dbPromise; 27 } 28 29 function txStore(storeName, mode) { 30 return openDb().then(function (db) { 31 return db.transaction(storeName, mode).objectStore(storeName); 32 }); 33 } 34 35 function putAll(storeName, rows) { 36 return openDb().then(function (db) { 37 return new Promise(function (resolve, reject) { 38 var tx = db.transaction(storeName, 'readwrite'); 39 var store = tx.objectStore(storeName); 40 rows.forEach(function (row) { store.put(row); }); 41 tx.oncomplete = function () { resolve(); }; 42 tx.onerror = function () { reject(tx.error); }; 43 }); 44 }); 45 } 46 47 window.SwbSyncStore = { 48 normalizeHighlightColor: normalizeHighlightColor, 49 50 getMeta: function () { 51 return txStore('meta', 'readonly').then(function (store) { 52 return new Promise(function (resolve) { 53 var req = store.get('sync'); 54 req.onsuccess = function () { 55 resolve(req.result || { key: 'sync', revision: 0, userId: null }); 56 }; 57 }); 58 }); 59 }, 60 setMeta: function (meta) { 61 return txStore('meta', 'readwrite').then(function (store) { 62 meta.key = 'sync'; 63 store.put(meta); 64 }); 65 }, 66 getAll: function (storeName) { 67 return txStore(storeName, 'readonly').then(function (store) { 68 return new Promise(function (resolve) { 69 var req = store.getAll(); 70 req.onsuccess = function () { resolve(req.result || []); }; 71 }); 72 }); 73 }, 74 putRow: function (storeName, row) { 75 return txStore(storeName, 'readwrite').then(function (store) { 76 store.put(row); 77 }); 78 }, 79 applyPullPayload: function (payload) { 80 var tasks = []; 81 if (payload.favourites) { 82 tasks.push(putAll('favourites', payload.favourites.filter(function (r) { return !r.isDeleted; }).map(toFavouriteRow))); 83 } 84 if (payload.highlights) { 85 tasks.push(putAll('highlights', payload.highlights.filter(function (r) { return !r.isDeleted; }).map(toHighlightRow))); 86 } 87 if (payload.verseNotes) { 88 tasks.push(putAll('verseNotes', payload.verseNotes.filter(function (r) { return !r.isDeleted; }).map(toVerseNoteRow))); 89 } 90 if (payload.linkedVerses) { 91 tasks.push(putAll('linkedVerses', payload.linkedVerses.filter(function (r) { return !r.isDeleted; }).map(toLinkedVerseRow))); 92 } 93 return Promise.all(tasks).then(function () { 94 return window.SwbSyncStore.setMeta({ revision: payload.revision || 0 }); 95 }); 96 }, 97 mergePullPayload: function (payload) { 98 var stores = [ 99 ['favourites', payload.favourites, toFavouriteRow], 100 ['highlights', payload.highlights, toHighlightRow], 101 ['verseNotes', payload.verseNotes, toVerseNoteRow], 102 ['linkedVerses', payload.linkedVerses, toLinkedVerseRow] 103 ]; 104 return window.SwbSyncStore.getMeta().then(function (meta) { 105 return Promise.all(stores.map(function (entry) { 106 return mergeStore(entry[0], entry[1] || [], entry[2]); 107 })).then(function () { 108 var revision = Math.max(meta.revision || 0, payload.revision || 0); 109 return window.SwbSyncStore.setMeta({ revision: revision, userId: meta.userId }); 110 }); 111 }); 112 }, 113 getChapterOverlay: function (book, chapter) { 114 return Promise.all([ 115 window.SwbSyncStore.getAll('favourites'), 116 window.SwbSyncStore.getAll('highlights'), 117 window.SwbSyncStore.getAll('verseNotes'), 118 window.SwbSyncStore.getAll('linkedVerses') 119 ]).then(function (results) { 120 return { 121 favourites: filterChapter(results[0], book, chapter), 122 highlights: filterChapter(results[1], book, chapter).map(function (row) { 123 return { 124 bibleId: row.bibleId, 125 book: row.book, 126 chapter: row.chapter, 127 verse: row.verse, 128 colorName: normalizeHighlightColor(row.colorName), 129 isDeleted: row.isDeleted, 130 updatedAt: row.updatedAt, 131 revision: row.revision 132 }; 133 }), 134 verseNotes: filterChapter(results[2], book, chapter), 135 linkedVerses: filterChapter(results[3], book, chapter) 136 }; 137 }); 138 }, 139 buildPushPayload: function () { 140 return Promise.all([ 141 window.SwbSyncStore.getAll('favourites'), 142 window.SwbSyncStore.getAll('highlights'), 143 window.SwbSyncStore.getAll('verseNotes'), 144 window.SwbSyncStore.getAll('linkedVerses') 145 ]).then(function (results) { 146 return { 147 favourites: results[0].map(fromFavouriteRow), 148 highlights: results[1].map(fromHighlightRow), 149 verseNotes: results[2].map(fromVerseNoteRow), 150 linkedVerses: results[3].map(fromLinkedVerseRow) 151 }; 152 }); 153 } 154 }; 155 156 function mergeStore(storeName, rows, mapper) { 157 return Promise.all(rows.map(function (row) { 158 if (row.isDeleted) { 159 return txStore(storeName, 'readwrite').then(function (store) { 160 store.delete(row.bibleId || toBibleId(row.book, row.chapter, row.verse)); 161 }); 162 } 163 var mapped = mapper(row); 164 return window.SwbSyncStore.putRow(storeName, mapped); 165 })); 166 } 167 168 function filterChapter(rows, book, chapter) { 169 return rows.filter(function (row) { 170 return row.book === book && row.chapter === chapter && !row.isDeleted; 171 }); 172 } 173 174 function toBibleId(book, chapter, verse) { 175 return book * 1000000 + chapter * 1000 + verse;
176 } 177 178 function nowMs() { return Date.now(); } 179 180 function toFavouriteRow(dto) { 181 return { 182 bibleId: dto.bibleId || toBibleId(dto.book, dto.chapter, dto.verse), 183 book: dto.book, 184 chapter: dto.chapter, 185 verse: dto.verse, 186 isStarred: dto.isStarred !== false, 187 isDeleted: !!dto.isDeleted, 188 updatedAt: dto.updatedAt || nowMs(), 189 revision: dto.revision || 0 190 }; 191 } 192 193 var legacyHighlightColors = { 194 highlightLemon: 'slot_1', 195 highlightPeach: 'slot_2', 196 highlightMint: 'slot_3', 197 highlightLavender: 'slot_4', 198 highlightSky: 'slot_5', 199 highlightOrchid: 'slot_6', 200 highlightCoral: 'slot_7', 201 highlightAmberDeep: 'slot_8' 202 }; 203 204 function normalizeHighlightColor(colorName) { 205 if (!colorName || colorName === 'Reset') return 'slot_1'; 206 if (legacyHighlightColors[colorName]) return legacyHighlightColors[colorName]; 207 if (/^slot_\d+$/.test(colorName)) return colorName; 208 return 'slot_1'; 209 } 210 211 function toHighlightRow(dto) { 212 return { 213 bibleId: dto.bibleId || toBibleId(dto.book, dto.chapter, dto.verse), 214 book: dto.book, 215 chapter: dto.chapter, 216 verse: dto.verse, 217 colorName: normalizeHighlightColor(dto.colorName), 218 isDeleted: !!dto.isDeleted, 219 updatedAt: dto.updatedAt || nowMs(), 220 revision: dto.revision || 0 221 }; 222 } 223 224 function toVerseNoteRow(dto) { 225 return { 226 bibleId: dto.bibleId || toBibleId(dto.book, dto.chapter, dto.verse), 227 book: dto.book, 228 chapter: dto.chapter, 229 verse: dto.verse, 230 note: dto.note || '', 231 audioPath: dto.audioPath || null, 232 isDeleted: !!dto.isDeleted, 233 updatedAt: dto.updatedAt || nowMs(), 234 revision: dto.revision || 0 235 }; 236 } 237 238 function toLinkedVerseRow(dto) { 239 return { 240 bibleId: dto.bibleId || toBibleId(dto.book, dto.chapter, dto.verse), 241 book: dto.book, 242 chapter: dto.chapter, 243 verse: dto.verse, 244 reference: dto.reference || '', 245 systemReference: dto.systemReference || '', 246 isDeleted: !!dto.isDeleted, 247 updatedAt: dto.updatedAt || nowMs(), 248 revision: dto.revision || 0 249 }; 250 } 251 252 function fromFavouriteRow(row) { 253 return { 254 bibleId: row.bibleId, 255 book: row.book, 256 chapter: row.chapter, 257 verse: row.verse, 258 isStarred: row.isStarred !== false, 259 isDeleted: !!row.isDeleted, 260 updatedAt: row.updatedAt || nowMs(), 261 revision: row.revision || 0 262 }; 263 } 264 265 function fromHighlightRow(row) { 266 return { 267 bibleId: row.bibleId, 268 book: row.book, 269 chapter: row.chapter, 270 verse: row.verse, 271 colorName: row.colorName, 272 isDeleted: !!row.isDeleted, 273 updatedAt: row.updatedAt || nowMs(), 274 revision: row.revision || 0 275 }; 276 } 277 278 function fromVerseNoteRow(row) { 279 return { 280 bibleId: row.bibleId, 281 book: row.book, 282 chapter: row.chapter, 283 verse: row.verse, 284 note: row.note, 285 audioPath: row.audioPath, 286 isDeleted: !!row.isDeleted, 287 updatedAt: row.updatedAt || nowMs(), 288 revision: row.revision || 0 289 }; 290 } 291 292 function fromLinkedVerseRow(row) { 293 return { 294 bibleId: row.bibleId, 295 book: row.book, 296 chapter: row.chapter, 297 verse: row.verse, 298 reference: row.reference, 299 systemReference: row.systemReference, 300 isDeleted: !!row.isDeleted, 301 updatedAt: row.updatedAt || nowMs(), 302 revision: row.revision || 0 303 }; 304 } 305})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.