PageSourceSearch

https://shelfbooksapp.netlify.app/js/reader-profile.js

js shelfbooksapp.netlify.app collected 2026-10-03 11:23:38 UTC 7,713 bytes, 229 lines download raw bytes

1window.ReaderProfileUI = {
2  escapeHtml: function (text) {
3    return String(text)
4      .replace(/&/g, "&")
5      .replace(/</g, "&lt;")
6      .replace(/>/g, "&gt;")
7      .replace(/"/g, "&quot;");
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.