1/** 2 * b247-user.js â share popups, save-article toggle, profile dropdown. 3 * Reads runtime config from window.b247User (printed inline by PHP). 4 */ 5(function () { 6 'use strict'; 7 8 var cfg = window.b247User || {}; 9 var i18n = cfg.i18n || {}; 10 var savedSet = new Set((cfg.savedIds || []).map(Number)); 11 var followedTerms = new Set((cfg.followedTerms || []).map(Number)); 12 var followedAuthors = new Set((cfg.followedAuthors || []).map(Number)); 13 14 // ââ Save toggle (REST) âââââââââââââââââââââââââââââââââââââââââââ 15 function rest(method, path, onDone, body) { 16 if (!cfg.restUrl) { onDone({ error: 'no_rest' }); return; } 17 var opts = { 18 method: method, 19 credentials: 'same-origin', 20 headers: { 'X-WP-Nonce': cfg.restNonce, 'Accept': 'application/json' } 21 }; 22 if (body) { 23 if (body instanceof FormData) { 24 opts.body = body; 25 } else { 26 opts.body = JSON.stringify(body); 27 opts.headers['Content-Type'] = 'application/json'; 28 } 29 } 30 fetch(cfg.restUrl + path.replace(/^\//, ''), opts) 31 .then(function (r) { return r.json().then(function (j) { return { status: r.status, body: j }; }); }) 32 .then(function (res) { onDone(res); }) 33 .catch(function () { onDone({ error: 'network' }); }); 34 } 35 36 function paintSaveBtn(btn, isSaved) { 37 btn.classList.toggle('is-saved', !!isSaved); 38 btn.setAttribute('aria-pressed', isSaved ? 'true' : 'false'); 39 btn.setAttribute('aria-label', isSaved ? 'Remover dos salvos' : 'Salvar artigo'); 40 var label = btn.querySelector('.b247-save-label'); 41 if (label) label.textContent = isSaved ? (i18n.saved || 'Salvo') : (i18n.save || 'Salvar'); 42 } 43 44 function handleSaveToggle(btn) { 45 var toolbar = btn.closest('.b247-post-toolbar'); 46 var postId = Number((toolbar && toolbar.getAttribute('data-post-id')) || btn.getAttribute('data-post-id') || 0); 47 if (!postId) return; 48 49 if (!cfg.isLoggedIn) { 50 redirectToLogin(); 51 return; 52 } 53 54 var currentlySaved = savedSet.has(postId); 55 var nextSaved = !currentlySaved; 56 // Optimistic UI 57 if (nextSaved) savedSet.add(postId); else savedSet.delete(postId); 58 document.querySelectorAll('[data-action="toggle-save"]').forEach(function (b) { 59 var bt = b.closest('.b247-post-toolbar'); 60 var bid = Number((bt && bt.getAttribute('data-post-id')) || b.getAttribute('data-post-id') || 0); 61 if (bid === postId) paintSaveBtn(b, nextSaved); 62 }); 63 btn.disabled = true; 64 65 rest(nextSaved ? 'POST' : 'DELETE', 'saved/' + postId, function (res) { 66 btn.disabled = false; 67 if (!res || (res.status && res.status >= 400)) { 68 // Roll back on error 69 if (nextSaved) savedSet.delete(postId); else savedSet.add(postId); 70 document.querySelectorAll('[data-action="toggle-save"]').forEach(function (b) { 71 var bt = b.closest('.b247-post-toolbar'); 72 var bid = Number((bt && bt.getAttribute('data-post-id')) || b.getAttribute('data-post-id') || 0); 73 if (bid === postId) paintSaveBtn(b, !nextSaved); 74 }); 75 return; 76 } 77 // On dashboard, remove the card we just unsaved 78 if (!nextSaved) { 79 var card = btn.closest('.b247-dashboard-card'); 80 if (card) card.remove(); 81 } 82 }); 83 } 84 85 // ââ Copy link âââââââââââââââââââââââââââââââââââââââââââââââââââââ 86 function handleCopyLink(btn) { 87 var toolbar = btn.closest('.b247-post-toolbar'); 88 bumpShareCount(toolbar); 89 var url = toolbar ? toolbar.getAttribute('data-post-url') : getCanonicalUrl(); 90 var done = function () { 91 var prev = btn.getAttribute('aria-label'); 92 btn.classList.add('is-copied'); 93 btn.setAttribute('aria-label', i18n.copied || 'Copiado!'); 94 setTimeout(function () { 95 btn.classList.remove('is-copied'); 96 btn.setAttribute('aria-label', prev || 'Copiar link'); 97 }, 1600); 98 }; 99 if (navigator.clipboard && navigator.clipboard.writeText) { 100 navigator.clipboard.writeText(url).then(done, function () {}); 101 } else { 102 var ta = document.createElement('textarea'); 103 ta.value = url; 104 document.body.appendChild(ta); 105 ta.select(); 106 try { document.execCommand('copy'); done(); } catch (e) {} 107 document.body.removeChild(ta); 108 } 109 } 110 111 // ââ Share buttons open in popup window for nicer UX ââââââââââââââ 112 function handleSharePopup(a) { 113 bumpShareCount(a.closest('.b247-post-toolbar')); 114 var url = a.getAttribute('href'); 115 if (!url) return; 116 var w = Math.min(640, window.innerWidth - 40);
117 var h = Math.min(560, window.innerHeight - 40); 118 var x = (window.outerWidth - w) / 2 + (window.screenX || 0); 119 var y = (window.outerHeight - h) / 2 + (window.screenY || 0); 120 window.open(url, 'b247share', 'width=' + w + ',height=' + h + ',left=' + x + ',top=' + y + ',noopener,noreferrer'); 121 } 122 123 // ââ Share counter: global per-post tally, this browser caps at 5 ââ 124 var SHARE_CAP = 5; 125 126 function formatInt(n) { 127 try { return Number(n).toLocaleString('pt-BR'); } catch (e) { return String(n); } 128 } 129 130 function paintShareCount(el, n) { 131 if (!el) return; 132 var num = el.querySelector('.b247-share-count-num'); 133 if (num) num.textContent = formatInt(n); 134 el.classList.toggle('is-empty', !(n > 0)); 135 } 136 137 // Count any share/copy click. Optimistically +1, then reconcile to the 138 // authoritative total the endpoint returns. localStorage caps this browser's 139 // contribution at 5 per post (server enforces the same cap per IP). 140 function bumpShareCount(toolbar) { 141 if (!toolbar) return; 142 var pid = Number(toolbar.getAttribute('data-post-id') || 0); 143 if (!pid) return; 144 145 var key = 'b247_sc_' + pid; 146 var mine = 0; 147 try { mine = parseInt(localStorage.getItem(key) || '0', 10) || 0; } catch (e) {} 148 if (mine >= SHARE_CAP) return; 149 150 var el = toolbar.querySelector('.b247-share-count'); 151 if (el) { 152 var num = el.querySelector('.b247-share-count-num'); 153 var current = num ? (parseInt(String(num.textContent).replace(/\D/g, ''), 10) || 0) : 0; 154 paintShareCount(el, current + 1); 155 } 156 try { localStorage.setItem(key, String(mine + 1)); } catch (e) {} 157 158 rest('POST', 'share/' + pid, function (res) { 159 if (el && res && res.body && typeof res.body.count !== 'undefined') { 160 paintShareCount(el, res.body.count); 161 } 162 }); 163 } 164 165 function safeUrl(raw, fallback) { 166 var defaultUrl = fallback === undefined ? '#' : fallback; 167 if (!raw) return defaultUrl; 168 try { 169 return new URL(String(raw), document.baseURI || '/').href; 170 } catch (e) { 171 return defaultUrl; 172 } 173 } 174 175 function getCanonicalUrl() { 176 var canonical = document.querySelector('link[rel="canonical"]'); 177 return canonical && canonical.href ? canonical.href : ''; 178 } 179 180 function redirectToLogin() { 181 window.open(safeUrl(cfg.loginUrl, '/wp-login.php'), '_self'); 182 } 183 184 function createEl(tag, className, text) { 185 var el = document.createElement(tag); 186 if (className) el.className = className; 187 if (text !== undefined && text !== null) el.textContent = String(text); 188 return el; 189 } 190 191 function createSvg(pathData) { 192 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 193 var path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 194 svg.setAttribute('viewBox', '0 0 24 24'); 195 svg.setAttribute('aria-hidden', 'true'); 196 svg.setAttribute('focusable', 'false'); 197 path.setAttribute('d', pathData); 198 svg.appendChild(path); 199 return svg; 200 } 201 202 function createCta(className, href, text) { 203 var link = createEl('a', className, text); 204 link.href = safeUrl(href, '#'); 205 return link; 206 } 207 208 function createSavedEmptyItem() { 209 return createEl('li', 'b247-profile-empty', i18n.noSaved || 'Nada salvo.'); 210 } 211 212 function createSavedItem(item) { 213 var li = document.createElement('li'); 214 var link = document.createElement('a'); 215 link.href = safeUrl(item && item.link, '#'); 216 if (item && item.image) { 217 var img = document.createElement('img'); 218 img.src = safeUrl(item.image, ''); 219 img.alt = ''; 220 img.loading = 'lazy'; 221 link.appendChild(img); 222 } else { 223 link.appendChild(createEl('span', 'b247-profile-thumb-empty')); 224 } 225 link.appendChild(createEl('span', '', (item && item.title) || '')); 226 li.appendChild(link); 227 return li; 228 } 229 230 // ââ Profile dropdown ââââââââââââââââââââââââââââââââââââââââââââââ 231 function buildProfilePanel(panel) { 232 var dashUrl = cfg.dashboardUrl || '/minha-conta/'; 233 panel.textContent = ''; 234 if (cfg.isLoggedIn) { 235 var feedUrl = cfg.customFeedUrl || (dashUrl + '#b247-d-para-voce'); 236 237 var head = createEl('header', 'b247-profile-head'); 238 if (cfg.avatarUrl) { 239 var avatar = document.createElement('img'); 240 avatar.className = 'b247-profile-avatar'; 241 avatar.src = safeUrl(cfg.avatarUrl, ''); 242 avatar.alt = ''; 243 head.appendChild(avatar); 244 } 245 var identity = document.createElement('div'); 246 identity.appendChild(createEl('strong', '', cfg.displayName || '')); 247 identity.appendChild(createCta('b247-profile-logout', cfg.logoutUrl, i18n.logout || 'Sair')); 248 head.appendChild(identity); 249 panel.appendChild(head); 250 251 var saved = createEl('div', 'b247-profile-saved'); 252 saved.appendChild(createEl('h4', '', i18n.saved || 'Salvos')); 253 var list = createEl('ul', 'b247-profile-saved-list'); 254 list.setAttribute('aria-live', 'polite'); 255 list.appendChild(createEl('li', 'b247-profile-loading', String.fromCharCode(0x2026))); 256 saved.appendChild(list); 257 panel.appendChild(saved); 258 259 var ctas = createEl('div', 'b247-profile-ctas'); 260 var feed = createCta('b247-profile-cta b247-profile-cta--feed', feedUrl, ''); 261 feed.appendChild(createSvg('M6 2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6H6zm7 1.5L18.5 9H14a1 1 0 0 1-1-1V3.5zM8 13h8v2H8v-2zm0 4h5v2H8v-2z')); 262 feed.appendChild(createEl('span', '', i18n.customFeed || 'Meu feed')); 263 ctas.appendChild(feed); 264 ctas.appendChild(createCta('b247-profile-cta', dashUrl, (i18n.viewAll || 'Ver dashboard completo') + ' ' + String.fromCharCode(0x203A))); 265 panel.appendChild(ctas); 266 loadSavedList(list); 267 } else { 268 var guest = createEl('header', 'b247-profile-head b247-profile-head--guest'); 269 guest.appendChild(createSvg('M12 12c2.7 0 4.9-2.2 4.9-4.9S14.7 2.2 12 2.2 7.1 4.4 7.1 7.1 9.3 12 12 12zm0 2.4c-3.3 0-9.9 1.6-9.9 4.9v2.4h19.8v-2.4c0-3.3-6.6-4.9-9.9-4.9z')); 270 guest.appendChild(createEl('p', '', i18n.loginNeeded || 'Faça login para salvar artigos.')); 271 panel.appendChild(guest); 272 panel.appendChild(createCta('b247-profile-cta b247-profile-cta--primary', cfg.loginUrl, i18n.login || 'Entrar')); 273 if (cfg.registerUrl) { 274 panel.appendChild(createCta('b247-profile-cta', cfg.registerUrl, i18n.register || 'Criar conta')); 275 } 276 } 277 } 278 279 function loadSavedList(ul) { 280 if (!ul) return; 281 rest('GET', 'saved', function (res) { 282 ul.textContent = ''; 283 if (!res || !res.body || !res.body.items) { 284 ul.appendChild(createSavedEmptyItem()); 285 return; 286 } 287 var items = res.body.items.slice(0, 5); 288 if (items.length === 0) { 289 ul.appendChild(createSavedEmptyItem()); 290 return; 291 }
292 items.forEach(function (it) { 293 ul.appendChild(createSavedItem(it)); 294 }); 295 }); 296 } 297 298 function setupProfileDropdown() { 299 var btn = document.getElementById('b247-profile-btn'); 300 var panel = document.getElementById('b247-profile-panel'); 301 if (!btn || !panel) return; 302 303 var built = false; 304 function open() { 305 if (!built) { buildProfilePanel(panel); built = true; } 306 panel.hidden = false; 307 btn.setAttribute('aria-expanded', 'true'); 308 document.addEventListener('click', onDocClick, true); 309 document.addEventListener('keydown', onEsc, true); 310 } 311 function close() { 312 panel.hidden = true; 313 btn.setAttribute('aria-expanded', 'false'); 314 document.removeEventListener('click', onDocClick, true); 315 document.removeEventListener('keydown', onEsc, true); 316 } 317 function onDocClick(e) { 318 if (panel.contains(e.target) || btn.contains(e.target)) return; 319 close(); 320 } 321 function onEsc(e) { if (e.key === 'Escape') close(); } 322 323 btn.addEventListener('click', function () { 324 if (panel.hidden) open(); else close(); 325 }); 326 } 327 328 // ââ Follow / unfollow buttons (term + author) âââââââââââââââââââââ 329 function paintFollowBtn(btn, isFollowing) { 330 btn.classList.toggle('is-following', !!isFollowing); 331 btn.setAttribute('aria-pressed', isFollowing ? 'true' : 'false'); 332 var label = btn.querySelector('.b247-follow-label'); 333 var icon = btn.querySelector('.b247-follow-icon'); 334 if (label) { 335 var base = label.getAttribute('data-base-label') || label.textContent; 336 if (!label.getAttribute('data-base-label')) { label.setAttribute('data-base-label', base); } 337 label.textContent = isFollowing ? (i18n.following || 'Seguindo') : base; 338 } 339 if (icon) icon.textContent = isFollowing ? 'â' : '+'; 340 } 341 342 function handleFollow(btn) { 343 var type = btn.getAttribute('data-follow-type'); 344 var id = Number(btn.getAttribute('data-follow-id') || 0); 345 if (!id || (type !== 'term' && type !== 'author')) return; 346 347 if (!cfg.isLoggedIn) { 348 redirectToLogin(); 349 return; 350 } 351 352 var set = type === 'term' ? followedTerms : followedAuthors; 353 var nowFollowing = !set.has(id); 354 if (nowFollowing) set.add(id); else set.delete(id); 355 document.querySelectorAll('.b247-follow-btn[data-follow-type="' + type + '"][data-follow-id="' + id + '"]').forEach(function (b) { 356 paintFollowBtn(b, nowFollowing); 357 }); 358 btn.disabled = true; 359 rest(nowFollowing ? 'POST' : 'DELETE', 'follow/' + type + '/' + id, function (res) { 360 btn.disabled = false; 361 if (!res || (res.status && res.status >= 400)) { 362 // roll back 363 if (nowFollowing) set.delete(id); else set.add(id); 364 document.querySelectorAll('.b247-follow-btn[data-follow-type="' + type + '"][data-follow-id="' + id + '"]').forEach(function (b) { 365 paintFollowBtn(b, !nowFollowing); 366 }); 367 } 368 }); 369 } 370 371 function handleUnfollowChip(btn) { 372 var type = btn.getAttribute('data-follow-type'); 373 var id = Number(btn.getAttribute('data-follow-id') || 0); 374 if (!id || (type !== 'term' && type !== 'author')) return; 375 var set = type === 'term' ? followedTerms : followedAuthors; 376 set.delete(id); 377 var li = btn.closest('li'); 378 if (li) li.remove(); 379 rest('DELETE', 'follow/' + type + '/' + id, function () {}); 380 } 381 382 // ââ History clear ââââââââââââââââââââââââââââââââââââââââââââââââ 383 function handleClearHistory(btn) { 384 if (!window.confirm('Limpar todo o seu histórico de leitura?')) return; 385 btn.disabled = true; 386 rest('DELETE', 'history', function (res) { 387 btn.disabled = false; 388 if (res && res.body && res.body.ok) { 389 var sec = document.getElementById('b247-d-historico'); 390 if (sec) { 391 var list = sec.querySelector('.b247-dashboard-list'); 392 if (list) list.remove(); 393 var head = sec.querySelector('.b247-dashboard-section-head .b247-link-btn'); 394 if (head) head.remove(); 395 var empty = document.createElement('p'); 396 empty.className = 'b247-dashboard-empty'; 397 empty.textContent = 'Os últimos artigos que você leu aparecerão aqui.'; 398 sec.appendChild(empty); 399 } 400 } 401 }); 402 } 403 404 // ââ Comments filter tabs âââââââââââââââââââââââââââââââââââââââââ 405 function handleCommentFilter(btn) { 406 var tabs = btn.parentElement.querySelectorAll('.b247-filter-tab');
407 tabs.forEach(function (t) { t.classList.remove('is-active'); }); 408 btn.classList.add('is-active'); 409 var status = btn.getAttribute('data-filter-status'); 410 var section = btn.closest('section'); 411 if (!section) return; 412 section.querySelectorAll('[data-comments-status]').forEach(function (ul) { 413 ul.hidden = ul.getAttribute('data-comments-status') !== status; 414 }); 415 } 416 417 // ââ Dashboard forms (profile / password / privacy) âââââââââââââââ 418 function setFormStatus(form, message, isError) { 419 var el = form.querySelector('.b247-form-status'); 420 if (!el) return; 421 el.textContent = message || ''; 422 el.classList.toggle('is-error', !!isError); 423 el.classList.toggle('is-success', !!message && !isError); 424 } 425 426 function handleFormSubmit(form) { 427 var kind = form.getAttribute('data-form'); 428 setFormStatus(form, '', false); 429 430 if (kind === 'profile') { 431 var fd = new FormData(form); 432 form.querySelector('button[type="submit"]').disabled = true; 433 rest('POST', 'profile', function (res) { 434 form.querySelector('button[type="submit"]').disabled = false; 435 if (res && res.body && res.body.ok) { 436 setFormStatus(form, i18n.saved_ok || 'Salvo!', false); 437 if (res.body.avatar_url) { 438 document.querySelectorAll('.b247-dashboard-avatar').forEach(function (img) { img.src = res.body.avatar_url; }); 439 } 440 } else { 441 var msg = (res && res.body && res.body.error) || (i18n.error || 'Erro'); 442 setFormStatus(form, msg, true); 443 } 444 }, fd); 445 return; 446 } 447 448 if (kind === 'password') { 449 var pwNew = form.querySelector('[name="new_password"]').value; 450 var pwConfirm = form.querySelector('[name="new_password_confirm"]').value; 451 if (pwNew !== pwConfirm) { 452 setFormStatus(form, 'As senhas não coincidem.', true); 453 return; 454 } 455 form.querySelector('button[type="submit"]').disabled = true; 456 rest('POST', 'password', function (res) { 457 form.querySelector('button[type="submit"]').disabled = false; 458 if (res && res.body && res.body.ok) { 459 form.reset(); 460 setFormStatus(form, 'Senha atualizada.', false); 461 } else { 462 var err = res && res.body && res.body.error; 463 var msg = err === 'wrong_current' ? 'Senha atual incorreta.' 464 : err === 'weak_password' ? 'A nova senha precisa ter pelo menos 8 caracteres.' 465 : (i18n.error || 'Erro'); 466 setFormStatus(form, msg, true); 467 } 468 }, { 469 current: form.querySelector('[name="current"]').value, 470 new_password: pwNew 471 }); 472 return; 473 } 474 475 if (kind === 'privacy') { 476 var body = { 477 analytics: form.querySelector('[name="analytics"]').checked ? 1 : 0, 478 marketing: form.querySelector('[name="marketing"]').checked ? 1 : 0, 479 lgpd_ack: form.querySelector('[name="lgpd_ack"]').checked ? 1 : 0 480 }; 481 form.querySelector('button[type="submit"]').disabled = true; 482 rest('POST', 'privacy', function (res) { 483 form.querySelector('button[type="submit"]').disabled = false; 484 if (res && res.body && res.body.ok) { 485 setFormStatus(form, 'Preferências salvas.', false); 486 } else { 487 setFormStatus(form, i18n.error || 'Erro', true); 488 } 489 }, body); 490 return; 491 } 492 } 493 494 // ââ Card card actions menu (3-dot) ââââââââââââââââââââââââââââââââââââââ 495 var openCardActions = null; 496 function closeCardActions() { 497 if (!openCardActions) return; 498 var menu = openCardActions.querySelector('.b247-card-actions-menu'); 499 var btn = openCardActions.querySelector('.b247-card-actions-toggle'); 500 if (menu) menu.hidden = true; 501 if (btn) btn.setAttribute('aria-expanded', 'false'); 502 openCardActions.classList.remove('is-open'); 503 openCardActions = null; 504 document.removeEventListener('click', onCardActionsDocClick, true); 505 document.removeEventListener('keydown', onCardActionsEsc, true); 506 } 507 function onCardActionsDocClick(e) { 508 if (openCardActions && !openCardActions.contains(e.target)) closeCardActions(); 509 } 510 function onCardActionsEsc(e) { if (e.key === 'Escape') closeCardActions(); } 511 512 function toggleCardActions(btn) { 513 var wrap = btn.closest('.b247-card-actions'); 514 if (!wrap) return; 515 if (openCardActions && openCardActions !== wrap) closeCardActions(); 516 var menu = wrap.querySelector('.b247-card-actions-menu'); 517 var isOpen = !menu.hidden; 518 menu.hidden = isOpen; 519 btn.setAttribute('aria-expanded', isOpen ? 'false' : 'true'); 520 wrap.classList.toggle('is-open', !isOpen); 521 if (!isOpen) { 522 openCardActions = wrap; 523 document.addEventListener('click', onCardActionsDocClick, true); 524 document.addEventListener('keydown', onCardActionsEsc, true); 525 } else { 526 openCardActions = null; 527 document.removeEventListener('click', onCardActionsDocClick, true); 528 document.removeEventListener('keydown', onCardActionsEsc, true); 529 } 530 } 531 532 function handleCardActionsItem(item) { 533 var wrap = item.closest('.b247-card-actions'); 534 if (!wrap) return; 535 var action = item.getAttribute('data-action'); 536 var postId = Number(wrap.getAttribute('data-post-id') || 0); 537 var url = wrap.getAttribute('data-post-url') || ''; 538 var authorId = Number(wrap.getAttribute('data-author-id') || 0); 539 540 if (action === 'card-action-save') { 541 if (!cfg.isLoggedIn) { redirectToLogin(); return; } 542 var nowSaved = !savedSet.has(postId); 543 if (nowSaved) savedSet.add(postId); else savedSet.delete(postId); 544 rest(nowSaved ? 'POST' : 'DELETE', 'saved/' + postId, function () {}); 545 // Visual confirmation in the menu item 546 var sp = item.querySelector('span'); 547 if (sp) sp.textContent = nowSaved ? (i18n.saved_ok || 'Salvo!') : 'Salvar para ler depois'; 548 setTimeout(closeCardActions, 700); 549 return; 550 } 551 if (action === 'card-action-share-wa' || action === 'card-action-share-x') { 552 var href = item.getAttribute('data-href'); 553 if (href) { 554 var w = Math.min(640, window.innerWidth - 40); 555 var h = Math.min(560, window.innerHeight - 40); 556 window.open(href, 'b247share', 'width=' + w + ',height=' + h + ',noopener,noreferrer'); 557 } 558 closeCardActions(); 559 return; 560 } 561 if (action === 'card-action-copy') { 562 if (navigator.clipboard && navigator.clipboard.writeText) { 563 navigator.clipboard.writeText(url).then(function () { 564 var sp = item.querySelector('span'); 565 if (sp) sp.textContent = i18n.copied || 'Copiado!'; 566 setTimeout(closeCardActions, 700); 567 }); 568 } else { closeCardActions(); } 569 return; 570 } 571 if (action === 'card-action-follow-author') { 572 if (!cfg.isLoggedIn) { redirectToLogin(); return; } 573 var aid = Number(item.getAttribute('data-follow-id') || authorId);
574 var nowFollowing = !followedAuthors.has(aid); 575 if (nowFollowing) followedAuthors.add(aid); else followedAuthors.delete(aid); 576 rest(nowFollowing ? 'POST' : 'DELETE', 'follow/author/' + aid, function () {}); 577 var sp2 = item.querySelector('span'); 578 if (sp2) sp2.textContent = nowFollowing ? (i18n.following || 'Seguindo') : 'Seguir autor'; 579 setTimeout(closeCardActions, 700); 580 return; 581 } 582 } 583 584 // ââ Wire everything up on DOMContentLoaded ââââââââââââââââââââââââ 585 function init() { 586 document.addEventListener('click', function (e) { 587 var saveBtn = e.target.closest && e.target.closest('[data-action="toggle-save"]'); 588 if (saveBtn) { e.preventDefault(); handleSaveToggle(saveBtn); return; } 589 590 var copyBtn = e.target.closest && e.target.closest('[data-action="copy-link"]'); 591 if (copyBtn) { e.preventDefault(); handleCopyLink(copyBtn); return; } 592 593 var share = e.target.closest && e.target.closest('.b247-share-btn[href]'); 594 if (share) { 595 e.preventDefault(); 596 handleSharePopup(share); 597 return; 598 } 599 600 // Only intercept real follow toggles (author/category). The follow 601 // cluster reuses .b247-follow-btn for the Apoie, Newsletter and 602 // Google News links too â those need their default click behaviour. 603 var followBtn = e.target.closest && e.target.closest('.b247-follow-btn[data-follow-type]'); 604 if (followBtn) { e.preventDefault(); handleFollow(followBtn); return; } 605 606 var chipX = e.target.closest && e.target.closest('.b247-chip-x[data-action="unfollow"]'); 607 if (chipX) { e.preventDefault(); handleUnfollowChip(chipX); return; } 608 609 var clearHist = e.target.closest && e.target.closest('[data-action="clear-history"]'); 610 if (clearHist) { e.preventDefault(); handleClearHistory(clearHist); return; } 611 612 var filterTab = e.target.closest && e.target.closest('.b247-filter-tab'); 613 if (filterTab) { e.preventDefault(); handleCommentFilter(filterTab); return; } 614 615 var actionsBtn = e.target.closest && e.target.closest('.b247-card-actions-toggle'); 616 if (actionsBtn) { e.preventDefault(); e.stopPropagation(); toggleCardActions(actionsBtn); return; } 617 618 var actionsItem = e.target.closest && e.target.closest('.b247-card-actions-item'); 619 if (actionsItem) { e.preventDefault(); handleCardActionsItem(actionsItem); return; } 620 }); 621 622 document.addEventListener('submit', function (e) { 623 var form = e.target.closest && e.target.closest('.b247-dashboard-form'); 624 if (!form) return; 625 e.preventDefault(); 626 handleFormSubmit(form); 627 }); 628 629 setupProfileDropdown(); 630 631 // Painting the initial follow / save indicators is non-critical â the 632 // delegated click/submit handlers above already carry every interaction. 633 // On a listing with ~30 cards these two sweeps are 60-120 synchronous DOM 634 // mutations; running them at idle keeps them out of the load / first-tap 635 // INP window. The state simply appears a few hundred ms later. 636 var paintInitialStates = function () { 637 // Follow-button states based on cfg. 638 document.querySelectorAll('.b247-follow-btn').forEach(function (btn) { 639 var type = btn.getAttribute('data-follow-type'); 640 var id = Number(btn.getAttribute('data-follow-id') || 0); 641 var set = type === 'term' ? followedTerms : (type === 'author' ? followedAuthors : null); 642 if (set) paintFollowBtn(btn, set.has(id)); 643 }); 644 // Toolbar save buttons whose state may be stale (e.g., page from cache). 645 document.querySelectorAll('.b247-post-toolbar').forEach(function (tb) { 646 var pid = Number(tb.getAttribute('data-post-id') || 0); 647 var btn = tb.querySelector('[data-action="toggle-save"]'); 648 if (btn) paintSaveBtn(btn, savedSet.has(pid)); 649 }); 650 }; 651 if ('requestIdleCallback' in window) { 652 requestIdleCallback(paintInitialStates, { timeout: 2000 }); 653 } else { 654 setTimeout(paintInitialStates, 200); 655 } 656 } 657 658 if (document.readyState === 'loading') { 659 document.addEventListener('DOMContentLoaded', init); 660 } else { 661 init(); 662 } 663}());
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.