1window.ReaderProfileUI = { 2 escapeHtml: function (text) { 3 return String(text) 4 .replace(/&/g, "&") 5 .replace(/</g, "<") 6 .replace(/>/g, ">") 7 .replace(/"/g, """); 8 }, 9 10 renderSummary: function (summary, options) { 11 options = options || {}; 12 if (!summary) return ""; 13 14 var rerollId = options.rerollId || "rerollReaderProfile"; 15 var showReroll = options.showReroll !== false; 16 var compact = options.compact; 17 18 var traitsHtml = (summary.traits || []) 19 .map(function (trait) { 20 return '<li class="profile-trait">' + trait + "</li>"; 21 }) 22 .join(""); 23 24 var extra = ""; 25 if (summary.topAuthors) { 26 extra += 27 '<p class="profile-summary__authors"><strong>Patron saints of your shelf:</strong> ' + 28 summary.topAuthors.join(", ") + 29 "</p>"; 30 } 31 if (summary.favorite) { 32 extra += '<p class="profile-summary__authors">' + summary.favorite + "</p>"; 33 } 34 35 var rerollHtml = showReroll 36 ? '<button type="button" class="btn btn--primary btn--sm profile-summary__reroll" id="' + 37 rerollId + 38 '" aria-label="Read me again â shuffle your reader profile">' + 39 "Read me again" + 40 '<span class="profile-summary__reroll-icons" aria-hidden="true">' + 41 '<span class="profile-summary__reroll-book">ð</span>' + 42 '<span class="profile-summary__reroll-refresh">â»</span>' + 43 "</span></button>" 44 : ""; 45 46 return ( 47 '<div class="profile-summary profile-summary--rich' + 48 (compact ? " profile-summary--compact" : "") + 49 '">' + 50 '<div class="profile-summary__top">' + 51 '<p class="profile-summary__badge">' + 52 (summary.source === "libby" 53 ? "Your reading horoscope · Libby" 54 : "Your reading horoscope · Survey") + 55 "</p>" + 56 (summary.cosmicLabel 57 ? '<p class="profile-summary__cosmic">' + summary.cosmicLabel + "</p>" 58 : "") + 59 "</div>" + 60 '<h3 class="profile-summary__type">' + 61 summary.readerType + 62 "</h3>" + 63 '<p class="profile-summary__headline">' + 64 summary.headline + 65 "</p>" + 66 (summary.blurb 67 ? '<p class="profile-summary__blurb">' + summary.blurb + "</p>" 68 : "") + 69 '<ul class="profile-summary__traits">' + 70 traitsHtml + 71 "</ul>" + 72 extra + 73 rerollHtml + 74 "</div>" 75 ); 76 }, 77 78 bindSummaryActions: function (root, onReroll) { 79 if (!root) return; 80 var rerollBtn = root.querySelector(".profile-summary__reroll"); 81 if (rerollBtn && onReroll) { 82 rerollBtn.addEventListener("click", function () { 83 if (window.UserState) window.UserState.rerollReaderArchetype(); 84 onReroll(); 85 }); 86 } 87 }, 88 89 renderLogoutFooter: function () { 90 return ( 91 '<div class="profile-popover__footer">' + 92 '<button type="button" class="btn btn--ghost btn--sm profile-popover__logout" data-profile-logout>Log out</button>' + 93 "</div>" 94 ); 95 }, 96 97 bindLogoutAction: function (root) { 98 if (!root) return; 99 var btn = root.querySelector("[data-profile-logout]"); 100 if (!btn) return; 101 btn.addEventListener("click", function () { 102 if (window.Auth) window.Auth.logout(); 103 window.location.href = "/"; 104 }); 105 }, 106 107 bindConnectActions: function (root) { 108 if (!root) return; 109 110 var connectBtn = root.querySelector("[data-profile-connect]"); 111 if (connectBtn) { 112 connectBtn.addEventListener("click", function () { 113 window.location.href = "/libby-connect"; 114 }); 115 } 116 117 var surveyBtn = root.querySelector("[data-profile-survey]"); 118 if (surveyBtn) { 119 surveyBtn.addEventListener("click", function () { 120 window.location.href = "/survey?from=libby"; 121 }); 122 } 123 124 var disconnectBtn = root.querySelector("[data-profile-disconnect]"); 125 if (disconnectBtn) { 126 disconnectBtn.addEventListener("click", function () { 127 if (window.Auth && window.Auth.disconnectLibby) { 128 window.Auth.disconnectLibby(); 129 } 130 window.location.reload(); 131 }); 132 } 133 }, 134 135 renderNavPanel: function () { 136 var state = window.UserState; 137 var data = window.MOCK_DATA || {}; 138 139 if (!state || !window.Auth || !window.Auth.isLoggedIn()) { 140 return ""; 141 } 142 143 var username = state.getUsername(); 144 var initials = state.getAvatarInitials(username); 145 var libbyStatus = state.getLibbyStatus(); 146 var summary = state.getProfileSummary(); 147 var profile = data.readerProfile || {}; 148 149 if (libbyStatus === state.LIBBY.SYNCING) { 150 return ( 151 '<div class="profile-popover">' + 152 '<p class="profile-popover__eyebrow">Reader profile</p>' + 153 '<div class="flow-loading profile-popover__loading">' + 154 '<div class="flow-loading__spinner"></div>' + 155 "<p>Finishing Libby connectionâ¦</p>" + 156 '<a href="/libby-connect" class="btn btn--ghost btn--sm">
156Return to sync</a>' + 157 "</div>" + 158 this.renderLogoutFooter() + 159 "</div>" 160 ); 161 } 162 163 if (!summary) { 164 return ( 165 '<div class="profile-popover">' + 166 '<p class="profile-popover__eyebrow">Reader profile</p>' + 167 '<div class="profile-popover__header">' + 168 '<span class="user-avatar user-avatar--nav">' + 169 initials + 170 "</span>" + 171 '<div class="profile-popover__who">' + 172 "<p class=\"profile-popover__name\">Welcome, " + 173 this.escapeHtml(username) + 174 "</p>" + 175 '<p class="profile-popover__meta">Connect Libby or complete the book-preferences slip to unlock your profile.</p>' + 176 "</div></div>" + 177 '<div class="profile-popover__actions">' + 178 '<button type="button" class="btn btn--primary btn--sm" data-profile-connect>Connect Libby</button>' + 179 '<button type="button" class="btn btn--ghost btn--sm" data-profile-survey>Libby sync unavailable</button>' + 180 "</div>" + 181 this.renderLogoutFooter() + 182 "</div>" 183 ); 184 } 185 186 var statusHtml = state.isLibbyConnected() 187 ? '<span class="status-badge status-badge--connected">Library connected</span>' 188 : '<a href="/libby-connect" class="btn btn--ghost btn--sm">Connect Libby</a>'; 189 190 var disconnectHtml = state.isLibbyConnected() 191 ? '<button type="button" class="btn btn--ghost btn--sm" data-profile-disconnect>Disconnect</button>' 192 : '<a href="/survey" class="btn btn--ghost btn--sm">Retake survey</a>'; 193 194 return ( 195 '<div class="profile-popover">' + 196 '<p class="profile-popover__eyebrow">Reader profile</p>' + 197 '<div class="profile-popover__header">' + 198 '<span class="user-avatar user-avatar--nav user-avatar--own">' + 199 initials + 200 "</span>" + 201 '<div class="profile-popover__who">' + 202 '<p class="profile-popover__name">' + 203 this.escapeHtml(username) + 204 "</p>" + 205 '<p class="profile-popover__meta">' + 206 this.escapeHtml(profile.library || "Your library") + 207 " · member since " + 208 this.escapeHtml(profile.memberSince || "") + 209 "</p>" + 210 "</div></div>" + 211 '<p class="profile-popover__stats">' + 212 (profile.booksRead != null ? profile.booksRead : "â") + 213 " books read · " + 214 (profile.holdsActive != null ? profile.holdsActive : "â") + 215 " on shelf</p>" + 216 this.renderSummary(summary, { 217 rerollId: "navRerollReaderProfile", 218 compact: true, 219 }) + 220 '<div class="profile-popover__actions">' + 221 statusHtml + 222 disconnectHtml + 223 "</div>" + 224 '<a href="/dashboard#sectionReaderProfile" class="profile-popover__dashboard">Open full profile on dashboard â</a>' + 225 this.renderLogoutFooter() + 226 "</div>" 227 ); 228 }, 229};
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.