')) {
previewElem.innerHTML = html;
previewElem.style.display = 'block';
resultElem.style.display = 'none';
modalSubmit.disabled = false;
dupeIdField.value = previewElem.querySelector('.js-question').dataset.questionid;
if (dupeNavi.children.length) {
dupeNavi.children[0].innerHTML = `
${dupeNavi.children[0].dataset.abbr}`;
// Add event listener to return to results
dupeNavi.querySelector('a').addEventListener('click', function (evt) {
dupeIdField.value = '';
previewElem.style.display = 'none';
resultElem.style.display = 'block';
modalSubmit.disabled = true;
dupeNavi.children[0].innerText = dupeNavi.children[0].dataset.text;
dupeSearch.value = dupeSearch.dataset.originalSearch ?? '';
});
}
return;
}
// List of results
resultElem.innerHTML = html;
await delay(10);
dupeNavi.innerHTML = resultElem.querySelector('.navi').outerHTML;
// No suggestions, clear
if (!dupeNavi.children.length) {
errorElem.innerText = 'Your search returned no matches; please try a different search';
resultElem.innerHTML = '';
return;
}
dupeNavi.children[0].innerText = dupeNavi.children[0].dataset.text;
// Add event listener to preview result
resultElem.querySelectorAll('.item').forEach(el => el.addEventListener('click', evt => {
evt.preventDefault();
const url = evt.currentTarget.querySelector('.post-link a').href;
dupeSearch.dataset.originalSearch = searchQuery;
dupeSearch.value = url;
doDupeSearch(closeModal);
}));
}).catch(error => {
errorElem.innerText = 'Your search returned no matches; please try a different search';
resultElem.innerHTML = '';
});
}
/**
* @summary Add event listeners (close buttons)
*/
const initEventListeners = async function () {
document.addEventListener('click', function (evt) {
const closeBtn = evt.target;
if (!closeBtn?.classList?.contains('js-close-question-link')) return;
// Blur button
closeBtn.blur();
const qid = closeBtn.dataset.questionId;
if (!qid) return;
// Fetch close reason dialog
fetch(`${location.origin}/flags/questions/${qid}/close/popup?loadedTimestamp=1663118348686`).then(
response => response.text()
).then(html => {
// Insert close reason dialog after close button
const closeWrapper = document.createElement('div');
closeWrapper.innerHTML = html;
closeBtn.parentNode.insertBefore(closeWrapper, null);
// Remove dialog wrapper on click
closeWrapper.addEventListener('click', function (evt) {
closeWrapper.remove();
});
// Get elements in dialog
const closeModal = closeWrapper.querySelector('#popup-close-question');
const modalTitle = closeModal.querySelector('.popup-title');
const modalBreadcrumbs = closeModal.querySelector('.js-breadcrumbs');
const form = closeWrapper.querySelector('form');
const mainPane = closeModal.querySelector('#pane-main');
const submitBtn = closeModal.querySelector('.js-popup-submit');
const backBtn = closeModal.querySelector('.js-popup-back');
const cancelBtn = closeModal.querySelector('.js-popup-cancel');
const radios = closeModal.querySelectorAll('input');
const dupeSearch = closeModal.querySelector('#duplicate-search, .js-duplicate-search-field');
const dupeNavi = closeModal.querySelector('.navi-container');
// Center dialog in window
setTimeout(closeModal => {
closeModal.style.position = 'fixed';
closeModal.style.top = (window.innerHeight / 2) - (closeModal.offsetHeight / 2) + 'px';
closeModal.style.left = (window.innerWidth / 2) - (closeModal.offsetWidth / 2) + 'px';
}, 10, closeModal);
// Add name to duplicate question field
form.querySelector('#original-question-id').name = 'duplicateOfQuestionId';
// Add breadcrumbs
const bcDivider = `
`
modalBreadcrumbs.innerHTML = `
Closing${bcDivider}
`;
modalBreadcrumbs.classList.remove('d-none');
// Event listeners for close dialog
closeModal.classList.add('d-block');
closeModal.addEventListener('click', function (evt) {
// Don't bubble up to parent
evt.stopPropagation();
});
// Event listeners for close buttons
const btns = closeModal.querySelectorAll('.popup-close a, .js-popup-close');
btns.forEach(btn => btn.addEventListener('click', function (evt) {
closeWrapper.remove();
}));
// Event listeners for back buttons to toggle subpanes
const homeLink = closeModal.querySelector('.s-breadcrumbs--item:first-child');
[homeLink, backBtn].forEach(btn => btn.addEventListener('click', function (evt) {
// Hide all subpanes
const subpanes = closeModal.querySelectorAll('.popup-subpane');
subpanes.forEach(pane => pane.classList.add('dno'));
// Show main pane
mainPane.classList.remove('dno');
// Hide back button and show cancel button
backBtn.classList.add('d-none');
cancelBtn.classList.remove('d-none');
// Clear radio buttons checked state
radios.forEach(radio => radio.checked = false);
// Disable close button
submitBtn.disabled = true;
// Change title
modalTitle.innerText = 'Why should this question be closed?';
}));
// Event listeners for radio buttons
radios.forEach(radio => radio.addEventListener('change', function (evt) {
// If there is a subpane
const subpane = closeWrapper.querySelector(`.popup-subpane[data-subpane-name="${this.dataset.subpaneName}"]`);
if (!subpane) {
// Enable close button
submitBtn.disabled = false;
// Focus close button
submitBtn.focus();
return;
}
// Disable close button
submitBtn.disabled = true;
// Hide all subpanes
const subpanes = closeModal.querySelectorAll('.popup-subpane');
subpanes.forEach(pane => pane.classList.add('dno'));
// Hide main pane
mainPane.classList.add('dno');
// Show selected subpane
subpane.classList.remove('dno');
// Show back button and hide cancel button
backBtn.classList.remove('d-none');
cancelBtn.classList.add('d-none');
// If duplicate search selected
if (this.value === 'Duplicate' || this.dataset.subpaneName === 'duplicate') {
// Change title
modalTitle.innerText = 'What question is this a duplicate of?';
// Focus duplicate search field
form.querySelector('#duplicate-search, .js-duplicate-search-field').focus();
// Load initial duplicate search suggestions if not already loaded
if (dupeSearch.value) return;
const resultElem = subpane.querySelector('.original-display .list-container');
fetch(resultElem.dataset.loadUrl).then(
response => response.text()
).then(async html => {
resultElem.innerHTML = html;
await delay(10);
dupeNavi.innerHTML = resultElem.querySelector('.navi').outerHTML;
// No suggestions, clear
if (!dupeNavi.children.length) {
resultElem.innerHTML = '';
return;
}
dupeNavi.children[0].innerText = dupeNavi.children[0].dataset.text ?? '';
// Add event listener to preview result
resultElem.querySelectorAll('.item').forEach(el => el.addEventListener('click', evt => {
evt.preventDefault();
const url = evt.currentTarget.querySelector('.post-link a').href;
dupeSearch.value = url;
doDupeSearch(closeModal);
}));
});
}
}));
// Event listeners for duplicate search field
let searchDebounceTimeout;
dupeSearch.addEventListener('input', evt => {
if (searchDebounceTimeout) clearTimeout(searchDebounceTimeout);
searchDebounceTimeout = setTimeout(doDupeSearch, 1000, closeModal);
});
// Event listeners for form submit
form.addEventListener('submit', function (evt) {
const closeReasonId = form.closeReasonId.value ?? undefined;
const siteSpecificCloseReasonId = form.siteSpecificCloseReasonId.value ?? undefined;
const siteSpecificOtherText = form.siteSpecificOtherText.value ?? undefined;
const duplicateOfQuestionId = form.duplicateOfQuestionId.value ?? undefined;
// Remove dialog wrapper
closeWrapper.remove();
// We do our own AJAX submission to avoid page reload
evt.preventDefault();
closeQuestionAsOfftopic(qid, closeReasonId, siteSpecificCloseReasonId, siteSpecificOtherText, duplicateOfQuestionId).then(() => {
// Rename close button and disable it
closeBtn.innerText = 'Closed';
closeBtn.disabled = true;
});
});
});
});
};
/**
* @summary Keyboard events for close dialog shortcuts, copied from ReviewQueueHelper
*/
function listenToKeyboardEvents() {
// Cancel existing handlers and implement our own keyboard shortcuts
$(document).off('keypress keyup');
// Keyboard shortcuts event handler
$(document).on('keyup', function (evt) {
// Back buttons: escape (27)
// Unable to use tilde (192) as on the UK keyboard it is swapped the single quote keycode
const cancel = evt.keyCode === 27;
const goback = evt.keyCode === 27;
// Get numeric key presses
let index = evt.keyCode - 49; // 49 = number 1 = 0 (index)
if (index == -1) index = 9; // remap zero to last index
if (index < 0 || index > 9) { // handle 1-0 number keys only (index 0-9)
// Try keypad keycodes instead
let altIndex = evt.keyCode - 97; // 97 = number 1 = 0 (index)
if (altIndex == -1) altIndex = 9; // remap zero to last index
if (altIndex >= 0 && altIndex <= 9) {
index = altIndex; // handle 1-0 number keys only (index 0-9)
}
else {
// Both are invalid
index = null;
}
}
// Do nothing if key modifiers were pressed
if (evt.shiftKey || evt.ctrlKey || evt.altKey) return;
// If edit mode, cancel if esc is pressed
if (cancel && $('.editing-review-content').length > 0) {
$('.js-review-cancel-button').trigger('click');
return;
}
// Get current popup
const currPopup = $('#delete-question-popup, #rejection-popup, #popup-flag-post, #popup-close-question').filter(':visible').last();
// #69 - If a textbox or textarea is focused, e.g.: comment box
// E.g.: if post is being edited or being commented on
if (document.activeElement.tagName == 'TEXTAREA' ||
(document.activeElement.tagName == 'INPUT' && document.activeElement.type == 'text') ||
document.getElementsByClassName('editing-review-content').length > 0) {
// Just unfocus the element if esc was pressed
if (currPopup.length && goback) document.activeElement.blur();
return;
}
// If there's an active popup
if (currPopup.length) {
// If escape key pressed, go back to previous pane, or dismiss popup if on main pane
if (goback) {
// If displaying a single duplicate post, go back to duplicates search
const dupeBack = currPopup.find('.original-display .navi a').filter(':visible');
if (dupeBack.length) {
dupeBack.trigger('click');
return false;
}
// Go back to previous pane if possible,
// otherwise default to dismiss popup
const link = currPopup.find('.popup-close a, .popup-breadcrumbs a, .js-popup-back').filter(':visible');
if (link.length) {
link.last().trigger('click');
// Always clear dupe closure search box on back action
$('#search-text').val('');
return false;
}
}
// If valid index, click it
else if (index != null) {
const currPopup = $('.popup:visible').last();
// Get active (visible) pane
const pane = currPopup.find('form .action-list, .popup-active-pane').filter(':visible').last();
// Get options
const opts = pane.find('input:radio');
// Click option
const opt = opts.eq(index).trigger('click');
// Job is done here. Do not bubble if an option was clicked
return opt.length !== 1;
}
} // end popup is active
});
}
// Append styles
addStylesheet(`
.s-post-summary--stats {
--s-post-summary-stats-gap: 3px;
}
.search-result .excerpt,
.s-post-summary--content .s-post-summary--content-excerpt {
-webkit-line-clamp: 10;
}
.search-result .excerpt > *,
.s-post-summary--content-excerpt > * {
margin-bottom: 0;
}
.search-result .excerpt pre,
.search-result .excerpt .snippet,
.s-post-summary--content-excerpt pre,
.s-post-summary--content-excerpt .s-table-container,
.s-post-summary--content-excerpt .snippet {
max-width: 100%;
max-height: 150px;
margin: 4px 0;
padding: 5px;
overflow: hidden;
white-space: break-spaces;
font-size: 4px;
}
.s-post-summary--content-excerpt .s-table-container {
zoom: 0.5;
}
.s-post-summary--content-excerpt .s-table-container table {
width: auto;
}
.s-post-summary--content b,
.s-post-summary--content strong {
font-weight: 500;
}
.s-post-summary--content-excerpt .snippet-code {
padding: 0;
}
.s-post-summary--content-excerpt .snippet-code-html {
margin: 0;
}
.search-result .excerpt br,
.search-result .excerpt hr,
.s-post-summary--content-excerpt br,
.s-post-summary--content-excerpt hr {
display: none;
}
.search-result .excerpt img,
.s-post-summary--content-excerpt img {
max-width: 100%;
max-height: 80px;
}
.excerpt sup,
.excerpt sub,
.s-post-summary--content-excerpt sup,
.s-post-summary--content-excerpt sub {
font-size: 0.9em;
vertical-align: unset;
}
.s-post-summary--morestats {
padding-top: 16px;
clear: both;
columns: 2;
color: var(--black-600);
font-size: 0.95em;
line-height: 1.2;
}
.somu-comments-preview {
display: none;
position: absolute;
top: -10px;
left: calc(100% + 10px);
width: 100vw;
max-width: 610px;
background: var(--white);
padding: 10px;
border-radius: 5px;
border: 1px solid var(--black-300);
color: var(--theme-body-font-color);
white-space: normal;
box-shadow: 2px 2px 11px -6px black;
}
.somu-comments-preview:before {
content: '';
position: absolute;
top: 6px;
right: 100%;
width: 90px;
height: 27px;
z-index: 0;
}
.somu-question-stats .statscontainer.s-post-summary--stats {
width: 108px;
margin-right: 8px;
margin-bottom: -2px;
}
.statscontainer .s-post-summary--stats-item {
font-size: 13px;
}
.s-post-summary--stats-item {
position: relative;
opacity: 1 !important;
z-index: 1;
}
.s-post-summary--stats-item:hover .somu-comments-preview {
display: block;
z-index: 2;
}
/* New s-btn-group-stacked */
.s-btn-group-stacked {
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
.s-btn-group-stacked .s-btn {
text-align: left;
}
.s-btn-group-stacked .s-btn:not(:last-child) {
margin-bottom: -1px;
}
.s-btn-group-stacked .s-btn:not(:first-child):not(:last-child) {
border-radius: 0;
}
.s-btn-group-stacked .s-btn:first-child:not(:only-child) {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.s-btn-group-stacked .s-btn:last-child:not(:only-child) {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.s-btn-group-stacked .s-btn.is-selected {
background: var(--theme-button-selected-background-color);
box-shadow: none;
}
.s-btn-group-stacked .s-btn__muted.is-selected {
color: var(--black-700);
background-color: var(--black-075);
}
.flush-left > .flush-left {
margin-left: 0;
}
`); // end stylesheet
// On script run
(async function init() {
// Run on question lists and search results pages only
qList = document.querySelector('#questions, #question-mini-list, .js-search-results > div:last-child');
if (!qList) {
console.log('Not a question list page.');
return;
}
// Transform old question lists to new style
let oldQuestionList = document.querySelector('.js-search-results, #qlist-wrapper');
if (oldQuestionList) {
oldQuestionList.classList.remove('ml0', 'bt', 's-card');
oldQuestionList.classList.add('flush-left');
oldQuestionList.querySelectorAll('.s-card').forEach(el => {
el.classList.remove('s-card');
el.classList.add('bb', 'bt', 'bc-black-100');
});
}
const mixedQuestionList = document.querySelector('.mixed-question-list');
if (mixedQuestionList) {
mixedQuestionList.classList.remove('ml0', 'bt', 's-card');
mixedQuestionList.classList.add('flush-left');
}
// When new questions are loaded
const observer = new MutationObserver((mutationsList, observer) => {
const hasNewChildElements = !!mutationsList.filter(m => m.type === 'childList').length;
if (hasNewChildElements) processQuestionLists();
});
observer.observe(qList, { attributes: false, childList: true, subtree: false });
// Init filters
await initQuestionListFilter();
// Do once on page load
await processQuestionLists();
// Add event listeners
initEventListeners();
listenToKeyboardEvents();
})();