Продолжение трекер-саги. Краткое содержание предыдущей серии — некоторые пользователей Хабра, в основном из тех кто одновременно участвует в нескольких дисcкусиях, активно используют функциональность трекера.
С переходом на новую версию дизайна, концепция трекера кардинально поменяла предоставляемый набор данных и функциональность в сравнении со старой версией. Не будем углубляться в причины этого, на данном этапе это есть объективная реальность, данная нам в ощущения.
В связи с ожиданиями окончания поддержки старой версии и её декомиссии, время от времени те пользователи которые привыкли к работе в парадигме старой версии подают прошения богам Хабра , но пока безуспешно. Похоже что прошений недостаточно, а дары пока не пробовали (ну или не тем богам молились).
В комментах к подобной статье-прошении от @vconst, по поводу судьбы трекера, я предположил что в крайнем случае можно взять это дело в свои руки и если гора не идёт к Магомеду, попробовать самим эту гору сравнять. Тем более, что каждый первый из читателей Хабра в той или иной мере владеет инструментарием позволяющим этого добиться.
В качестве пробы пера, я написал скрипт для старой версии сайта, при внедрении которого на страницу, позволяющий добавить секцию трекера в правый сайдбар.
На удивление это случилось сильно проще чем ожидалось и лично для меня это оказалась довольно удобно.
Но тот скрипт не решал основную проблему — мигрировать функциональность старого трекера в новую версию.
Для достижения этого существует вполне прямолинейный способ — читаем данные со страницы нового трекера, и потом для тех статей которые имеют новые комментарии, читаем страницу статьи для получения недостающих данных. В данном сценарии существует одно неудобство — придётся считывать множество страниц сайта (потенциально десятки) для формирования требуемого набора данных.
Поэтому переключившись в инферфейс новой версии, я углубился в изучение функциональности нового дизайна.
К сожалению чуда не произошло и в тёмных закоулках html новой версии не удалось обнаружить недостающих залежей данных. Я было приготовился к реализации воркера который время от времени оббегает страницы и обновляет статистики, но случайно обнаружил страницу, которая по сути является тем же трекером, но под другим именем — «закладки».
И самое интересное что требуемые данные уже отображаются на странице закладок.
В самом простом случае, пользователи новой версии могут незначительно изменить свои привычки и приспособиться использовать галочку «добавить в закладки», вместо «добавить в трекер». Ну может быть добавить CSS правило для сокрытия превью для статей. И получится вполне себе рабочее решение.
Но на всякий случай, я всё же подшаманил скрипт для старой версии и приспособил его для использования закладок в качестве трекера на правом сайдбаре. (Скрипт можно найти в конце этой статьи)
Оказалось что результат практически не отличается от такового для старой версии. И количество страниц которые необходимо прочитать, всего лишь одна или две, ну может три, вместо десятка.

