);
}
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);
}
})();
In this Nov. 2, 2017, photo, …
more >
The Supreme Court on Monday declined to take up Carter Page’s case against former FBI officials who oversaw what the government now concedes was an ill-advised attempt to surveil his activities during the 2016 campaign.
The justices rejected the case without comment.
Mr. Page had said the FBI fabricated information to obtain four warrants obtained against him under the Foreign Intelligence Surveillance Act, then leaked information to the press.
His lawsuit targeted former FBI Director James Comey, former employees Peter Strzok and Lisa Page, and others.
A lower court had barred the case from moving forward, saying the statute of limitations had run on Mr. Carter’s appeal.
It would have taken four justices to hear the dispute for oral arguments to be granted for the court’s next term, which starts in October.
Mr. Page was an adviser to President Trump’s 2016 campaign and got caught up in the federal government’s investigation into now-discredited claims that Mr. Trump colluded with Russia to undermine the election.
Through the probe and the fallout from it, Mr. Comey lost his post as head of the FBI.
Mr. Strzok and Ms. Page were fired for inappropriate conduct and Kevin Clinesmith, another FBI official named in the case, pleaded guilty to falsifying an email to justify the FISA surveillance applications.
Special Counsel Robert Mueller III found no evidence of collusion between Mr. Trump and Russian efforts to influence the 2016 election.
Latest Video
