// ==UserScript== // @name Habr.Features // @version 3.7.71 // @description Всякое-разное для Habr aka habr.com // @author AngReload // @include https://habr.com/* // @include http://habr.com/* // @namespace habr_comments // @run-at document-start // @grant GM.xmlHttpRequest // @connect m.habr.com // @icon https://habr.com/favicon.ico // @downloadURL https://update.greasyfork.icu/scripts/368828/HabrFeatures.user.js // @updateURL https://update.greasyfork.icu/scripts/368828/HabrFeatures.meta.js // ==/UserScript== /* global localStorage, MutationObserver, GM */ // настройки по умолчанию const FLAGS = {}; // размеры кнопок FLAGS.SMALL_BUTTONS = false; // Предотвратить закрытие страницы, если статья удалена FLAGS.PREVENT_CLOSING_DELETED_PAGES = false; // остановка гифок // клик по гифке заменит картинку на заглушку // повторный клик вернет гифку на место FLAGS.GIF_STOP = true; // остановить гифки при загрузке страницы FLAGS.GIF_STOP_ONLOAD = false; // цвета заглушки FLAGS.GIF_STOP_COLOR_FG = 'White'; // White FLAGS.GIF_STOP_COLOR_BG = 'LightGray'; // LightGray or WhiteSmoke // менять src вместо создания-удаления нод FLAGS.GIF_STOP_OVERTYPE = false; // показывать счетчики рейтинга в виде: // рейтинг = число_плюсов - число_минусов FLAGS.RATING_DETAILS = true; // клик мышкой по рейтингу меняет вид на простой \ детальный FLAGS.RATING_DETAILS_ONCLICK = false; // рейтинг = число_голосовавших * (процент_плюсов - процент_минусов)% FLAGS.RATING_DETAILS_PN = false; // карма = число_голосовавших * (процент_товарищей - процент_неприятелей)% FLAGS.KARMA_DETAILS = true; // показывать метки времени в текущем часовом поясе // абсолютно, либо относительно текущего времени, либо относительно родительского времени // меняется по клику, в всплывающей подсказке другие виды времени, автообновляется FLAGS.TIME_DETAILS = true; // добавить возможность сортировки комментариев FLAGS.COMMENTS_SORT = true; // сортировать комменты при загрузке страницы или оставить сортировку по времени FLAGS.COMMENTS_SORT_ONLOAD = true; // список доступных сортировок FLAGS.COMMENTS_SORT_BY_FRESHNESS = true; FLAGS.COMMENTS_SORT_BY_TREND = true; // самая полезная сортировка FLAGS.COMMENTS_SORT_BY_QUALITY = false; FLAGS.COMMENTS_SORT_BY_RATING = false; FLAGS.COMMENTS_SORT_BY_POPULARITY = false; FLAGS.COMMENTS_SORT_BY_REDDIT = false; FLAGS.COMMENTS_SORT_BY_RANDOM = false; // добавить возможность сворачивать комментарии FLAGS.COMMENTS_HIDE = false; // свернуть комментарии если их глубина вложенности равна некому числу FLAGS.HIDE_LEVEL = 4; // свернуть комменты с 4-го уровня FLAGS.HIDE_LEVEL_4 = false; // сделать «возврат каретки» для комментариев чтобы глубина вложенности не превышала некого числа FLAGS.LINE_LEN = 8; // отбивать каждый следущий уровень вложенности дополнительным отступом FLAGS.REDUCE_NEW_LINES = true; // глубина вложенности при «возврате каретки» комментариев обозначается разным цветом FLAGS.RAINBOW_NEW_LINE = true; // заменить ссылки ведущие к новым комментариям на дерево комментариев FLAGS.COMMENTS_LINKS = true; // запоминание галки «Использовать MarkDown» для комментариев FLAGS.COMMENTS_MD = true; // добавить ссылку на хабрасторадж в форму редактирования комментариев FLAGS.HTSO_BTN = true; // добавить ссылку на отслеживаемых в странице профиля FLAGS.SUBS_BTN = true; // включить поиск комментариев в профилях FLAGS.FIND_COMMENTS = true; // линии вдоль прокрутки для отображения размеров поста и комментариев FLAGS.SCROLL_LEGEND = true; // добавляет кнопку активации ночного режима FLAGS.NIGHT_MODE = true; // добавляет диалок настроек FLAGS.CONFIG_INTERFACE = true; // включить разные стили FLAGS.USERSTYLE = true; // лента постов с увеличенными отступами, нижний бар выровнен вправо FLAGS.USERSTYLE_FEED_DISTANCED = true; // мелкий фикс для отступа в счетчике ленты FLAGS.USERSTYLE_COUNTER_NEW_FIX_SPACE = true; // удаляет правую колонку, там где не жалко FLAGS.USERSTYLE_REMOVE_SIDEBAR_RIGHT = true; // аватарки без скруглений FLAGS.USERSTYLE_REMOVE_BORDER_RADIUS_AVATARS = true; // большие аватарки в профиле и карточках FLAGS.USERSTYLE_USERINFO_BIG_AVATARS = true; // ограничить размер картинок в комментах FLAGS.USERSTYLE_COMMENTS_IMG_MAXSIZE = 0; // ограничить размер картинок в комментах FLAGS.USERSTYLE_COMMENTS_OPACITY = false; // нормальные стили для комментариев FLAGS.USERSTYLE_COMMENTS_FIX = true; // нормальные стили для кода FLAGS.USERSTYLE_CODE_FIX = true; // окантовка границ спойлеров FLAGS.USERSTYLE_SPOILER_BORDERS = true; // делает глючные плавающие блоки статичными FLAGS.USERSTYLE_STATIC_STICKY = true; // показывать язык подсветки блоков кода FLAGS.USERSTYLE_HLJS_LANG = true; // показывать язык кода только при наведении FLAGS.USERSTYLE_HLJS_LANG_HOVER = false; // свой шрифт для блоков кода FLAGS.USERSTYLE_CODE_FONT = ''; // PT Mono // размер табов в коде FLAGS.USERSTYLE_CODE_TABSIZE = 2; // для ночного режима FLAGS.USERSTYLE_CODE_NIGHT = true; // для настроек FLAGS.USERSTYLE_CONFIG_INTERFACE = true; FLAGS.SCROLL_BEHAVIOR_SMOOTH = true; const configOptions = [ ['KARMA_DETAILS', 'счётчики кармы с плюсами и минусами'], ['RATING_DETAILS', 'рейтинги с плюсами и минусами'], ['RATING_DETAILS_PN', 'рейтинги в процентах'], ['TIME_DETAILS', 'отметки о времени с подробностями'], ['PREVENT_CLOSING_DELETED_PAGES', 'предотвратить закрытие страницы, если статья удалена'], ['GIF_STOP', 'остановка гифок'], ['GIF_STOP_ONLOAD', 'остановить гифки при загрузке страницы'], ['COMMENTS_SORT', 'сортировка комментов'], ['COMMENTS_SORT_ONLOAD', 'сортировать комменты при загрузке'], ['COMMENTS_SORT_BY_FRESHNESS', 'сортировка новые'], ['COMMENTS_SORT_BY_TREND', 'сортировка горячие'], ['COMMENTS_SORT_BY_QUALITY', 'сортировка хорошие'], ['COMMENTS_SORT_BY_REDDIT', 'сортировка проверенные'], ['COMMENTS_SORT_BY_RATING', 'сортировка рейтинговые'], ['COMMENTS_SORT_BY_POPULARITY', 'сортировка популярные'], ['COMMENTS_SORT_BY_RANDOM', 'сортировка случайные'], // ['COMMENTS_HIDE', 'сворачивание комментов'], // ['HIDE_LEVEL_4', 'свернуть комменты с 4-го уровня'], // ['REDUCE_NEW_LINES', 'возврат каретки с уменьшением ширины'], // ['RAINBOW_NEW_LINE', 'возврат каретки комментов в разных цветах'], // ['COMMENTS_LINKS', '"читать комментарии" ведёт на корень комментов'], // ['COMMENTS_MD', 'запоминать галку MarkDown'], ['USERSTYLE_COMMENTS_OPACITY', 'заминусованные комменты без прозрачности'], // ['FIX_JUMPING_SCROLL', 'заморозить высоту статьи при загрузке'], ['SCROLL_LEGEND', 'ленгенда страницы у скроллбара'], ['SUBS_BTN', 'табы подписок в профилях'], ['FIND_COMMENTS', 'поиск по комментариям в профилях'], ['NIGHT_MODE', 'ночной режим'], ['SMALL_BUTTONS', 'маленькие кнопки настроек и ночного режима'], // ['USERSTYLE', 'стилизация'], ['USERSTYLE_COMMENTS_FIX', 'стили комментов'], ['USERSTYLE_HLJS_LANG', 'показать язык для блоков кода'], // ['USERSTYLE_HLJS_LANG_HOVER', 'язык кода скрыт до наведении курсора'], ['USERSTYLE_STATIC_STICKY', 'зафиксировать плавающие блоки'], // ['USERSTYLE_SPOILER_BORDERS', 'видимые границы спойлеров'], ['USERSTYLE_FEED_DISTANCED', 'большие отступы между постами в ленте'], ['USERSTYLE_REMOVE_SIDEBAR_RIGHT', 'удалить правую колонку сайта'], // ['USERSTYLE_REMOVE_BORDER_RADIUS_AVATARS', 'квадратные аватарки'], ['USERSTYLE_USERINFO_BIG_AVATARS', 'по возможности большие аватарки в профилях'], ]; // сохраняем умолчания для панели настроек if (!localStorage.getItem('habrafixFlags')) { localStorage.setItem('habrafixFlags', JSON.stringify(FLAGS)); } else { const jsonString = localStorage.getItem('habrafixFlags'); const loadedConfig = jsonString ? JSON.parse(jsonString) : {}; const loadedKeys = Object.keys(loadedConfig); Object.keys(FLAGS).forEach((key) => { if ( loadedKeys.includes(key) && configOptions.find(arr => arr[0] === key) ) { FLAGS[key] = loadedConfig[key]; } }); } let BUTTON_SIZE = 16; let BUTTON_SIZE2 = 25; let BUTTON_SIZE4 = 48; let KARMA_WIDTH = 84; // ? if (FLAGS.SMALL_BUTTONS) { BUTTON_SIZE = 16; BUTTON_SIZE2 = 25; BUTTON_SIZE4 = 48; KARMA_WIDTH = 84; // ? } else { BUTTON_SIZE = 32; BUTTON_SIZE2 = 25; BUTTON_SIZE4 = 88; KARMA_WIDTH = 84; } // интерфейс для хранения настроек const userConfig = { // имя записи в localsorage key: 'habrafix', // модель настроек: ключ - возможные значения model: { time_publications: ['fromNow', 'absolute'], time_comments: ['fromParent', 'fromNow', 'absolute'], comments_order: ['trend', 'time'], scores_details: [true, false], comment_markdown: [false, true], night_mode: [false, true], }, config: {}, // при старте для конфига берем сохраненные параметры либо по умолчанию init() { let jsonString = localStorage.getItem(userConfig.key); const loadedConfig = jsonString ? JSON.parse(jsonString) : {}; const loadedKeys = Object.keys(loadedConfig); const config = {}; Object.keys(userConfig.model).forEach((key) => { const exist = loadedKeys.indexOf(key) >= 0; config[key] = exist ? loadedConfig[key] : userConfig.model[key][0]; }); jsonString = JSON.stringify(config); localStorage.setItem(userConfig.key, jsonString); userConfig.config = config; }, getItem(key) { const jsonString = localStorage.getItem(userConfig.key); const config = JSON.parse(jsonString); return config[key]; // return userConfig.config[key]; }, setItem(key, value) { let jsonString = localStorage.getItem(userConfig.key); const config = JSON.parse(jsonString); config[key] = value; jsonString = JSON.stringify(config); localStorage.setItem(userConfig.key, jsonString); userConfig.config = config; }, // каруселит параметр по значения модели shiftItem(key) { const currentValue = userConfig.getItem(key); const availableValues = userConfig.model[key]; const currentIdx = availableValues.indexOf(currentValue); const nextIdx = (currentIdx + 1) % availableValues.length; const nextValue = availableValues[nextIdx]; userConfig.setItem(key, nextValue); return nextValue; }, }; userConfig.init(); // свои стили const userStyleEl = document.createElement('style'); let userStyle = ''; if (FLAGS.SCROLL_LEGEND) { userStyle += ` .legend_el { position: fixed; width: 4px; right: 0; transition: top 1s ease-out, height 1s ease-out; z-index: 10000; } #xpanel { right: 4px; } `; } if (FLAGS.USERSTYLE_FEED_DISTANCED) { userStyle += ` .post__body_crop { text-align: right; } .post__body_crop .post__text { text-align: left; } .post__footer { text-align: right; } .posts_list .content-list__item_post { padding: 40px 0; } `; } if (FLAGS.USERSTYLE_COUNTER_NEW_FIX_SPACE) { userStyle += ` .toggle-menu__item-counter_new { margin-left: 4px; } `; } if (FLAGS.USERSTYLE_COMMENTS_OPACITY) { userStyle += ` .comment__message_downgrade { opacity: 1; } `; } if (FLAGS.USERSTYLE_REMOVE_SIDEBAR_RIGHT || FLAGS.PREVENT_CLOSING_DELETED_PAGES) { // remove for // https://habr.com/post/352896/ // https://habr.com/sandbox/ // https://habr.com/sandbox/115216/ // https://habr.com/users/saggid/posts/ // https://habr.com/users/saggid/comments/ // https://habr.com/users/saggid/favorites/ // https://habr.com/users/saggid/favorites/posts/ // https://habr.com/users/saggid/favorites/comments/ // https://habr.com/company/pvs-studio/blog/353640/ // https://habr.com/company/pvs-studio/blog/ // https://habr.com/company/pvs-studio/blog/top/ // https://habr.com/company/pvs-studio/ // https://habr.com/feed/ // https://habr.com/top/ // https://habr.com/top/yearly/ // https://habr.com/all/ // https://habr.com/all/top10/ // display for // https://habr.com/company/pvs-studio/profile/ // https://habr.com/company/pvs-studio/vacancies/ // https://habr.com/company/pvs-studio/fans/all/rating/ // https://habr.com/company/pvs-studio/workers/new/rating/ // https://habr.com/feed/settings/ // https://habr.com/users/ // https://habr.com/hubs/ // https://habr.com/hubs/admin/ // https://habr.com/companies/ // https://habr.com/companies/category/software/ // https://habr.com/companies/new/ // https://habr.com/flows/design/ const path = window.location.pathname; const isPost = /^\/(ru|en)\/post\/\d+\/$/.test(path); const isSandbox = /^\/(ru|en)\/sandbox\//.test(path); const isUserPosts = /^\/(ru|en)\/users\/[^/]+\/posts\//.test(path); const isUserComments = /^\/(ru|en)\/users\/[^/]+\/comments\//.test(path); const isUserFavorites = /^\/(ru|en)\/users\/[^/]+\/favorites\//.test(path); // const isUserSubscription = /^\/(ru|en)\/users\/[^/]+\/subscription\//.test(path); const isCompanyBlog = /^\/(ru|en)\/company\/[^/]+\/blog\//.test(path); const isCompanyBlog2 = /^\/(ru|en)\/company\/[^/]+\/(page\d+\/)?$/.test(path); const isCompanyBlogPost = /^\/(ru|en)\/company\/[^/]+\/blog\/[^/]+/.test(path); const isFeed = /^\/(ru|en)\/feed\//.test(path); const isHome = /^\/(ru|en)\/$/.test(path); const isTop = /^\/(ru|en)\/top\//.test(path); const isAll = /^\/(ru|en)\/all\//.test(path); const isNews = /^\/(ru|en)\/news\//.test(path); const isNewsT = /^\/(ru|en)\/news\/t\/\d+\/$/.test(path); if (FLAGS.USERSTYLE_REMOVE_SIDEBAR_RIGHT && ( isPost || isSandbox || isUserPosts || isUserComments || isUserFavorites || isCompanyBlog || isCompanyBlog2 || isFeed || isHome || isTop || isAll || isNews || isNewsT )) { userStyle += ` .sidebar_right, .sidebar { display: none; } .content_left { padding-right: 0; } .comment_plain { max-width: 860px; } `; } if (FLAGS.PREVENT_CLOSING_DELETED_PAGES && ( isPost || isSandbox || isCompanyBlogPost || isNews || isNewsT )) { window.onbeforeunload = (e) => { const xhr = new XMLHttpRequest(); xhr.open('HEAD', '', false); xhr.send(); if (xhr.status < 200 || xhr.status >= 400) { e.preventDefault(); const message = 'Статья уже удалена или недоступна'; e.returnValue = message; return message; } return undefined; }; } } if (FLAGS.USERSTYLE_REMOVE_BORDER_RADIUS_AVATARS) { userStyle += ` .user-info__image-pic, .user-pic_popover, .media-obj__image-pic, .company-info__image-pic { border-radius: 0; } `; } if (FLAGS.USERSTYLE_USERINFO_BIG_AVATARS) { userStyle += ` .page-header { height: auto; } .media-obj__image-pic_hub, .user-info__stats .media-obj__image-pic_user, /* .media-obj__image-pic_company, */ .company-info__image-pic { width: auto; height: auto; } .page-header_tall .company-info__image-pic { width: 48px; height: 48px; } `; } if (FLAGS.COMMENTS_SORT) { userStyle += ` .comments_order { color: #333; font-size: 14px; font-family: "-apple-system",BlinkMacSystemFont,Arial,sans-serif; text-rendering: optimizeLegibility; border-bottom: 1px solid #e3e3e3; padding: 8px; text-align: right; } .comments_order a { color: #548eaa; font-style: normal; text-decoration: none; } .comments_order a:hover { color: #487284; } `; } if (FLAGS.USERSTYLE_COMMENTS_FIX) { userStyle += ` .content-list_comments { overflow: visible; } .comment__folding-dotholder { display: none !important; } .content-list_nested-comments { border-left: 1px solid #e3e3e3; margin: 0; /*padding-top: 20px;*/ padding-left: 20px !important; } .content-list_comments { /*border-left: 1px solid silver;*/ margin: 0; /*padding-left: 0;*/ padding-top: 20px; /*background: #FCE4EC;*/ } #comments-list .js-form_placeholder:not(:empty) { border-left: 1px solid #e3e3e3; padding-left: 20px; } .comments_new-line { border-left: 1px solid #777; border-bottom: 1px solid #777; border-top: 1px solid #777; margin-left: -${FLAGS.LINE_LEN * 21}px !important; background: white; padding-bottom: 4px; } /* .comment__head_topic-author.comment__head_new-comment */ .comment__head_topic-author .user-info { text-decoration: underline; } /* фикс, когда не добавляется класс &_plus или minus при user_vote_action */ .voting-wjt__button[title="Вы проголосовали положительно"] { color: #7ba600; } .voting-wjt__button[title="Вы проголосовали отрицательно"] { color: #d53c30; } `; } if (FLAGS.USERSTYLE_COMMENTS_FIX && FLAGS.RAINBOW_NEW_LINE) { userStyle += ` .comments_new-line-1 { border-color: #0caefb; } .comments_new-line-2 { border-color: #06feb7; } .comments_new-line-3 { border-color: #fbcb02; } .comments_new-line-0 { border-color: #fb0543; } .js-comment_parent:not(:hover) { color: #cd66cd !important; } `; } if (FLAGS.USERSTYLE_COMMENTS_FIX && FLAGS.REDUCE_NEW_LINES) { userStyle += ` .comments_new-line .comments_new-line { margin-left: -${(FLAGS.LINE_LEN - 1) * 21}px !important; } `; } if (FLAGS.USERSTYLE_COMMENTS_IMG_MAXSIZE) { userStyle += ` .comment__message img { max-height: ${FLAGS.USERSTYLE_COMMENTS_IMG_MAXSIZE}px; } .comment__message .spoiler .img { max-height: auto; } `; } if (FLAGS.USERSTYLE_CODE_FIX) { let addFont = ''; if (FLAGS.USERSTYLE_CODE_FONT) { addFont = FLAGS.USERSTYLE_CODE_FONT; if (addFont.indexOf(' ') >= 0) { addFont = `"${addFont}"`; } addFont += ','; } const tabSize = FLAGS.USERSTYLE_CODE_TABSIZE || 4; userStyle += ` .editor .text-holder textarea, .tm-editor__textarea, pre { font-family: ${addFont} 'Ubuntu Mono', Menlo, Monaco, Consolas, 'Lucida Console', 'Courier New', monospace; } code { font-family: ${addFont} 'Ubuntu Mono', Menlo, Monaco, Consolas, 'Lucida Console', 'Courier New', monospace !important;; -o-tab-size: ${tabSize}; -moz-tab-size: ${tabSize}; tab-size: ${tabSize}; background: #f7f7f7; border-radius: 3px; color: #505c66; display: inline-block; font-weight: 500; line-height: 1.29; padding: 5px 9px; vertical-align: 1px; } `; } if (FLAGS.USERSTYLE_SPOILER_BORDERS) { userStyle += ` .spoiler .spoiler_text { border: 1px dashed rgb(12, 174, 251); } `; } if (FLAGS.USERSTYLE_STATIC_STICKY) { userStyle += ` .wrapper-sticky, .js-ad_sticky, .js-ad_sticky_comments { position: static !important; } .sticky-spacer { display: none !important; } `; } if (FLAGS.GIF_STOP) { userStyle += ` .habrafix_gif-stop:hover { outline: 4px solid #548eaa; outline-offset: -4px; } `; } if (FLAGS.USERSTYLE_HLJS_LANG) { let hover = ''; if (FLAGS.USERSTYLE_HLJS_LANG_HOVER) hover = ':hover'; userStyle += ` pre { position: relative; } .hljs${hover}::after { position: absolute; font-size: 12px; content: 'code'; right: 0; top: 0; padding: 1px 5px 0 4px; /*border-bottom: 1px solid #e5e8ec; border-left: 1px solid #e5e8ec; border-bottom-left-radius: 3px; color: #505c66;*/ opacity: .5; } `; userStyle += [ ['1c', '1C:Enterprise (v7, v8)'], ['abnf', 'Augmented Backus-Naur Form'], ['accesslog', 'Access log'], ['actionscript', 'ActionScript'], ['ada', 'Ada'], ['apache', 'Apache'], ['applescript', 'AppleScript'], ['arduino', 'Arduino'], ['armasm', 'ARM Assembly'], ['asciidoc', 'AsciiDoc'], ['aspectj', 'AspectJ'], ['autohotkey', 'AutoHotkey'], ['autoit', 'AutoIt'], ['avrasm', 'AVR Assembler'], ['awk', 'Awk'], ['axapta', 'Axapta'], ['bash', 'Bash'], ['basic', 'Basic'], ['bnf', 'Backus–Naur Form'], ['brainfuck', 'Brainfuck'], ['cal', 'C/AL'], ['capnproto', 'Cap’n Proto'], ['ceylon', 'Ceylon'], ['clean', 'Clean'], ['clojure-repl', 'Clojure REPL'], ['clojure', 'Clojure'], ['cmake', 'CMake'], ['coffeescript', 'CoffeeScript'], ['coq', 'Coq'], ['cos', 'Caché Object Script'], ['cpp', 'C++'], ['crmsh', 'crmsh'], ['crystal', 'Crystal'], ['cs', 'C#'], ['csp', 'CSP'], ['css', 'CSS'], ['d', 'D'], ['dart', 'Dart'], ['delphi', 'Delphi'], ['diff', 'Diff'], ['django', 'Django'], ['dns', 'DNS Zone file'], ['dockerfile', 'Dockerfile'], ['dos', 'DOS .bat'], ['dsconfig', 'dsconfig'], ['dts', 'Device Tree'], ['dust', 'Dust'], ['ebnf', 'Extended Backus-Naur Form'], ['elixir', 'Elixir'], ['elm', 'Elm'], ['erb', 'ERB (Embedded Ruby)'], ['erlang-repl', 'Erlang REPL'], ['erlang', 'Erlang'], ['excel', 'Excel'], ['fix', 'FIX'], ['flix', 'Flix'], ['fortran', 'Fortran'], ['fsharp', 'F#'], ['gams', 'GAMS'], ['gauss', 'GAUSS'], ['gcode', 'G-code (ISO 6983)'], ['gherkin', 'Gherkin'], ['glsl', 'GLSL'], ['go', 'Go'], ['golo', 'Golo'], ['gradle', 'Gradle'], ['groovy', 'Groovy'], ['haml', 'Haml'], ['handlebars', 'Handlebars'], ['haskell', 'Haskell'], ['haxe', 'Haxe'], ['hsp', 'HSP'], ['htmlbars', 'HTMLBars'], ['http', 'HTTP'], ['hy', 'Hy'], ['inform7', 'Inform 7'], ['ini', 'Ini'], ['irpf90', 'IRPF90'], ['java', 'Java'], ['javascript', 'JavaScript'], ['jboss-cli', 'jboss-cli'], ['json', 'JSON'], ['julia-repl', 'Julia REPL'], ['julia', 'Julia'], ['kotlin', 'Kotlin'], ['lasso', 'Lasso'], ['ldif', 'LDIF'], ['leaf', 'Leaf'], ['less', 'Less'], ['lisp', 'Lisp'], ['livecodeserver', 'LiveCode'], ['livescript', 'LiveScript'], ['llvm', 'LLVM IR'], ['lsl', 'Linden Scripting Language'], ['lua', 'Lua'], ['makefile', 'Makefile'], ['markdown', 'Markdown'], ['mathematica', 'Mathematica'], ['matlab', 'Matlab'], ['maxima', 'Maxima'], ['mel', 'MEL'], ['mercury', 'Mercury'], ['mipsasm', 'MIPS Assembly'], ['mizar', 'Mizar'], ['mojolicious', 'Mojolicious'], ['monkey', 'Monkey'], ['moonscript', 'MoonScript'], ['n1ql', 'N1QL'], ['nginx', 'Nginx'], ['nimrod', 'Nimrod'], ['nix', 'Nix'], ['nsis', 'NSIS'], ['objectivec', 'Objective-C'], ['ocaml', 'OCaml'], ['openscad', 'OpenSCAD'], ['oxygene', 'Oxygene'], ['parser3', 'Parser3'], ['perl', 'Perl'], ['pf', 'pf'], ['pgsql', 'PostgreSQL'], ['plaintext', 'просто текст'], // на будущее ['php', 'PHP'], ['pony', 'Pony'], ['powershell', 'PowerShell'], ['processing', 'Processing'], ['profile', 'Python profile'], ['prolog', 'Prolog'], ['protobuf', 'Protocol Buffers'], ['puppet', 'Puppet'], ['purebasic', 'PureBASIC'], ['python', 'Python'], ['q', 'Q'], ['qml', 'QML'], ['r', 'R'], ['rib', 'RenderMan RIB'], ['roboconf', 'Roboconf'], ['routeros', 'Microtik RouterOS script'], ['rsl', 'RenderMan RSL'], ['ruby', 'Ruby'], ['ruleslanguage', 'Oracle Rules Language'], ['rust', 'Rust'], ['scala', 'Scala'], ['scheme', 'Scheme'], ['scilab', 'Scilab'], ['scss', 'SCSS'], ['shell', 'Shell Session'], ['smali', 'Smali'], ['smalltalk', 'Smalltalk'], ['sml', 'SML'], ['sqf', 'SQF'], ['sql', 'SQL'], ['stan', 'Stan'], ['stata', 'Stata'], ['step21', 'STEP Part 21'], ['stylus', 'Stylus'], ['subunit', 'SubUnit'], ['swift', 'Swift'], ['taggerscript', 'Tagger Script'], ['tap', 'Test Anything Protocol'], ['tcl', 'Tcl'], ['tex', 'TeX'], ['thrift', 'Thrift'], ['tp', 'TP'], ['twig', 'Twig'], ['typescript', 'TypeScript'], ['vala', 'Vala'], ['vbnet', 'VB.NET'], ['vbscript-html', 'VBScript in HTML'], ['vbscript', 'VBScript'], ['verilog', 'Verilog'], ['vhdl', 'VHDL'], ['vim', 'Vim Script'], ['x86asm', 'Intel x86 Assembly'], ['xl', 'XL'], ['xml', 'HTML, XML'], ['xquery', 'XQuery'], ['yaml', 'YAML'], ['zephir', 'Zephir'], ].map(([langTag, langName]) => `.hljs.${langTag}${hover}::after{content:'${langName} [${langTag}]'}`).join(''); } if (FLAGS.USERSTYLE_CODE_NIGHT) { userStyle += ` .night_mode_switcher { box-sizing: border-box; position: fixed; width: 32px; height: 32px; right: 32px; bottom: 32px; width: ${BUTTON_SIZE}px; height: ${BUTTON_SIZE}px; right: ${BUTTON_SIZE}px; bottom: ${BUTTON_SIZE}px; z-index: 10000; background-color: transparent; border-radius: 50%; border: 4px solid #aaa; border-right-width: ${BUTTON_SIZE / 2}px; transition: border-color 0.1s ease-out; } .night_mode_switcher:hover { border-color: #333; } .night .night_mode_switcher { border-color: #515151; } .night .night_mode_switcher:hover { border-color: #9e9e9e; } .night ::-webkit-scrollbar, .night ::-webkit-scrollbar-corner, .night ::-webkit-scrollbar-track-piece { background-color: #000; } .night ::-webkit-scrollbar-thumb { background-color: #22272b; border: 1px solid #000; } .night ::-webkit-scrollbar-thumb:hover { background-color: #2C3237; } .night { scrollbar-color: dark; scrollbar-face-color: #22272b; scrollbar-track-color: #000; scrollbar-color: #22272b #000; } /* bg */ .night .sidebar-block__suggest, .night .dropdown-container, .night .poll-result__bar, .night .comments_new-line, .night .tm-editor__textarea, .night .layout, .night .toggle-menu__most-read, .night .toggle-menu_most-comments, .night .partner-info { background: #171c20; } /* text */ .night .companies-rating__name:not(:hover), .night .profile-section__title, .night .profile-section__about-text, .night .profile-section__invited, .night .sidebar-block__suggest, .night .user-message__body, .night .promo-block__title_total, .night .beta-anounce__text, .night .defination-list__label, .night .defination-list__value, .night .search-field__select, .night .search-field__input[type="text"], .night .search-form__field, .night .post-info__title:not(:hover), .night .dropdown__user-stats, .night .dropdown-container_white .user-info__special, .night .n-dropdown-menu__item-link, .night body, .night .default-block__polling-title, .night .poll-result__data-label, .night code, .night .user-info__fullname, .night .user-info__specialization, .night .page-header__info-title, .night .page-header__info-desc, .night .post__title-text, .night .post__title_link:not(:visited), .night .checkbox__label, .night .radio__label, .night .tm-editor__textarea, .night .footer-block__title, .night #TMpanel .container .bmenu > a.current, .night .post__text-html, .night .comment__message, .night .comment-form__preview, .night .post-share__title, .night .post-donate__title, .night .news-block__title, .night .news-topic__title:not(:visited), .night .partner-info__title, .night .partner-info__description { color: #9e9e9e; } /* non important text */ .night .icon-svg_bookmark, .night .icon-svg_views-count, .night .icon-svg_post-comments, .night .icon-svg_edit, .night .icon-svg_recommend, .night .icon-svg_report, .night .voting-wjt__button:not(.voting-wjt__button_plus):not(.voting-wjt__button_minus), .night .icon_comment-edit, .night .icon_comment-anchor, .night .icon_comment-bookmark, .night .icon_comment-branch, .night .icon_comment-arrow-up, .night .icon_comment-arrow-down, .night .layout__elevator { color: #515151; } .night .voting-wjt__button:not(.voting-wjt__button_plus):not(.voting-wjt__button_minus):hover, .night .icon_comment-anchor:hover, .night .icon_comment-bookmark:hover, .night .icon_comment-branch:hover, .night .icon_comment-arrow-up:hover, .night .icon_comment-arrow-down:hover { color: #548eaa; } .night .n-dropdown-menu__item-link:hover { color: white; } /* top lvl bg */ .night .h-popover, .night .profile-section__user-hub:not(.profile-section__user-hub_cross), .night a.sort-panel__item-toggler.active, .night .checkbox__label::before, .night .radio__label::before, .night .content-list__item_conversation:hover, .night .search-field__select, .night .search-field__input[type="text"], .night .search-form__field, .night .dropdown-container, .night .n-dropdown-menu, .night .post__translatation, .night code, .night .megapost-teasers, .night .tm-editor_comments, .night .promo-block__header, .night .post__text-html blockquote, .night .default-block, .night .post-share, .night .post-donate, .night .company-info__author, .night .layout__row_footer-links { background: #22272B; } /* not important bg */ .night .profile-section__user-hub:not(.profile-section__user-hub_cross):hover, .night .btn_blue.disabled, .night .btn_blue[disabled], .night .tracker_page table.tracker_folowers tr.new, .night .dropdown__user-stats, .night .comment__head_topic-author, .night .promo-item:hover, .night .layout__row_navbar, .night .layout__row_footer, .night #TMpanel, .night .n-dropdown-menu__item-link_flow:hover, .night #tracker-page .tracker-table__row.new { background: #1f2327; } /* borders */ .night #tracker-page .tracker-table__header, .night #tracker-page .tracker-table__cell, .night .h-popover, .night .h-popover__stats, .night .default-block__footer, .night .toggle-menu__item-link_bordered, .night .default-block_promote, .night .sort-panel, .night .n-dropdown-menu_flows, .night .for_users_only_msg, .night #comments-list .js-form_placeholder, .night .sidebar-block__suggest, .night .content-list_preview-message, .night .btn_outline_blue[disabled], .night .user-message__body_html pre code, .night .content-list_user-dialog, .night .wysiwyg-toolbar, .night .content-list__item_bordered, .night .promo-block__total, .night .search-field__select, .night .search-field__input[type="text"], .night .search-form__field, .night .tracker_page table.tracker_folowers tr td, .night .tracker_page table.tracker_folowers tr th, .night .stacked-menu__item_devided, .night .post__text-html table, .night .post__text-html table td, .night .post__text-html table th, .night .n-dropdown-menu__item_border, .night .dropdown-container, .night .default-block_bordered, .night .default_block_polling, .night .column-wrapper_tabs .sidebar_right, .night .post__type-label, .night .promo-block__header, .night .user-info__contacts, .night .comment__message pre code, .night .comment-form__preview pre code, .night .sandbox-panel, .night .comment__post-title, .night .tm-editor__textarea, .night .promo-block__footer, .night .author-panel, .night .promo-block, .night .post__text-html pre code, .night .footer-block__title, .night #TMpanel, .night .layout__row_navbar, .night .page-header_bordered, .night .post-stats, .night .company-info__about, .night .company-info_post-additional, .night .company-info__contacts, .night .post-share, .night .post-donate, .night .content-list__item_devided, .night .comments_order, .night .comments-section__head, .night .content-list_nested-comments, .night .default-block__header, .night .column-wrapper_bordered, .night .tabs-menu, .night .toggle-menu, .night .news-block__header, .night .news-block__footer { border-color: #393d41; } .night .rating-info__progress, .night .poll-result__progress { background-color: #515151; } .night .poll-result__progress_winner { background-color: #5e8eac; } .night .layout__elevator:hover { background-color: #22272B; } .night .comment__head_topic-author { background: #003030; } .night .comment__head_my-comment { background: #003000; } .night .comment__head_new-comment { background: black } .night .user-info__nickname_comment, .night .icon-svg_logo-habrahabr { color: inherit; } .night [disabled] { opacity: 0.5 } .night .content-list_comments .comment__folding-dotholder::before, .night .comment.is_selected::after { filter: invert(0.9); } /* img filter */ .night .comment__message img, .night .comment-form__preview img, .night .default-block__content #facebook_like_box, .night .default-block__content #vk_groups, .night .post img, .night .page-header__banner img, .night .company_top_banner img, .night img .teaser__image, .night .teaser__image-pic, .night .article__body img { filter: brightness(0.5); transition: filter .6s ease-out; } .night .comment__message img:hover, .night .comment-form__preview img:hover, .night .default-block__content #facebook_like_box:hover, .night .default-block__content #vk_groups:hover, .night img[alt="en"], .night img[alt="habr"], .night img:hover, .night a.post-author__link img, .night img.user-info__image-pic, .night .teaser__image-pic:hover, .night .teaser__image:hover { filter: none; } /* Atelier Cave Dark */ .night .hljs-comment, .night .hljs-quote { color:#7e7887 !important } .night .hljs-variable, .night .hljs-template-variable, .night .hljs-attribute, .night .hljs-regexp, .night .hljs-link, .night .hljs-tag, .night .hljs-name, .night .hljs-selector-id, .night .hljs-selector-class { color:#be4678 !important } .night .hljs-number, .night .hljs-meta, .night .hljs-built_in, .night .hljs-builtin-name, .night .hljs-literal, .night .hljs-type, .night .hljs-params { color:#aa573c !important } .night .hljs-string, .night .hljs-symbol, .night .hljs-bullet { color:#2a9292 !important } .night .hljs-title, .night .hljs-section { color:#576ddb !important } .night .hljs-keyword, .night .hljs-selector-tag { color:#955ae7 !important } .night .hljs-deletion, .night .hljs-addition { color:#19171c !important; display:inline-block !important; width:100% !important } .night .hljs-deletion { background-color:#be4678 !important } .night .hljs-addition { background-color:#2a9292 !important } .night .hljs { display:block !important; overflow-x:auto !important; background:#19171c !important; color:#8b8792 !important; /*padding:0.5em !important*/ } .night .hljs-emphasis { font-style:italic !important } .night .hljs-strong { font-weight:bold !important } `; } if (FLAGS.USERSTYLE_CONFIG_INTERFACE) { userStyle += ` .config_button { box-sizing: border-box; position: fixed; width: ${BUTTON_SIZE}px; height: ${BUTTON_SIZE2}px; right: ${BUTTON_SIZE}px; bottom: ${FLAGS.NIGHT_MODE ? BUTTON_SIZE4 : BUTTON_SIZE}px; z-index: 10000; background: -webkit-linear-gradient(top, #aaa 50%, transparent 50%); background: -moz-linear-gradient(top, #aaa 50%, transparent 50%); background: -moz-linear-gradient(top, #aaa 50%, transparent 50%); background-size: 10px 10px; transition: background 0.1s ease-out; } .config_button:hover { background: -webkit-linear-gradient(top, #333 50%, transparent 50%); background: -moz-linear-gradient(top, #333 50%, transparent 50%); background: -moz-linear-gradient(top, #333 50%, transparent 50%); background-size: 10px 10px; } .night .config_button { background: -webkit-linear-gradient(top, #515151 50%, transparent 50%); background: -moz-linear-gradient(top, #515151 50%, transparent 50%); background: -moz-linear-gradient(top, #515151 50%, transparent 50%); background-size: 10px 10px; } .night .config_button:hover { background: -webkit-linear-gradient(top, #9e9e9e 50%, transparent 50%); background: -moz-linear-gradient(top, #9e9e9e 50%, transparent 50%); background: -moz-linear-gradient(top, #9e9e9e 50%, transparent 50%); background-size: 10px 10px; } .config_frame { box-sizing: border-box; position: fixed; right: 80px; bottom: 32px; z-index: 10000; border: 1px solid #aaa; padding: 8px; background: #f7f7f7; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; overflow-y: auto; max-height: calc(100vh - 64px); min-width: 390px; } .config_frame label:hover { cursor: pointer; background: rgba(128, 128, 128, 0.3); } .config_frame input { cursor: pointer; position: absolute; opacity: 0; } .config_frame input + span:before { content: ''; display: inline-block; width: 0.5em; height: 0.5em; margin: 0 0.4em 0.1em 0.3em; outline: 1px solid currentcolor; outline-offset: 1px; } .config_frame input:checked + span:before { background: currentcolor; } .night .config_frame { background: #22272B; border-color: #393d41; } `; } userStyleEl.innerHTML = userStyle; const navigatorEdge = /Edge/.test(navigator.userAgent); function readyHead(fn) { if (document.body) { // если есть body, значит head готов fn(); } else if (document.documentElement && !navigatorEdge) { const observer = new MutationObserver(() => { if (document.body) { observer.disconnect(); fn(); } }); observer.observe(document.documentElement, { childList: true }); } else { // рекурсивное ожидание появления DOM setTimeout(() => readyHead(fn), 16); } } readyHead(() => { if (document.getElementById('habrafixmarker')) return; if (FLAGS.USERSTYLE) document.head.appendChild(userStyleEl); if (FLAGS.NIGHT_MODE && userConfig.getItem('night_mode')) { document.documentElement.classList.add('night'); } }); function ready(fn) { const { readyState } = document; if (readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { fn(); }); } else { fn(); } } ready(() => { if (document.getElementById('habrafixmarker')) return; if (FLAGS.COMMENTS_MD) { const mdSelectorEl = document.getElementById('comment_markdown'); if (mdSelectorEl) { if (userConfig.getItem('comment_markdown')) mdSelectorEl.checked = true; mdSelectorEl.addEventListener('input', () => { userConfig.setItem('comment_markdown', mdSelectorEl.checked); }); } } if (FLAGS.HTSO_BTN) { const commentForm = document.getElementById('comment-form'); if (commentForm) { const toolbar = commentForm.querySelector('.tm-editor__toolbar'); if (toolbar) { const item = document.createElement('li'); item.classList.add('wysiwyg-toolbar__item'); item.innerHTML = ` `; toolbar.appendChild(item); } } } if (FLAGS.SUBS_BTN) { const userBtn = document.querySelector('.tabs-menu__item_link'); const isUserPage = /^\/(ru|en)\/users\/[^/]+\//.test(window.location.pathname); if (userBtn && isUserPage) { const bar = userBtn.parentElement; const tab = document.createElement('a'); const isSubs = /subscription\/$/.test(window.location.pathname); tab.classList.add('tabs-menu__item', 'tabs-menu__item_link'); tab.href = `${userBtn.href}subscription/`; tab.innerHTML = `