Конечно можно чуток и его улучшить — добавить автодобавление в закладки при добавление в трекер. Или реализовать автоудаление из закладок при отсутствии активностей в комментариях в статье за прошедшую неделю и т.д. и т.п. Всё это легко реализуется, было бы желание.
Но как же быть с закладками спросит пытливый читатель? И конечно будет прав. Есть два способа решения этой дилеммы.
Первый это мирное сосуществование.
Как мне кажется, статьи заслуживающие быть добавленными в закладки встречаются довольно редко, поэтому это не должно стать проблемой. Тем более трекать комменты в статье которую вы реально поместили в закладки ради закладки тоже вполне полезно.
Второй способ, это использование для закладок, один из множества уже существующих универсальных сервисов или плагинов для организации закладок. Они специализируются на этом и имеют множество дополнительных полезных фич, типа поиск внутри статей из закладок, трекинг обновлений, добавлений кастомных тэгов и комментирование контента страницы.
И особенно обратить внимание на те из них, которые на момент добавления в закладки создаёт скриншот или PDF страницы.
Это кстати решит давнюю проблему закладок Хабра — пропажу страниц при их удалении автором. Это конечно не частое явление и не конец света, в конце концов можно на одном из зеркал их отыскать, но всё же. Так что я лично склоняюсь ко второму варианту.
Кстати, можете поделится своими любимыми сервисами в комментах, может кому пригодится. Лично я пользуюсь букмарками самого браузера, организовав древовидную структуру папок.
Кстати, нет худа без добра — пока смотрел на структуру сайта для реализации трекера, заодно решил проблему которая время от времени меня подбешивала — случайное обновление страницы и пропажа выделения новых комментов.
Если интересно, опубликую и это решение, но скрипт надо бы чуток причесать. Если же нету интереса к нему — мне ок и нечёсанным пользоваться, что время зазря терять.
На этом я с данной сагой завязываю, считаю что это вполне рабочее решение, хоть и не идеальное, но тратить время на дальнейшие эксерсайзы нету большого желания.
Если у вас есть идеи того что ещё можно сделать с функциональностью Хабра, чтобы ширнармассам стало удобнее — отпишитесь в комментах, может быть удасться и их реализовать.
Ну и конечно в отличии от расширений браузера, выделеный сервер с краулером комментов и статей с лёгкостью позволит реализовать 99% хотелок, с гораздо лучшей производительностью. Увы, но мне его поддерживать не с руки, если вы в себе чувствуете потенциал для этого, дерзайте, я готов по мере сил подсобить.
У меня есть задумки:
1. Комментирование контента статьи, т.е. возможность добавления замечаний или просто выделение цветом частей текста, с возможностью делиться этим с другими участниками.
2. Сохранение историй изменений, т.е. чтобы было можно увидеть различия между статьёй и комментами различных версий.
3. Трекинг комментариев под моим комментом или всей веткой в которой я что либо писал.
4. Трекинг активностей и ведение профайлов некоторых пользователей (добавлять свои комменты для них, назначать свои иконки чтобы сразу было понятно, выделять их комменты цветом, скрывать их)
5. Прокси для внедрения кастомных скриптов/css для мобильной версии сайта (многие мобильные браузеры не поддерживают расширения в мобильных версиях, а хочется)
6. Создание тематических подборок или роадмапов из статей и возможности ими управлять группой товарищей.
7. Функцию «совет аксакала» — возможность пользователям советовать статьи и подписываться на их ленту(ы), чтобы не пропустить интересный материал (не только с хабра)
8. Организовать ленты — «лучшее в этот день год назад»/»три/пять лет назад»/»забытые бриллианты» — чтобы постоянно шла лента ретроспективы старых статей.
9. Бот(ы) для мессенджеров
10. Отдавать полный архив, а также дневные, недельные и месячные дельты всем желающим. Пущай играются с данными, может интересное получится.
11. Отдавать фиды статей и комментов через API , тоже самое — пусть желающие творят добро.
12 Трекинг моих плюсов и минусов для статей и комментов, иногда было бы интересно глянуть на свои былые подпрыгивания.
Я не думаю что имеет смысл создавать ещё одно зеркало хабра, в дополнение к десятку уже существующих, но сервер который поможет данными приложениям и расширениям, конечно будет полезным дополнением к самому сайту.
Код скрипта для новой версии
(function () { function injectTracker() { const MAX_TRACKS = 30; const MAX_PAGES = 3; // To add the tracker as a first block on the right sidebar, change "false" to "true" const insertAsFirstBlock = false; const userName = getUserName(); if(!userName) return; // styling const trackerBlockCss = `<style scoped> @keyframes rotate { 0% { transform: rotate(0); } 100% { transform: rotate(360deg); } } svg.tm-article-comments-counter-link__icon { display: none; } .post-info__title a { color: #444; } a[href*="#comments"] { color: #82a3b1; } a[href*="#first_unread"] { color: #cf0000; } .tracker-stats-info { display: flex; } a.tm-block__title { text-decoration: none; } .tm-user-info__username, span.tracker * { font-weight: normal; } span.tracker { position: absolute; right: 0; display: flex; margin-left: auto; padding-right: 1em; } span.tracker-hide-or-remove { right: 1em; display: none; position: absolute; background: white; } li:hover .tm-article-title span.tracker-hide-or-remove { display: inline-block; color: #888; } .tm-article-title:hover span.remove-tracker:hover { color: #444; } .tracker-refresh-icon, .delete-icon, .hide-icon { cursor: pointer; } .icon-anim { animation-name: rotate; animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: linear; animation-play-state: running; } </style>`; const deleteSvg = '<svg class="delete-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" style="vertical-align: middle;" height="16" width="16"><path d="m16.5 33.6 7.5-7.5 7.5 7.5 2.1-2.1-7.5-7.5 7.5-7.5-2.1-2.1-7.5 7.5-7.5-7.5-2.1 2.1 7.5 7.5-7.5 7.5ZM24 44q-4.1 0-7.75-1.575-3.65-1.575-6.375-4.3-2.725-2.725-4.3-6.375Q4 28.1 4 24q0-4.15 1.575-7.8 1.575-3.65 4.3-6.35 2.725-2.7 6.375-4.275Q19.9 4 24 4q4.15 0 7.8 1.575 3.65 1.575 6.35 4.275 2.7 2.7 4.275 6.35Q44 19.85 44 24q0 4.1-1.575 7.75-1.575 3.65-4.275 6.375t-6.35 4.3Q28.15 44 24 44Zm0-3q7.1 0 12.05-4.975Q41 31.05 41 24q0-7.1-4.95-12.05Q31.1 7 24 7q-7.05 0-12.025 4.95Q7 16.9 7 24q0 7.05 4.975 12.025Q16.95 41 24 41Zm0-17Z"/></svg>'; const refeshSvg = '<svg class="tracker-refresh-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" style="vertical-align: middle;" height="20" width="20" ><path d="M24 40q-6.65 0-11.325-4.675Q8 30.65 8 24q0-6.65 4.675-11.325Q17.35 8 24 8q4.25 0 7.45 1.725T37 14.45V8h3v12.7H27.3v-3h8.4q-1.9-3-4.85-4.85Q27.9 11 24 11q-5.45 0-9.225 3.775Q11 18.55 11 24q0 5.45 3.775 9.225Q18.55 37 24 37q4.15 0 7.6-2.375 3.45-2.375 4.8-6.275h3.1q-1.45 5.25-5.75 8.45Q29.45 40 24 40Z"/></svg>'; const hideSvg = '<svg class="hide-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" style="vertical-align: middle;" height="16" width="16"><path d="m31.45 27.05-2.2-2.2q1.3-3.55-1.35-5.9-2.65-2.35-5.75-1.2l-2.2-2.2q.85-.55 1.9-.8 1.05-.25 2.15-.25 3.55 0 6.025 2.475Q32.5 19.45 32.5 23q0 1.1-.275 2.175-.275 1.075-.775 1.875Zm6.45 6.45-2-2q2.45-1.8 4.275-4.025Q42 25.25 42.85 23q-2.5-5.55-7.5-8.775Q30.35 11 24.5 11q-2.1 0-4.3.4-2.2.4-3.45.95L14.45 10q1.75-.8 4.475-1.4Q21.65 8 24.25 8q7.15 0 13.075 4.075Q43.25 16.15 46 23q-1.3 3.2-3.35 5.85-2.05 2.65-4.75 4.65Zm2.9 11.3-8.4-8.25q-1.75.7-3.95 1.075T24 38q-7.3 0-13.25-4.075T2 23q1-2.6 2.775-5.075T9.1 13.2L2.8 6.9l2.1-2.15L42.75 42.6ZM11.15 15.3q-1.85 1.35-3.575 3.55Q5.85 21.05 5.1 23q2.55 5.55 7.675 8.775Q17.9 35 24.4 35q1.65 0 3.25-.2t2.4-.6l-3.2-3.2q-.55.25-1.35.375T24 31.5q-3.5 0-6-2.45T15.5 23q0-.75.125-1.5T16 20.15Zm15.25 7.1Zm-5.8 2.9Z"/></svg>'; const oldTrackerBlock = animateRefreshIcon(); let hiddenItems = localStorage.getItem('hbr_tracker'); hiddenItems = hiddenItems ? JSON.parse(hiddenItems) : []; let currentTrack = 0; fetch(`https://habr.com/ru/users/${userName}/favorites/posts/`) .then(response => response.text()) .then(response => processTracker(response)); function processTracker(response) { const d = document.querySelector('.tm-sexy-sidebar'); if (!d) return null; oldTrackerBlock?.parentNode.removeChild(oldTrackerBlock); const articles = parseArticles(response); const newDiv = addNewElement(d, 'section', 'tm-block tm-block tm-block_spacing-top tm-block_spacing-bottom block-tracker', trackerBlockCss, insertAsFirstBlock); const headerDiv = addNewElement(newDiv, 'header', 'tm-block__header tm-block__header', '<div class="tm-block__header-container"><h2 class="tm-block__title"><a class="tm-block__title" href="https://habr.com/ru/tracker/" target="_blank">Tracker</a> <span class="tracker-refresh-btn" >' + refeshSvg + '</h2></div> '); headerDiv.querySelector('.tracker-refresh-btn').addEventListener('click', injectTracker); const newContent = addNewElement(newDiv, 'div', 'tm-block__body tm-block__body'); const newUl = addNewElement(newContent, 'ul', 'tm-article-list-block__list'); if (articles) addArticles(articles, 1, newUl, newContent); } function addArticles(articles, pageId, newUl, newContent) { articles.filter(el => { if (currentTrack >= MAX_TRACKS) return false; currentTrack += addTrack(el, newUl); }); if (currentTrack === 0) addNewElement(newContent, 'p', '', 'No articles with new comments'); else if (currentTrack < MAX_TRACKS && pageId < MAX_PAGES && articles.length == 20) processNextPage(pageId + 1, newUl, newContent); } function parseArticles(response) { const m = response.match(/<div[^>]+data-test-id="articles-list"[^>]*>(.*?)<\/body>/s); if (!m) return null; const parseDiv = document.createElement("div"); parseDiv.innerHTML = m[0]; return [...parseDiv.querySelectorAll('div.tm-articles-list article')]; } function processNextPage(pageId, newUl, newContent) { setTimeout(() => { fetch(`https://habr.com/ru/users/${userName}/favorites/posts/page${pageId}/`) .then(response => response.text()) .then(response => { const articles = parseArticles(response); if (articles) addArticles(articles, pageId, newUl, newContent); }); }, 0); } function addTrack(el, newUl) { const firstTd = el.querySelector('div.tm-article-snippet__meta span.tm-user-info__user'); if (!firstTd) return 0; const trackerHtml = el.querySelector('div.tm-article-comments-counter-link')?.innerHTML; if (!trackerHtml?.includes('+')) return 0; const articleId = parseInt(el.getAttribute('id')); if (hiddenItems.includes(articleId)) return 0; const newLi = addNewElement(newUl, 'li', 'tm-article-list-block__item'); const newArcticle = addNewElement(newLi, 'article', 'tm-article-snippet-block-sidebar tm-article-snippet-block-sidebar_preview'); const newTopDiv = addNewElement(newArcticle, 'div', 'tracker-stats-info'); addNewElement(newTopDiv, 'span', 'post_author', firstTd?.innerHTML.replaceAll('<a ', '<a target="_blank" ')); addNewElement(newTopDiv, 'span', 'tracker', trackerHtml.replaceAll('<a ', '<a target="_blank" ')); const newPost = addNewElement(newArcticle, 'h2', 'tm-article-title tm-article-title_sidebar', `<span class="tracker-hide-or-remove"><span class="hide-tracker" title="Hide from tracker">${hideSvg}</span> <span class="remove-tracker" title="Remove from tracker">${deleteSvg}</span></span>` + el.querySelector('h2.tm-article-snippet__title')?.innerHTML.replaceAll('<a ', '<a target="_blank" ').replaceAll('"tm-article-snippet__title-link"', '"tm-article-title__link"')); newPost.querySelector('.hide-tracker').addEventListener('click', hideTracker); newPost.querySelector('.remove-tracker').addEventListener('click', removeTracker); return 1; } function removeTracker(event) { if (!confirm('Do you really want to delete it?')) return; const postId = getPostId(event.currentTarget); if (!postId) return; event.currentTarget.querySelector('svg')?.classList.add('icon-anim'); fetch(`https://habr.com/kek/v1/articles/${postId}/bookmarks/remove`, { method: 'POST', body: {}, headers: { 'Content-Type': 'application/json' } }).then(() => injectTracker()); } function hideTracker(event) { const postId = getPostId(event.currentTarget); if (!postId) return; if (hiddenItems.length > 100) hiddenItems.length = 99; hiddenItems.unshift(postId); localStorage.setItem('hbr_tracker', JSON.stringify(hiddenItems)); injectTracker(); } function getPostId(el) { const arctileEl = el.parentNode.parentNode.querySelector('* > a'); if (!arctileEl) return; const m = arctileEl.getAttribute('href')?.match(/\/([0-9]+)\//); if (!m) return null; return parseInt(m[1]); } function addNewElement(parentElement, tageName, className, html, makeAsFirstChild) { const newEl = document.createElement(tageName); newEl.className = className ?? ''; newEl.innerHTML = html ?? ''; if (makeAsFirstChild) parentElement.insertBefore(newEl, parentElement.firstChild); else parentElement.appendChild(newEl); return newEl; } function animateRefreshIcon() { const oldTrackerBlock = document.querySelector('section.block-tracker'); oldTrackerBlock?.querySelector('.tracker-refresh-icon')?.classList.add('icon-anim'); return oldTrackerBlock; } function getUserName() { const userHref = document.querySelector('.tm-footer-menu__list-item > a[href*="/ru/users/"]')?.getAttribute("href"); if (!userHref) return null; const um = userHref.match(/\/ru\/users\/([^\/]+)\/posts\//) if (!um) return null; return um[1]; } } injectTracker(); })();
Если вы используете Greasemonkey или подобное расширение, то можете добавить этот скрипт для сайта хабра и на каждой странице у вас будет новая секция «tracker».
Я использую кастомизированный HabrSanitizer и если вы также его поклонник и хотели бы добавить функциональность трекера к нему, то надо загрузить исходники к себе на локальный компьютер, добавить его в качестве локального расширения (Load Unpacked) и вставить код скрипта перед строчкой (на сегодня это строка 397)
Можно протестировать функциональность и без установки расширения. На любой странице хабра нужно открыть DevTools и вставить код этого скрипта в Console. Но конечно это единовременное изменение страницы и при навигации со страницы, трекер блок будет удалён и на новой странице необходимо будет повторить процедуру ещё раз.
Если всё сделано правильно то вы увидите под блоком «Читают сейчас» новый блок, «Tracker».
Буду благодарен за замечания и обратную связь.
ссылка на оригинал статьи https://habr.com/ru/post/699806/
Добавить комментарий