);
}
function setButtonState(button, state) {
const hint = button.parentElement && button.parentElement.querySelector(‘[data-author-follow-hint]’);
const followLabel = button.dataset.followLabel || ‘Follow’;
const followingLabel = button.dataset.followingLabel || ‘Following’;
const signinLabel = button.dataset.signinLabel || ‘Sign in to follow’;
const loadingLabel = button.dataset.loadingLabel || ‘Saving…’;
const pendingAuthLabel = button.dataset.pendingAuthLabel || ‘Loading…’;
button.classList.remove(‘is-following’, ‘is-loading’, ‘is-auth-required’);
if (state === ‘loading’) {
button.classList.add(‘is-loading’);
button.disabled = true;
button.textContent = loadingLabel;
if (hint) {
hint.textContent=”Updating your followed authors…”;
}
return;
}
if (state === ‘pending-auth’) {
button.classList.add(‘is-loading’);
button.disabled = true;
button.textContent = pendingAuthLabel;
if (hint) {
hint.textContent=”Checking your sign-in status…”;
}
return;
}
button.disabled = false;
if (state === ‘following’) {
button.classList.add(‘is-following’);
button.textContent = followingLabel;
if (hint) {
hint.textContent = followingLabel === ‘Unfollow’
? ‘Remove this writer from your followed list.’
: ‘Click again to unfollow.’;
}
return;
}
if (state === ‘signin’) {
button.classList.add(‘is-auth-required’);
button.textContent = signinLabel;
if (hint) {
hint.textContent=”Sign in to follow writers and manage your list.”;
}
return;
}
button.textContent = followLabel;
if (hint) {
hint.textContent=”Click to follow.”;
}
}
function renderWidgets(scope) {
const root = scope || document;
root.querySelectorAll(‘[data-author-follow-widget]’).forEach(function (widget) {
const button = widget.querySelector(‘[data-author-follow-button]’);
if (!button) {
return;
}
const staffId = widget.dataset.staffId;
const hasToken = !!getBearerToken();
if (!isPianoIdentityReady() && !hasToken) {
setButtonState(button, ‘pending-auth’);
return;
}
if (!hasToken) {
setButtonState(button, ‘signin’);
return;
}
if (followedAuthorsMap.has(String(staffId))) {
setButtonState(button, ‘following’);
} else {
setButtonState(button, ‘follow’);
}
});
}
function renderManageList(container) {
if (!container) {
return;
}
const hasToken = !!getBearerToken();
if (!isPianoIdentityReady() && !hasToken) {
container.innerHTML = (
‘
‘
);
return;
}
if (!hasToken) {
container.innerHTML = (
‘
‘
);
return;
}
const followedAuthors = Array.from(followedAuthorsMap.values());
if (!followedAuthors.length) {
container.innerHTML = (
‘
‘
);
return;
}
container.innerHTML = followedAuthors
.map(function (author) {
return (
‘
‘
);
})
.join(”);
bindEvents(container);
renderWidgets(container);
}
async function refresh(scope, options) {
const forceFetch = !!(options && options.force);
if (!hasFollowWidgets(scope)) {
return;
}
// Wipe the cached map BEFORE the first render so a user-switch can’t
// momentarily display the previous user’s “Following” state.
discardCacheIfTokenChanged();
renderWidgets(scope);
const manageContainer = document.querySelector(‘[data-author-follow-manage-list]’);
const managePreferencesContainer = document.querySelector(‘[data-author-follow-manage-preferences]’);
const hasToken = !!getBearerToken();
if (!isPianoIdentityReady() && !hasToken) {
renderWidgets(scope);
renderManagePreferences(managePreferencesContainer);
renderManageList(manageContainer);
return;
}
if (!hasToken) {
renderManagePreferences(managePreferencesContainer);
renderManageList(manageContainer);
return;
}
try {
await loadFollowedAuthors(forceFetch);
renderWidgets(scope);
renderManagePreferences(managePreferencesContainer);
renderManageList(manageContainer);
} catch (error) {
console.warn(‘Unable to load followed authors.’, error);
if (error && error.code === ‘AUTH_REQUIRED’) {
renderWidgets(scope);
renderManagePreferences(managePreferencesContainer);
renderManageList(manageContainer);
}
}
}
function startLoginPoll() {
if (loginPoll) {
clearInterval(loginPoll);
}
let attempts = 0;
loginPoll = setInterval(function () {
attempts += 1;
if (getBearerToken() || isLoggedIn() || attempts > 25) {
clearInterval(loginPoll);
loginPoll = null;
refresh(document, { force: true });
}
}, 1000);
}
function stopPianoReadyPoll() {
if (pianoReadyPoll) {
clearInterval(pianoReadyPoll);
pianoReadyPoll = null;
}
}
function startPianoReadyPoll() {
stopPianoReadyPoll();
let attempts = 0;
pianoReadyPoll = setInterval(function () {
attempts += 1;
if (isPianoIdentityReady() || getBearerToken() || attempts > 40) {
stopPianoReadyPoll();
// Non-forced: if bindPianoInitRefresh already fetched, the TTL
// dedupes this call. If it lost the race, this call wins.
refresh(document);
}
}, 500);
}
function bindPianoInitRefresh() {
if (pianoInitBound) {
return;
}
pianoInitBound = whenPianoReady(function () {
// The Piano init callback is the canonical “identity is ready” signal;
// cancel the fallback poll so it can’t issue a redundant second fetch.
stopPianoReadyPoll();
refresh(document, { force: true });
});
}
async function handleWidgetClick(widget, button) {
const staffId = widget.dataset.staffId;
if (!getBearerToken()) {
setButtonState(button, isPianoIdentityReady() ? ‘signin’ : ‘pending-auth’);
if (typeof window.showPianoLogin === ‘function’) {
window.showPianoLogin();
startLoginPoll();
}
return;
}
// If the identity changed since the cache was last filled, drop the stale
// map before the follow/unfollow decision below — otherwise the click
// could issue the wrong action against the previous user’s state.
discardCacheIfTokenChanged();
setButtonState(button, ‘loading’);
try {
if (followedAuthorsMap.has(String(staffId))) {
await apiRequest(apiBaseUrl + “https://www.washingtontimes.com/” + staffId, { method: ‘DELETE’ });
followedAuthorsMap.delete(String(staffId));
removePushlyFollowedAuthorId(staffId);
} else {
const author = await apiRequest(apiBaseUrl + “https://www.washingtontimes.com/” + staffId, {
method: ‘PUT’,
body: JSON.stringify({
push_enabled: followDeliveryPreferences.push_enabled,
email_fallback_enabled: followDeliveryPreferences.email_fallback_enabled,
}),
});
followedAuthorsMap.set(String(staffId), author);
appendPushlyFollowedAuthorId(staffId);
}
saveStoredFollowDeliveryPreferences(
deriveFollowDeliveryPreferences(Array.from(followedAuthorsMap.values())),
);
syncPushlyFollowedAuthorsProfile();
renderManagePreferences(document.querySelector(‘[data-author-follow-manage-preferences]’));
renderWidgets(document);
renderManageList(document.querySelector(‘[data-author-follow-manage-list]’));
} catch (error) {
console.warn(‘Unable to update followed author.’, error);
if (error && error.code === ‘AUTH_REQUIRED’) {
setButtonState(button, ‘signin’);
}
renderWidgets(document);
}
}
function bindEvents(scope) {
const root = scope || document;
root.querySelectorAll(‘[data-author-follow-widget]’).forEach(function (widget) {
if (widget.dataset.followWidgetBound === ‘true’) {
return;
}
widget.dataset.followWidgetBound = ‘true’;
const button = widget.querySelector(‘[data-author-follow-button]’);
if (!button) {
return;
}
button.addEventListener(‘click’, function () {
handleWidgetClick(widget, button);
});
});
root.querySelectorAll(‘[data-author-follow-manage-preferences]’).forEach(function (container) {
if (container.dataset.followGlobalPreferencesBound === ‘true’) {
return;
}
container.dataset.followGlobalPreferencesBound = ‘true’;
async function handlePreferenceChange() {
const pushInput = container.querySelector(‘[data-author-follow-global-pref=”push”]’);
const emailInput = container.querySelector(‘[data-author-follow-global-pref=”email”]’);
if (!pushInput || !emailInput) {
return;
}
const previousPreferences = {
push_enabled: followDeliveryPreferences.push_enabled,
email_fallback_enabled: followDeliveryPreferences.email_fallback_enabled,
};
const payload = {
push_enabled: !!pushInput.checked,
email_fallback_enabled: !!emailInput.checked,
};
// Drop a stale map if the identity changed before iterating — we
// don’t want to issue follow updates against the previous user’s
// authors with the current user’s bearer token.
discardCacheIfTokenChanged();
const followedAuthors = Array.from(followedAuthorsMap.values());
saveStoredFollowDeliveryPreferences(payload);
managePreferencesPending = true;
pushInput.disabled = true;
emailInput.disabled = true;
try {
await Promise.all(
followedAuthors.map(function (author) {
return updateFollowPreferences(author.id, payload);
}),
);
renderManagePreferences(container);
renderWidgets(document);
renderManageList(document.querySelector(‘[data-author-follow-manage-list]’));
} catch (error) {
console.warn(‘Unable to update author follow preferences.’, error);
saveStoredFollowDeliveryPreferences(previousPreferences);
pushInput.checked = previousPreferences.push_enabled;
emailInput.checked = previousPreferences.email_fallback_enabled;
} finally {
managePreferencesPending = false;
pushInput.disabled = false;
emailInput.disabled = false;
}
}
container.addEventListener(‘change’, function (event) {
const target = event.target;
if (!target || !target.matches(‘[data-author-follow-global-pref]’)) {
return;
}
handlePreferenceChange();
});
});
}
function init(scope) {
const root = scope || document;
if (!hasFollowWidgets(root)) {
return;
}
bindEvents(root);
renderWidgets(root);
bindPianoInitRefresh();
startPianoReadyPoll();
refresh(root);
}
window.WTAuthorFollow = {
init: init,
refresh: refresh,
};
document.addEventListener(‘DOMContentLoaded’, function () {
init(document);
});
window.addEventListener(‘focus’, function () {
if (hasFollowWidgets(document)) {
refresh(document);
}
});
document.addEventListener(‘visibilitychange’, function () {
if (!document.hidden && hasFollowWidgets(document)) {
refresh(document);
}
});
} else {
window.WTAuthorFollow.init(document);
}
})();
FILE – San Francisco 49ers wide …
more >
–
Associated Press
–
Wednesday, June 3, 2026
SANTA CLARA, Calif. — The Santa Clara County District Attorney’s Office has issued an arrest warrant for San Francisco 49ers receiver Brandon Aiyuk on a misdemeanor charge of exhibition of speeding.
District attorney’s spokesman Sean Webby confirmed Wednesday that the warrant has been issued in response to a video Aiyuk posted to social media last December that appeared to show him speeding on the road in front of Levi’s Stadium.
The California Post first reported the arrest warrant.
Aiyuk posted an apology a few days after the video that appeared to show him driving well over the posted speed limit of 40 mph.
“Sorry ya’ll, my car content won’t come with speeding anymore,” Aiyuk wrote in a social media post. “Was praying with my son tonight and wouldn’t want anybody else to miss out on an opportunity to do the same with their loved ones! My apologies.”
Aiyuk is currently on the reserve/left squad list after he stopped showing up late last season as he rehabilitates a knee injury that has sidelined him since October 2024.
The issues with the 49ers and Aiyuk go back to last summer when the team previously voided $27 million guaranteed in his contract for next season for failing to participate in meetings and other team activities.
General manager John Lynch has said he doesn’t expect Aiyuk to play again for the 49ers. The team has been waiting to see if another team is willing to trade for Aiyuk. The 49ers otherwise could either cut him or keep him on the reserve list.
Aiyuk has three years remaining on the four-year, $120 million extension he signed last year. But he now has no guaranteed money remaining.
The 28-year-old Aiyuk has 294 catches for 4,305 yards and 25 TDs since being drafted in the first round in 2020.
Latest Video
