// ==UserScript== // @name Stack Print Styles // @description Print preprocessor and print styles for Stack Exchange Q&A, blog, and chat. Includes a handy load all comments button at bottom right. // @homepage https://github.com/samliew/SO-mod-userscripts // @author Samuel Liew // @version 2.0.13 // // @match https://*.stackoverflow.com/* // @match https://*.serverfault.com/* // @match https://*.superuser.com/* // @match https://*.askubuntu.com/* // @match https://*.mathoverflow.net/* // @match https://*.stackapps.com/* // @match https://*.stackexchange.com/* // @match https://stackoverflowteams.com/* // @match https://stackoverflow.blog/* // // @match https://chat.stackoverflow.com/* // @match https://chat.stackexchange.com/* // @match https://chat.meta.stackexchange.com/* // // @exclude https://api.stackexchange.com/* // @exclude https://data.stackexchange.com/* // @exclude https://contests.stackoverflow.com/* // @exclude https://winterbash*.stackexchange.com/* // // @require https://raw.githubusercontent.com/samliew/SO-mod-userscripts/master/lib/se-ajax-common.js // @require https://raw.githubusercontent.com/samliew/SO-mod-userscripts/master/lib/common.js // ==/UserScript== /* globals StackExchange */ /// 'use strict'; function processTimestampTooltips() { $('.relativetime, .relativetime-clean').not('[data-timestamp]').each(function () { const title = $(this).attr('title'); $(this).attr('data-timestamp', title.replace(/,.+$/, '')); }); $('time[datetime]').not('[data-timestamp]').each(function () { const title = $(this).attr('datetime'); $(this).attr('data-timestamp', title.replace('T', ' ') + 'Z'); }); $('#question-header').next().find('a[title]').each(function () { const title = $(this).attr('title'); $(this).attr('data-timestamp', title); }); } function loadAllAnswersAndComments(inclDeletedComments = false) { // Load answers from other pages (if more than one) function loadAllAnswers() { // Wrap all existing answers in a div let previousPage = $(`
`).insertAfter('#answers-header'); $('#answers').children('.answer, a[name]').not('[name="new-answer"], [name="tab-top"]').appendTo(previousPage); return new Promise(function (resolve, reject) { // Get other pages, if no other pages resolve immediately const pages = $('.pager-answers').first().children('a').not('[rel="prev"]').not('[rel="next"]'); if (pages.length == 0) { console.log('only one page of answers'); resolve(); return; } // Remove pagination $('.pager-answers').remove(); // Load each pager's url into divs below existing answers let deferreds = []; pages.each(function (i, el) { const pageNum = el.innerText.trim(); const page = $(`
`).insertAfter(previousPage); const aj = page.load(el.href + ' #answers', function () { page.children().children().not('.answer, a').remove(); }); previousPage = page; deferreds.push(aj); }); // Resolve when all pages load $.when.all(deferreds).then(function () { console.log('loaded all answer pages'); // short delay to allow comments to be added to page setTimeout(() => { resolve(); }, 2000); }, reject); }); } // Always expand comments if comments have not been expanded yet function loadAllComments(inclDeletedComments) { return new Promise(function (resolve, reject) { let deferreds = []; $('.question, #answers .answer').find('.js-comments-container').not('.js-del-loaded').addClass('js-del-loaded').each(function () { // Get post id which is required for loading comments const postId = this.dataset.answerid || this.dataset.questionid || this.dataset.postId; // Remove default comment expander after loading comments const elems = $(this).next().find('.js-show-link.comments-link:visible').prev('.js-link-separator').addBack(); // If no comment expander and not loading deleted comments, // do nothing else since everything is already displayed if (!inclDeletedComments && elems.length == 0) { return; } // Get all including deleted comments // This method will avoid jumping to comments section const commentsUrl = `/posts/${postId}/comments?includeDeleted=${inclDeletedComments}&_=${Date.now()}`; const aj = $('#comments-' + postId).show().children('ul.js-comments-list').load(commentsUrl, function () { console.log('loaded comments ' + postId); elems.remove(); }); deferreds.push(aj); }); // Resolve when all comments load $.when.all(deferreds).then(function () { console.log('loaded all comments'); // short delay to allow comments to be added to page setTimeout(() => { resolve(); }, 2000); }, reject); }); } // Short delay for Q&A to init setTimeout(() => { // Do one then the other after loadAllAnswers().then(() => loadAllComments(inclDeletedComments)); }, 1000); } // Append styles addStylesheet(` /* Styles for loading comments buttons */ .print-comment-buttons { position: fixed !important; bottom: 3px; right: 3px; z-index: 999999; } .print-comment-buttons:hover button { display: inline-block !important; } .print-comment-buttons button { display: none; margin-right: 3px; opacity: 0.5; } .print-comment-buttons button:hover { opacity: 1; } .print-comment-buttons button:nth-last-child(1) { display: inline-block; margin-right: 0; } `); // end stylesheet // On script run (function init() { if (location.hostname.includes('chat.')) { appendChatPrintStyles(); } else if (document.getElementById('question') || location.pathname.includes('/questions/') || document.body.classList.contains('unified-theme')) { appendQnaPrintStyles(); processTimestampTooltips(); $(document).on('ajaxStop', processTimestampTooltips); const commentButtons = $(``).appendTo('body'); if (StackExchange.options.user.isModerator) { commentButtons.prepend(``); } commentButtons.on('click', 'button', function (evt) { // Remove buttons commentButtons.remove(); const inclDeletedComments = !!evt.target.dataset.incldeletedcomments; loadAllAnswersAndComments(inclDeletedComments); return false; }); } })(); function appendQnaPrintStyles() { const qnaCss = ` @media print { html, body { max-width: none; } body { font-size: 1rem; background-color: #fff; background-image: none; } .comments, .comments .comment-body > * { font-size: 1.1rem; line-height: 1.35; } header, footer, #topbar, #sidebar, #left-sidebar, #footer, #post-form, body > span, .site-header, .deleted-comment-info, .comment-flagging, .comment-voting, .js-post-issue, .comments + div[id^="comments-link"], .pager-answers, .bottom-notice, a.new-answer, .js-comment-edit, .js-comment-delete, .z-banner, #edit-tags, .js-bookmark-btn, .js-new-contributor-indicator, .new-contributor-indicator { display: none; } #content .question-page #answers .answer { border: none; } /* Lighten bg of deleted answers */ .deleted-answer { background-color: #fff8f8; } /* Do not fade downvoted answers */ .downvoted-answer .comment-body, .downvoted-answer .post-signature, .downvoted-answer .s-prose, .downvoted-answer .vote>* { opacity: 1; } /* Don't show if you have voted */ .js-voting-container .s-btn { color: inherit; } /* No relative dates */ .relativetime, .relativetime-clean, #question-header + .grid > .flex--item time, #question-header + .grid > .flex--item a { font-size: 0; } .relativetime:before, .relativetime-clean:before, #question-header + .grid > .flex--item time:before, #question-header + .grid > .flex--item a:before { content: attr(data-timestamp); font-size: 13px; white-space: nowrap; } .comment-date .relativetime:before, .comment-date .relativetime-clean:before, .user-info .user-action-time .relativetime:before, .user-info .user-action-time .relativetime-clean:before { font-size: 12px; } /* Answers starts on a new page */ #answers-header, #answers .pager-answers + a[name], #answers-header + .js-answer-page[data-page="1"] > a:first-child { page-break-before: avoid; } #answers > a, .js-answer-page > a { display: block; height: 1px; page-break-before: always; } #answers > a:nth-of-type(2), #answers > a:last-of-type, .js-answer-page[data-page="1"] > a:first-child, #mainbar > table .msg { page-break-before: always; } /* Deleted comments on deleted posts */ .deleted-answer .comments .comment.deleted-comment .comment-text, .deleted-answer .comments .comment.deleted-comment .comment-actions { background-color: var(--red-100); } /* Embiggen images */ .s-prose p { page-break-inside: avoid; } .s-prose img { width: auto; max-width: 100%; max-height: 90vh; page-break-inside: avoid; } /* Do not break comments and usercards between pages */ .comment-text, .post-signature, .user-gravatar32, .s-prose ~ div { page-break-inside: avoid; } /* Hide/Reset SOMU stuff */ #somusidebar, #usersidebar, .redact-buttons, .print-comment-buttons, .post-id, .post-mod-menu-link, .mod-userlinks, .somu-mod-message-link, .comment-flagcount, .meta-mentioned { display: none; } .votecell .vote, .votecell .js-voting-container, .post-stickyheader { position: relative; top: 0; } .post-stickyheader .relativetime { border-bottom: none; } .deleted-answer .votecell .vote, .deleted-answer .votecell .js-voting-container { background-color: transparent; } /* Hide/Reset other userscripts stuff */ #roombaFieldRow, .mod-tools, .pronouns { display: none; } } `.replace(/ !important/g, '').replace(/;/g, ' !important;'); // failsafe to prevent dupe !important addStylesheet(qnaCss); } // End QnA styles function appendChatPrintStyles() { const printCss = ` @media print { html, body { max-width: 780px; } body { font-size: 11px; background-color: #fff; background-image: none; } body > span[style*="absolute"], #topbar, .topbar, #feed-ticker, #bottom, #input-area, #sound, input, button, .button, #container > a, #container > br, #widgets > .sidebar-widget:nth-child(2), #widgets > .sidebar-widget:last-child, #sidebar .more, #sidebar .user-currentuser, #sidebar .js-hasfull .message-orig, #sidebar #room-ad, #toggle-favorite, #transcript-body #info br, #transcript-body .room-mini ~ br, #transcript-body .room-mini .mspbar.now, #transcript-body #info .tag, #transcript-body #transcript-logo, #transcript-body #copyright, #transcript-body .action-link, #transcript-body .transcript-nav, .monologue .avatar, .message-controls, .message > .action-link, .message > .meta, .username .name + br, .username .pronouns { display: none; } #sidebar #info #roomdesc > div, #starred-posts > div > ul > li, .ob-message.js-onebox-hidden, #chat .monologue:first-child .js-dynamic-timestamp { display: block; } #sidebar .js-hasfull .message-full { display: inline; } #main { display: flex; flex-direction: column-reverse; width: 100%; } #sidebar { position: relative; width: auto; margin: 10px 0 20px; padding: 10px; border: 1px dotted black; } #transcript-body #container { padding: 0; } #transcript-body #sidebar { margin-top: 0; margin-bottom: -10px; } #sidebar #info #roomdesc { position: relative; height: auto; padding-bottom: 0; border: none; background: transparent; white-space: unset; } #sidebar #info #roomdesc + #sidebar-menu { margin-top: 10px; } #sidebar #present-users-list { max-height: none; overflow: visible; color: #000; } #sidebar #present-users-list li { flex: 0 0 20%; } #sidebar #present-users-list li.inactive { opacity: 0.7; } #sidebar #starred-posts ul.collapsible, #sidebar #starred-posts ul.collapsible.expanded { max-height: none; padding-bottom: 0; overflow: visible; } #chat-body #container { padding-top: 0; } #chat { padding-bottom: 20px; } .monologue { display: table; page-break-inside: avoid; width: calc(100% - 26px); margin: 0; padding: 0; } .monologue, .system-message-container { padding-top: 15px; margin-bottom: -15px; } .monologue .signature { flex: 0 1 120px; margin-right: 8px; } .monologue .messages { flex: 1 0 80%; border-color: #f2f2f2; background-color: #f8f8f8; } div.message.reply-parent, div.message.reply-child { border-color: #f2f2f2; background-color: #f8f8f8; } .monologue.catchup-marker { padding-top: 0; border-top: none; } #chat .message { display: flex; } .message { page-break-inside: avoid; border: none; } .message .content { flex: 1 1 100%; padding-right: 52px; } .message .mention { background-color: transparent; } div.message { padding-left: 15px; } div.message .full, div.message .partial { max-height: none; } #chat .messages .timestamp, #chat .message.cmmt-deleted span.deleted { position: absolute; right: 38px; } .stars .img { filter: saturate(0) grayscale(1) brightness(0); } #transcript-body .pager { text-align: center; } #transcript-body .pager > * { float: none; display: inline-block; } #transcript-body .pager .page-numbers { margin-bottom: 3px; } /* SOMU - Chat Transcript Helper - switch back to original timestamp (UTC) */ .page-numbers[data-orig-text], .timestamp[data-orig-timestamp] { font-size: 0; } .page-numbers[data-orig-text]:before, .timestamp[data-orig-timestamp]:before { content: attr(data-orig-timestamp); font-size: 9px; white-space: nowrap; } .page-numbers[data-orig-text]:before { content: attr(data-orig-text); font-size: 14px; } /* Chat Transcript - room mini - expand full description */ #transcript-body #info .room-mini { width: auto; margin-bottom: 15px; } #transcript-body #info .room-mini .room-mini-description { font-size: 0; } #transcript-body #info .room-mini .room-current-user-count, #transcript-body #info .room-mini .room-message-count { display: none; width: auto; font-size: 11px; } #transcript-body #info .room-mini .room-current-user-count:before, #transcript-body #info .room-mini .room-message-count:before, #transcript-body #info .room-mini .room-mini-description:before { display: inline-block; content: attr(title); margin-right: 3px; font-size: 11px; word-break: break-word; } /* Chat Transcript - convert calendar to text with year */ #transcript-body #info > h2 { display: inline-block; } #transcript-body #info .icon .calendar, #transcript-body #info .calendar-small-link { display: none; } #transcript-body #info .icon { display: inline-block; float: none; font-size: 0; } #transcript-body #info .icon:before { content: attr(title); font-size: 16.5px; font-weight: bold; } } `.replace(/ !important/g, '').replace(/;/g, ' !important;'); // failsafe to prevent dupe !important addStylesheet(printCss); } // End chat print styles