Он читает

`; bar.appendChild(tab); const tab2 = document.createElement('a'); const isFols = /followers\/$/.test(window.location.pathname); tab2.classList.add('tabs-menu__item', 'tabs-menu__item_link'); tab2.href = `${userBtn.href}subscription/followers/`; tab2.innerHTML = `

Его читают

`; bar.appendChild(tab2); } } // надо ли ещё Array.from(document.querySelectorAll('iframe[src^="https://codepen.io/"]')) .map(el => el.setAttribute('scrolling', 'no')); // остановка гифок по клику и воспроизведение при повторном клике function toggleGIF(el) { // если атрибут со старым линком пуст или отсутствует if (!el.dataset.oldSrc) { // заменим ссылку на data-url-svg с треугольником в круге const w = Math.max(el.clientWidth || 256, 16); const h = Math.max(el.clientHeight || 128, 16); const cx = w / 2; const cy = h / 2; const r = Math.min(w, h) / 4; const ax = (r * 61) / 128; const by = (r * 56) / 128; const bx = (r * 35) / 128; const svg = `data:image/svg+xml;utf8, `; el.dataset.oldSrc = el.getAttribute('src'); // eslint-disable-line no-param-reassign el.setAttribute('src', svg); } else if (FLAGS.GIF_STOP_OVERTYPE) { // иначе поставим svg с троеточием const w = el.clientWidth; const h = el.clientHeight; const cx = w / 2; const cy = h / 2; const r = Math.min(w, h) / 4; const r2 = r / 4; const svg = `data:image/svg+xml;utf8, `; el.setAttribute('src', svg); // когда отрендерится троеточие, можно менять на исходную гифку setTimeout(() => { if (el.dataset.oldSrc) { el.setAttribute('src', el.dataset.oldSrc); el.dataset.oldSrc = ''; // eslint-disable-line no-param-reassign } }, 100); } else { const img = document.createElement('img'); img.setAttribute('src', el.dataset.oldSrc); if (el.hasAttribute('align')) { img.setAttribute('align', el.getAttribute('align')); } el.parentNode.insertBefore(img, el); img.onclick = () => toggleGIF(img); // eslint-disable-line no-param-reassign el.parentNode.removeChild(el); } } if (FLAGS.GIF_STOP) { Array.from(document.querySelectorAll('.post__text img[src$=".gif"], .comment__message img[src$=".gif"]')) .filter((el) => { const excludes = [ 'https://habrastorage.org/storage3/976/d3e/38a/976d3e38a34b003f86f91795524af9f8.gif', 'https://habrastorage.org/storage3/2e2/522/737/2e2522737ec404a9f76047e108dfaea0.gif', 'https://habrastorage.org/getpro/habr/post_images/d4b/289/ef0/d4b289ef0a00e969108c25d0c3d75f58.gif', ]; return !excludes.includes(el.getAttribute('src')); }) .forEach((el) => { if (FLAGS.GIF_STOP_ONLOAD) toggleGIF(el); el.classList.add('habrafix_gif-stop'); el.onclick = () => toggleGIF(el); // eslint-disable-line no-param-reassign }); } // счетчики кармы if (FLAGS.KARMA_DETAILS) { Array.from(document.querySelectorAll('.user-info__stats-item.stacked-counter')).forEach((itemCounter) => { itemCounter.style.marginRight = '16px'; // eslint-disable-line no-param-reassign }); Array.from(document.querySelectorAll('.page-header__stats_karma')).forEach((karmaEl) => { karmaEl.style.width = 'auto'; // eslint-disable-line no-param-reassign karmaEl.style.minWidth = `${KARMA_WIDTH}px`; // eslint-disable-line no-param-reassign }); Array.from(document.querySelectorAll(` .stacked-counter[href="https://habr.com/ru/info/help/karma/"], .stacked-counter[href="https://habr.com/en/info/help/karma/"] `)).forEach((counterEl) => { let total = parseInt(counterEl.title, 10); const scoreEl = counterEl.querySelector('.stacked-counter__value'); if (!scoreEl || !total) return; counterEl.style.width = 'auto'; // eslint-disable-line no-param-reassign counterEl.style.minWidth = `${KARMA_WIDTH}px`; // eslint-disable-line no-param-reassign const score = parseFloat(scoreEl.innerHTML.replace('–', '-').replace(',', '.'), 10); if (score > total) total = score; const likes = (total + score) / 2; const percent = Math.round((100 * likes) / total); const details = ` = ${total} × (${percent} − ${100 - percent})%`; const detailsEl = document.createElement('span'); detailsEl.innerHTML = details; detailsEl.style.color = '#545454'; detailsEl.style.fontFamily = '"-apple-system",BlinkMacSystemFont,Arial,sans-serif'; detailsEl.style.fontSize = '13px'; detailsEl.style.fontWeight = 'normal'; detailsEl.style.verticalAlign = 'middle'; scoreEl.appendChild(detailsEl); counterEl.title += `, ${(likes).toFixed(2)} плюсов и ${(total - likes).toFixed(2)} минусов`; // eslint-disable-line no-param-reassign }); } // счетчики рейтинга с подробностями const scoresMap = new Map(); class Score { constructor(el) { this.el = el; this.parentEl = el.parentNode; const data = this.constructor.parse(el); this.rating = data.rating; this.total = data.total; this.likes = data.likes; this.dislikes = data.dislikes; this.isDetailed = false; this.observer = new MutationObserver(() => this.update()); } setDetails(isDetailed) { if (this.isDetailed === isDetailed) return; this.isDetailed = isDetailed; this.update(); } update() { const newChild = this.parentEl.querySelector('.voting-wjt__counter, .post-stats__result-counter'); if (!newChild) return; this.el = newChild; const data = this.constructor.parse(this.el); this.rating = data.rating; this.total = data.total; this.likes = data.likes; this.dislikes = data.dislikes; this.observer.disconnect(); if (this.isDetailed) { this.details(); } else { this.simply(); } this.observer.observe(this.parentEl, { childList: true }); } static parse(el) { let [, likes, dislikes] = el .attributes.title.textContent .match(/[0-9]+/g).map(Number); let total = likes + dislikes; let [, sign, rating] = el.innerHTML.match(/([–]?)(\d+)/); // eslint-disable-line prefer-const rating = Number(rating); if (sign) rating = -rating; // не знаю что там происходит при голосовании, так что на всякий случай const diff = rating - (likes - dislikes); if (diff < 0) { total += Math.abs(diff); dislikes += Math.abs(diff); } else if (diff > 0) { total += diff; likes += diff; } return { rating, total, likes, dislikes, }; } simply() { let innerHTML = ''; if (this.rating > 0) { innerHTML = `+${this.rating}`; } else if (this.rating < 0) { innerHTML = `–${Math.abs(this.rating)}`; } else { innerHTML = '0'; } this.el.innerHTML = innerHTML; } details() { let innerHTML = ''; if (this.rating > 0) { innerHTML = `+${this.rating}`; } else if (this.rating < 0) { innerHTML = `–${Math.abs(this.rating)}`; } else { innerHTML = '0'; } if (this.total !== 0) { let details = ''; if (FLAGS.RATING_DETAILS_PN) { const percent = Math.round((100 * this.likes) / this.total); details = ` = ${this.total} × (${percent} − ${100 - percent})%`; } else { details = ` = ${this.likes} − ${this.dislikes}`; } innerHTML += ` ${details}`; } this.el.innerHTML = innerHTML; } } // парсим их Array.from(document.querySelectorAll('.voting-wjt__counter, .post-stats__result-counter')).forEach((el) => { scoresMap.set(el, new Score(el)); }); // добавляем подробностей if (FLAGS.RATING_DETAILS) { if (FLAGS.RATING_DETAILS_ONCLICK) { const isDetailed = userConfig.getItem('scores_details'); if (isDetailed) scoresMap.forEach(score => score.setDetails(isDetailed)); scoresMap.forEach((score) => { score.el.onclick = () => { // eslint-disable-line no-param-reassign const nowDetailed = userConfig.shiftItem('scores_details'); scoresMap.forEach(s => s.setDetails(nowDetailed)); }; }); } else { scoresMap.forEach(score => score.setDetails(true)); } } // метки времени и работа с ними const pageLoadTime = new Date(); const monthNames = [ 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря', ]; const monthNamesEng = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; class HabraTime { constructor(el, parent) { this.el = el; this.parent = parent; this.attrDatetime = this.constructor.getAttributeDatetime(el); this.date = new Date(this.attrDatetime); } // вот было бы хорошо, если б на хабре были datetime атрибуты static getAttributeDatetime(el) { const imagination = el.getAttribute('datetime') || el.getAttribute('data-time_published'); if (imagination) return imagination; let recently; let day; let month; let year; let time; let isEng = false; let meridiem; if (/^\d\d:\d\d$/.test(el.innerHTML)) { [, time] = el.innerHTML.match(/(\d\d:\d\d)/); recently = 'сегодня'; } else if (/^\d\d:\d\d (AM|PM)$/.test(el.innerHTML)) { [, time, meridiem] = el.innerHTML.match(/(\d\d:\d\d) (AM|PM)/); recently = 'сегодня'; } else if (/at/.test(el.innerHTML)) { isEng = true; const re = /((today|yesterday)|([A-z]+) (\d+), (\d+)) at (\d\d:\d\d) (AM|PM)/; [,, recently, month, day, year, time, meridiem, ] = el.innerHTML.match(re); if (recently === 'today') { recently = 'сегодня'; } else if (recently === 'yesterday') { recently = 'вчера'; } } else { const re = /((сегодня|вчера)|(\d+)[ .]([а-я]+|\d+)[ .]?(\d+)?) в (\d\d:\d\d)/; [,, recently, day, month, year, time, ] = el.innerHTML.match(re); } const [, h, m] = time.match(/(\d\d):(\d\d)/); if (meridiem === 'PM') { time = `${Number(h === '12' ? 0 : h) + 12}:${m}`; } else if (meridiem === 'AM') { time = `${h === '12' ? '00' : h}:${m}`; } // и местное время let moscow; if (recently || year === undefined) { const offsetMoscow = 3 * 60 * 60 * 1000; const yesterdayShift = (recently === 'вчера') ? 24 * 60 * 60 * 1000 : 0; const offset = pageLoadTime.getTimezoneOffset() * 60 * 1000; const value = (pageLoadTime - yesterdayShift) + offsetMoscow + offset; moscow = new Date(value); } if (recently) { day = moscow.getDate(); month = moscow.getMonth() + 1; } else if (month.length !== 2) { month = (isEng ? monthNamesEng : monthNames).indexOf(month) + 1; } else { month = +month; } if (day < 10) day = `0${+day}`; if (month < 10) month = `0${month}`; if (year < 100) year = `20${year}`; if (year === undefined) year = moscow.getFullYear(); return `${year}-${month}-${day}T${time}+03:00`; } absolute() { let result = ''; const time = this.date; const day = time.getDate(); const month = time.getMonth(); const monthName = monthNames[month]; const year = time.getFullYear(); const hours = time.getHours(); const minutes = time.getMinutes(); const now = new Date(); const nowDay = now.getDate(); const nowMonth = now.getMonth(); const nowYear = now.getFullYear(); const yesterday = new Date(now - (24 * 60 * 60 * 1000)); const yesterdayDay = yesterday.getDate(); const yesterdayMonth = yesterday.getMonth(); const yesterdayYear = yesterday.getFullYear(); const hhmm = `${hours}:${minutes >= 10 ? minutes : `0${minutes}`}`; const isToday = day === nowDay && month === nowMonth && year === nowYear; const isYesterday = day === yesterdayDay && month === yesterdayMonth && year === yesterdayYear; if (isToday) { result = `сегодня в ${hhmm}`; } else if (isYesterday) { result = `вчера в ${hhmm}`; } else if (nowYear === year) { result = `${day} ${monthName} в ${hhmm}`; } else { result = `${day} ${monthName} ${year} в ${hhmm}`; } return result; } static relative(milliseconds) { let result = ''; const pluralForm = (n, forms) => { if (n % 10 === 1 && n % 100 !== 11) return forms[0]; if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return forms[1]; return forms[2]; }; const formats = [ ['год', 'года', 'лет'], ['месяц', 'месяца', 'месяцев'], ['день', 'дня', 'дней'], ['час', 'часа', 'часов'], ['минуту', 'минуты', 'минут'], ]; const minutes = milliseconds / 60000; const hours = minutes / 60; const days = hours / 24; const months = days / 30; const years = months / 12; const idx = [years, months, days, hours, minutes].findIndex(x => x >= 1); if (idx === -1) { result = 'несколько секунд'; } else { const value = Math.floor([years, months, days, hours, minutes][idx]); const forms = formats[idx]; const form = pluralForm(value, forms); result = `${value} ${form}`; } return result; } fromNow() { const diff = Math.abs(Date.now() - this.date); return `${this.constructor.relative(diff)} назад`; } fromParent() { const diff = Math.abs(this.date - this.parent.date); return `через ${this.constructor.relative(diff)}`; } static datetimeToMsk(datetime) { const [, yyyy, mm, dd, h, m] = datetime .match(/([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+)/); return `${Number(dd)} ${monthNames[mm - 1]} ${yyyy} в ${h}:${m}`; } } // собираем метки времени const datesMap = new Map(); const megapostTimeEl = document.querySelector('.megapost-head__meta > .list_inline > .list__item'); (megapostTimeEl ? [megapostTimeEl] : []) .concat(Array.from(document.querySelectorAll(` .post__time, .preview-data__time-published, time.comment__date-time_published, .tm-post__date, .user-message__date-time, .news-topic__attr_date-time `))).forEach((el) => { datesMap.set(el, new HabraTime(el)); }); function updateTime(el) { datesMap.forEach((habraTime) => { if ( !habraTime.el || !document.body.contains(habraTime.el) || (el && el !== habraTime.el) ) return; let type; let otherTypes; if (habraTime.parent) { type = userConfig.config.time_comments; otherTypes = userConfig.model.time_comments .filter(str => str !== type); } else { type = userConfig.config.time_publications; otherTypes = userConfig.model.time_publications .filter(str => str !== type); } const title = otherTypes.map(otherType => habraTime[otherType]()).join(', '); habraTime.el.innerHTML = habraTime[type](); // eslint-disable-line no-param-reassign habraTime.el.setAttribute('title', title); }); } if (FLAGS.TIME_DETAILS) { datesMap.forEach((habraTime) => { habraTime.el.setAttribute( 'style', 'cursor: pointer; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; user-select: none;', ); habraTime.el.onclick = () => { // eslint-disable-line no-param-reassign if (habraTime.parent) { userConfig.shiftItem('time_comments'); } else { userConfig.shiftItem('time_publications'); } updateTime(); }; }); // подождём, когда дерево комментариев будет построено // у некоторых меток времени будут установлены родители // тогда и обновим их тексты setTimeout(updateTime, 100); setInterval(updateTime, 30 * 1000); } // время публикации, понадобится для корня древа комментариев let datePublication = datesMap.get(megapostTimeEl || document.querySelector('.post__time')); // если нету публикации поищем самую раннюю метку времени if (!datePublication) { datePublication = { date: pageLoadTime }; datesMap.forEach((date) => { if (date.date < datePublication.date) datePublication = date; }); } if (FLAGS.FIND_COMMENTS) { const commentsList = document.querySelector('.user_comments'); const match = document.location.pathname.match(/users\/([^/]+)\/comments/i); if (match && commentsList) { const nickname = match[1]; const originalTitle = document.title; const searchForm = document.createElement('div'); searchForm.classList.add('search-form', 'search-form_expanded'); searchForm.style.width = 'auto'; searchForm.innerHTML = ` `; const notFoundLabel = document.createElement('p'); notFoundLabel.style.textAlign = 'center'; notFoundLabel.style.fontSize = '18px'; notFoundLabel.style.color = 'gray'; notFoundLabel.style.display = 'none'; notFoundLabel.textContent = 'Ничего не найдено'; const commentsSubList = document.createElement('ul'); commentsSubList.classList.add('content-list', 'content-list_comments'); commentsSubList.id = 'search-comments'; commentsSubList.style.display = 'none'; commentsList.insertBefore(commentsSubList, commentsList.firstChild); commentsList.insertBefore(notFoundLabel, commentsList.firstChild); commentsList.insertBefore(searchForm, commentsList.firstChild); const Progress = { set(value) { if (value === 1) { document.title = 'Поиск завершён'; this.setProgressBar(0); } else { const percent = Math.round(value * 100); document.title = `${percent}%, идёт поиск`; this.setProgressBar(value); } }, setProgressBar(value) { const percent = value * 100; document.getElementById('comments_search_label').style.background = ` linear-gradient(to right, rgba(84, 142, 170, 0.2) ${percent}%, transparent ${percent}%) `; }, reset() { this.setProgressBar(0); document.title = originalTitle; }, }; const makeComment = (comment) => { const commentEl = document.createElement('li'); commentEl.classList.add('content-list__item', 'content-list__item_comment', 'content-list__item_comment-plain'); let ratingClass = ''; let rating = '0'; if (comment.score > 0) { rating = `+${comment.score}`; ratingClass = 'voting-wjt__counter_positive'; } else if (comment.score < 0) { rating = `–${Math.abs(comment.score)}`; ratingClass = 'voting-wjt__counter_negative'; } let avatar; if (comment.avatar === 'https://habr.com/images/avatars/stub-user-middle.gif') { avatar = ``; } else { avatar = ``; } commentEl.innerHTML = `
${comment.post.title}
${comment.post.comments_count}
${rating}
${comment.message}
`; return commentEl; }; const addComment = (comment) => { document.getElementById('search-comments').appendChild(makeComment(comment)); const timeEl = document.querySelector(`#habrafix_comment_${comment.id} time`); if (FLAGS.TIME_DETAILS) { timeEl.setAttribute( 'style', 'cursor: pointer; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; user-select: none;', ); timeEl.onclick = () => { // eslint-disable-line no-param-reassign userConfig.shiftItem('time_publications'); updateTime(); }; datesMap.set(timeEl, new HabraTime(timeEl)); updateTime(timeEl); } else { timeEl.textContent = HabraTime.datetimeToMsk(comment.time_published); } }; // let fetchCors = url => fetch(`https://cors.io/?${url}`); let fetchCors = url => fetch(url); if (typeof GM !== 'undefined' && GM.xmlHttpRequest) { fetchCors = url => new Promise((resolve, reject) => GM.xmlHttpRequest({ method: 'GET', url, onload(response) { resolve({ json: () => Promise.resolve(JSON.parse(response.responseText)), }); }, onerror() { reject(); }, ontimeout() { reject(); }, })); } const loadComments = async (page) => { const url = `https://m.habr.com/kek/v1/users/${nickname}/comments?comments=true&user=${nickname}&page=${page}`; const res = await fetchCors(url); const json = await res.json(); return json.data; }; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const pageCache = {}; const memoizedLoadComments = async (page) => { if (pageCache[page]) { await sleep(0); return pageCache[page]; } const result = await loadComments(page); pageCache[page] = result; return result; }; let latestSearchId = 0; const removeResultsList = () => { document.getElementById('search-comments-field').value = ''; latestSearchId += 1; document.getElementById('search-comments').innerHTML = ''; document.getElementById('search-comments').style.display = 'none'; document.getElementById('comments').style.display = ''; const footer = document.querySelector('.page__footer'); if (footer) footer.style.display = ''; Progress.reset(); notFoundLabel.style.display = 'none'; searchForm.classList.remove('loading'); }; const addResultsList = () => { document.getElementById('search-comments').innerHTML = ''; document.getElementById('search-comments').style.display = ''; document.getElementById('comments').style.display = 'none'; const footer = document.querySelector('.page__footer'); if (footer) footer.style.display = 'none'; Progress.set(0); notFoundLabel.style.display = 'none'; searchForm.classList.add('loading'); }; document.getElementById('search-comments-clear').onclick = removeResultsList; // eslint-disable-next-line consistent-return const search = async () => { latestSearchId += 1; const currentSearchId = latestSearchId; const text = document.getElementById('search-comments-field').value; if (!text) return removeResultsList(); addResultsList(); const filter = (comment) => { const t = text.toLowerCase(); const message = comment.message.toLowerCase(); const title = comment.post.title.toLowerCase(); return message.includes(t) || title.includes(t); }; let pages = 1; for (let curPage = 1; curPage <= pages; curPage += 1) { let data; while (!data) { try { // eslint-disable-next-line no-await-in-loop data = await memoizedLoadComments(curPage); } catch (e) { document.title = 'Ошибка сети, повтор...'; // eslint-disable-next-line no-await-in-loop await sleep(5000); } } // eslint-disable-next-line consistent-return if (currentSearchId !== latestSearchId) return; // пользователь изменил текст pages = pages === 1 ? data.pages : pages; Progress.set(curPage / pages); data.comments .filter(filter) .forEach(addComment); } if (commentsSubList.childNodes.length === 0) notFoundLabel.style.display = ''; searchForm.classList.remove('loading'); }; const awaitEndOfInput = (func, ms) => { let timerId; return () => { clearTimeout(timerId); timerId = setTimeout(func, ms); }; }; document.getElementById('search-comments-field').oninput = awaitEndOfInput(search, 1000); } } // создаем дерево комментариев class ItemComment { constructor(el, parent) { this.parent = parent; this.el = el; this.lvl = parent.lvl + 1; this.id = Number(el.getAttribute('rel')); this.commentEl = el.querySelector('.comment'); if (this.commentEl) { this.timeEl = this.commentEl.querySelector('time'); this.ratingEl = this.commentEl.querySelector('.js-score'); } this.date = datesMap.get(this.timeEl); if (this.date) { this.date.parent = parent.date; } else { this.date = parent.date; } this.votes = scoresMap.get(this.ratingEl) || { total: 0, likes: 0, dislikes: 0, rating: 0, }; this.elList = el.querySelector('.content-list_nested-comments'); } existId(id) { return !!this.elList.querySelector(id); } existNew() { return !!this.elList.querySelector('.js-comment_new'); } getLength() { let { length } = this.list; this.list.forEach((node) => { length += node.getLength(); }); return length; } } class CommentsTree { constructor() { this.root = { isRoot: true, date: datePublication, lvl: 0, elList: document.getElementById('comments-list'), list: [], }; } static exist() { return !!document.getElementById('comments-list'); } update() { if (!this.root.elList) return; const recAdd = (node) => { node.list = Array.from(node.elList.children) // eslint-disable-line no-param-reassign .map(el => new ItemComment(el, node)); node.list.forEach(recAdd); }; recAdd(this.root); } walkTree(fn) { const walk = (tree) => { fn(tree); tree.list.forEach(walk); }; walk(this.root); } sort(fn) { if (!this.root.elList) return; this.walkTree((tree) => { tree.list.sort(fn).forEach(subtree => tree.elList.appendChild(subtree.el)); }); } shuffle() { if (!this.root.elList) return; const randInt = maximum => Math.floor(Math.random() * (maximum + 1)); this.walkTree((tree) => { const { list } = tree; for (let i = 0; i < list.length; i += 1) { const j = randInt(i); [list[i], list[j]] = [list[j], list[i]]; } list.forEach(subtree => tree.elList.appendChild(subtree.el)); }); } } const commentsTree = new CommentsTree(); commentsTree.update(); FLAGS.sortVariants = [ ['time', 'старые'], ]; if (FLAGS.COMMENTS_SORT_BY_FRESHNESS) FLAGS.sortVariants.push(['freshness', 'новые']); if (FLAGS.COMMENTS_SORT_BY_TREND) FLAGS.sortVariants.push(['trend', 'горячие']); if (FLAGS.COMMENTS_SORT_BY_QUALITY) FLAGS.sortVariants.push(['quality', 'хорошие']); if (FLAGS.COMMENTS_SORT_BY_REDDIT) FLAGS.sortVariants.push(['reddit', 'проверенные']); if (FLAGS.COMMENTS_SORT_BY_RATING) FLAGS.sortVariants.push(['rating', 'рейтинговые']); if (FLAGS.COMMENTS_SORT_BY_POPULARITY) FLAGS.sortVariants.push(['popularity', 'популярные']); if (FLAGS.COMMENTS_SORT_BY_RANDOM) FLAGS.sortVariants.push(['shuffle', 'случайные']); // здесь начинается сортировка комментариев const commentsOrderEl = document.createElement('div'); commentsOrderEl.classList.add('comments_order'); commentsOrderEl.innerHTML = FLAGS.sortVariants.map(([type, text]) => { const underline = (type === 'time') ? '; text-decoration: underline' : ''; return `${text}`; }).join(', '); if (FLAGS.COMMENTS_SORT && document.getElementById('comments-list')) { const commentsList = document.getElementById('comments-list'); commentsList.parentElement.insertBefore(commentsOrderEl, commentsList); } const commentsComparators = { time(a, b) { return a.id - b.id; }, freshness(a, b) { return b.id - a.id; }, rating(a, b) { const ascore = a.votes.rating; const bscore = b.votes.rating; if (bscore !== ascore) return bscore - ascore; return b.id - a.id; }, popularity(a, b) { const aVotes = a.votes.total; const bVotes = b.votes.total; if (aVotes !== bVotes) return bVotes - aVotes; const aLength = a.getLength(); const bLength = b.getLength(); if (aLength !== bLength) return bLength - aLength; return b.id - a.id; }, quality(a, b) { const aQuality = a.votes.rating / a.votes.total || 0; const bQuality = b.votes.rating / b.votes.total || 0; if (aQuality !== bQuality) return bQuality - aQuality; if (a.votes.rating !== b.votes.rating) return b.votes.rating - a.votes.rating; return b.id - a.id; }, trend(a, b) { // в первые сутки после публикации статьи число посещений больше чем в остальное время const oneDay = 24 * 60 * 60 * 1000; const firstDayEnd = +datePublication.date + oneDay; // у комментария есть только три дня на голосование с момента его создания const threeDays = 3 * oneDay; const now = Date.now(); // прикинем число голосов в первый день const aDate = +a.date.date; let aViews = 0; // в первый день if (aDate <= firstDayEnd) { aViews += Math.min(firstDayEnd, now) - aDate; } // и в остальное время if (now >= firstDayEnd) { const threeDaysEnd = aDate + threeDays; // для этого соотношения я собрал статистику aViews += (Math.min(threeDaysEnd, now) - Math.max(firstDayEnd, aDate)) / 16; } const aScore = a.votes.rating / aViews; // аналогично const bDate = +b.date.date; let bViews = 0; if (bDate <= firstDayEnd) { bViews += Math.min(firstDayEnd, now) - bDate; } if (now >= firstDayEnd) { const threeDaysEnd = bDate + threeDays; // найти зависимость активности голосования от времени суток не удалось bViews += (Math.min(threeDaysEnd, now) - Math.max(firstDayEnd, bDate)) / 16; } const bScore = b.votes.rating / bViews; if (bScore === aScore) return b.id - a.id; return bScore - aScore; }, reddit(a, b) { const wilsonScore = (ups, downs) => { const n = ups + downs; if (n === 0) return 0; const z = 1.281551565545; const p = ups / n; const left = p + ((1 / (2 * n)) * z * z); const right = z * Math.sqrt(((p * (1 - p)) / n) + ((z * z) / (4 * n * n))); const under = 1 + ((1 / n) * (z * z)); return (left - right) / under; }; const aScore = wilsonScore(a.votes.likes, a.votes.dislikes); const bScore = wilsonScore(b.votes.likes, b.votes.dislikes); if (bScore === aScore) return b.id - a.id; return bScore - aScore; }, }; const sortComments = () => { const order = userConfig.getItem('comments_order'); Array.from(commentsOrderEl.children).forEach((el) => { if (el.dataset.order === order) { el.style.textDecoration = 'underline'; // eslint-disable-line no-param-reassign } else { el.style.textDecoration = ''; // eslint-disable-line no-param-reassign } }); if (order === 'shuffle') { commentsTree.shuffle(); } else { const compare = commentsComparators[order]; commentsTree.sort(compare); } }; // сортируем комменты при загрузке страницы // или не сортируем, если они уже по порядку if (FLAGS.COMMENTS_SORT && FLAGS.COMMENTS_SORT_ONLOAD && userConfig.getItem('comments_order') !== 'time') { sortComments(); } Array.from(commentsOrderEl.children).forEach((el) => { el.onclick = () => { // eslint-disable-line no-param-reassign userConfig.setItem('comments_order', el.dataset.order); sortComments(); }; }); // меняем ссылки ведущие к новым комментариям на ссылки к началу комментариев if (FLAGS.COMMENTS_LINKS) { const commentsLinks = document.getElementsByClassName('post-stats__comments-link'); for (let i = 0; i < commentsLinks.length; i += 1) { const iLink = commentsLinks[i]; const hrefValue = iLink.getAttribute('href'); const hrefToComments = hrefValue.replace('#first_unread', '#comments'); iLink.setAttribute('href', hrefToComments); } } // сворачивание комментов // eslint-disable-next-line no-constant-condition if (false) { // FLAGS.COMMENTS_HIDE const commentHash = window.location.hash; const toggle = (subtree) => { const listLength = subtree.list.length; if (listLength === 0) return; /* eslint-disable */ if (subtree.switcherEl.dataset.isVisibleList === 'true') { subtree.switcherEl.dataset.isVisibleList = 'false'; subtree.switcherEl.innerHTML = `\u229E раскрыть ветку ${subtree.getLength()}`; subtree.elList.style.display = 'none'; } else { subtree.switcherEl.dataset.isVisibleList = 'true'; subtree.switcherEl.innerHTML = '\u229F'; subtree.elList.style.display = 'block'; } /* eslint-enable */ }; commentsTree.walkTree((subtree) => { // не пытаемся сворачивать корень if (subtree.isRoot) return; // у похищенных нет футера const footerEl = subtree.commentEl.querySelector('.comment__footer'); if (footerEl === null) return; // создаём переключатель const switcher = document.createElement('a'); switcher.classList.add('comment__footer-link'); switcher.classList.add('comment__switcher'); switcher.dataset.isVisibleList = 'true'; switcher.innerHTML = '\u229F'; if (subtree.list.length === 0) switcher.innerHTML = '\u22A1'; switcher.style.cursor = 'pointer'; switcher.style.marginLeft = '-5px'; footerEl.insertBefore(switcher, footerEl.children[0]); subtree.switcherEl = switcher; // eslint-disable-line no-param-reassign switcher.onclick = () => toggle(subtree); const isHideLvl = subtree.lvl === FLAGS.HIDE_LEVEL && FLAGS.HIDE_LEVEL_4; const isLineLvl = subtree.lvl % FLAGS.LINE_LEN === 0; if (isLineLvl) { subtree.elList.classList.add('comments_new-line'); const lineNumber = subtree.lvl / FLAGS.LINE_LEN; subtree.elList.classList.add(`comments_new-line-${lineNumber % 4}`); } // при запуске не сворачиваем ветки с новыми комментами, и содержащие целевой id if ( (isHideLvl || isLineLvl) && !subtree.existNew() && !(commentHash && subtree.existId(commentHash)) ) { toggle(subtree); } }); } if (FLAGS.SCROLL_LEGEND) { const postBodyEl = document.querySelector('.post__body_full') || document.querySelector('.article__body'); const commentsEl = document.getElementById('comments-list'); const getPercents = (el) => { if (!el) return { topPercent: 0, heightPercent: 0 }; const pageHeight = document.documentElement.scrollHeight; const top = el.getBoundingClientRect().top + window.pageYOffset; const topPercent = ((100 * top) / pageHeight).toFixed(2); const height = el.clientHeight; const heightPercent = ((100 * height) / pageHeight).toFixed(2); return { topPercent, heightPercent }; }; const updateLegend = (pageEl, legendEl) => { const { topPercent, heightPercent } = getPercents(pageEl); legendEl.style.top = `${topPercent}%`; // eslint-disable-line no-param-reassign legendEl.style.height = `${heightPercent}%`; // eslint-disable-line no-param-reassign }; const legendPost = document.createElement('div'); legendPost.classList.add('legend_el'); legendPost.style.background = 'rgba(84, 142, 170, 0.66)'; updateLegend(postBodyEl, legendPost); document.body.appendChild(legendPost); const legendComments = document.createElement('div'); legendComments.classList.add('legend_el'); legendComments.style.background = 'rgba(49, 176, 7, 0.66)'; updateLegend(commentsEl, legendComments); document.body.appendChild(legendComments); setInterval(() => { updateLegend(postBodyEl, legendPost); updateLegend(commentsEl, legendComments); }, 1000); } if (FLAGS.NIGHT_MODE) { const switcherEl = document.createElement('div'); switcherEl.classList.add('night_mode_switcher'); switcherEl.onclick = () => { const isNightMode = userConfig.shiftItem('night_mode'); document.documentElement.classList.toggle('night', isNightMode); }; document.body.appendChild(switcherEl); setInterval(() => { const boolClass = document.documentElement.classList.contains('night'); const isNightMode = userConfig.getItem('night_mode'); if (boolClass !== isNightMode) { document.documentElement.classList.toggle('night', isNightMode); } }, 1000); } if (FLAGS.CONFIG_INTERFACE) { const configFrame = document.createElement('div'); configOptions.forEach(([key, text]) => { if (typeof FLAGS[key] !== 'boolean') return; const inputEl = document.createElement('input'); inputEl.type = 'checkbox'; inputEl.value = key; inputEl.checked = FLAGS[key]; const labelEl = document.createElement('label'); labelEl.setAttribute('unselectable', 'on'); labelEl.setAttribute('onselectstart', 'return false'); const spanEl = document.createElement('span'); spanEl.innerHTML = text; configFrame.appendChild(labelEl); labelEl.appendChild(inputEl); labelEl.appendChild(spanEl); inputEl.onchange = () => { FLAGS[key] = inputEl.checked; localStorage.setItem('habrafixFlags', JSON.stringify(FLAGS)); }; configFrame.appendChild(document.createElement('br')); }); const reloadText = document.createElement('div'); reloadText.style.textAlign = 'right'; reloadText.innerHTML = ` * чтобы увидеть изменения обновите страницу `; configFrame.appendChild(reloadText); configFrame.classList.add('config_frame'); configFrame.style.display = 'none'; document.body.appendChild(configFrame); const configButton = document.createElement('div'); configButton.classList.add('config_button'); document.body.appendChild(configButton); configButton.onclick = () => { if (configFrame.style.display) { configFrame.style.display = ''; } else { configFrame.style.display = 'none'; } }; } setTimeout(() => { const marker = document.createElement('meta'); marker.id = 'habrafixmarker'; document.head.appendChild(marker); }, 300); });