);
}
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);
}
})();
Pope Leo XIV waves as he …
more >
By Associated Press –
Thursday, May 28, 2026
ROME — Chicago Mayor Brandon Johnson on Thursday invited Pope Leo XIV to visit his native Chicago next year during a private meeting with the pontiff at the Vatican.
In a letter that Johnson handed to the pontiff, the mayor recalled Pope John Paul II’s visit to Chicago and Mass in Grant Park on Oct. 5, 1979, “forever remembered as the most spiritually inspiring day in Chicago history.”
“Your Holiness, you were a young priest-in-training at the time. Perhaps you were there. Perhaps you would consider a repeat Papal visit nearly 50 years later to share your own message of hope, unity and service,’’ Johnson wrote.
Johnson, who grew up the son of a pastor, invited the pope to say Mass in Grant Park in 2027, noting that Chicago is home to one of the largest Catholic populations in the United States.
It’s at least the second official invitation that Leo has received to visit the United States. U.S. Vice President JD Vance invited Leo soon after he became pope last May.
Leo was born Robert Prevost in 1955 in the South Side of Chicago neighborhood of Bronzeville and grew up in suburban Dolton, near St. Mary of the Assumption, where he attended Mass and elementary school.
PHOTOS: Chicago mayor invites Pope Leo XIV to visit his native city next year
He later studied theology at the Catholic Theological Union of Chicago in Hyde Park and taught in local Catholic schools.
Latest Video
