1import Store from 'reader/lib/Store'; 2import User from 'reader/core/models/User'; 3 4export default class Bookmark { 5 constructor(key, value) { 6 this.key = key; 7 this.value = value; 8 } 9 10 get issueId() { 11 const splitKey = this.key.split('.'); 12 return Number(splitKey[splitKey.length - 1]); 13 } 14 15 /** 16 * @returns {{label: string, index: number}[]} 17 */ 18 get pages() { 19 return this.value.map((page) => { 20 const newPage = { label: page.label, index: Number(page.index) }; 21 22 if (page.title) { 23 newPage.title = page.title; 24 } 25 26 return newPage; 27 }); 28 } 29 30 static get namespace() { 31 return [User.current().id, 'bookmark']; 32 } 33 34 static async all() { 35 const bookmarks = await Store 36 .withNamespace(this.namespace) 37 .getAll(); 38 39 return bookmarks.map(([key, value]) => new Bookmark(key, value)); 40 } 41 42 static async flat() { 43 return (await this.all()).map((bookmark) => ( 44 bookmark.pages.map((page) => ( 45 { 46 bookmark: bookmark.key, 47 issueId: bookmark.issueId, 48 page: { label: page.label, index: page.index, title: page.title }, 49 } 50 )) 51 )).flat(); 52 } 53 54 static async totalPages() { 55 return Math.ceil((await this.flat()).length / this.PER_PAGE); 56 } 57 58 static async paginated(currentPage = 1) { 59 const start = (currentPage - 1) * this.PER_PAGE; 60 const end = currentPage * this.PER_PAGE; 61 62 return (await this.flat()).slice(start, end); 63 } 64 65 /** 66 * Toggles a bookmark. Returns `true` if it has been created, 67 * false if it has been deleted. 68 * @returns {Promise<boolean>} 69 */ 70 static async toggle(issueId, page) { 71 if (await this.isPresent(issueId, page)) { 72 await this.delete(issueId, page); 73 return false; 74 } 75 76 this.create(issueId, page); 77 return true; 78 } 79 80 static async find(keys) { 81 if (Array.isArray(keys)) { 82 return (await this.all()).filter((bookmark) => keys.includes(bookmark.key)); 83 } 84 85 const bookmark = await new Store(keys).get(); 86 87 if (!bookmark) return null; 88 89 return new Bookmark(keys, bookmark); 90 } 91 92 static saveAll(toSave) { 93 Store.withNamespace(this.namespace).setAll(toSave); 94 } 95 96 static create(issueId, page) { 97 if (!issueId) return; 98 if (!page || page === 'null') return; 99 100 Store 101 .withNamespace(this.namespace, issueId) 102 .concatUniq(page); 103 } 104 105 static async delete(issueId, page) { 106 if (!issueId) return; 107 if (!page || page === 'null') return; 108 109 const bookmark = await this.find(this.keyFor(issueId)); 110 111 if (!bookmark) return; 112 const toSave = bookmark.value 113 .filter((bookmarkPage) => Number(bookmarkPage.index) !== Number(page.index)); 114 115 Store.withNamespace(this.namespace, issueId).set(toSave); 116 } 117 118 delete(page) { 119 return Bookmark.delete(this.issueId, page); 120 } 121 122 static keyFor(issueId) { 123 return Store.withNamespace(this.namespace, issueId).key; 124 } 125 126 static async isPresent(issueId, page) { 127 if (!issueId) return false; 128 if (!page || page === 'null') return false; 129 130 return (await this.all()) 131 .filter((bookmark) => bookmark.key === this.keyFor(issueId)) 132 .some((bookmark) => bookmark.pages 133 .some((bookmarkPage) => Number(bookmarkPage.index) === Number(page.index))); 134 } 135} 136 137Bookmark.PER_PAGE = 28;
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.