Несколько дней назад, @vconst написал пост "Верни трекер! (С)" с воззваниями по поводу изменения функциональности трекера в новой версии Хабра.
У меня возникла идея, что используя только расширение браузера возможно реализовать трекер с фактически любой функциональностью.
В качестве Proof-of-Concept, я попробовал самую простую идею, добавить старую версию трекера в правый сайдбар. Всё получилось даже проще, чем казалось.
Исходный код скрипта
(function () {
function injectTracker() {
const MAX_TRACKS = 10;
// To add the tracker as a first block on the right sidebar, change "false" to "true"
const insertAsFirstBlock = false;
// set "true" if you want to tract articles only with unseen comments
const onlyWithUnseenComments = false;
// styling
const trackerBlockCss = `<style scoped>
.post-info__title a { color: #444; }
a[href*="#comments"] { color: #82a3b1; }
a[href*="#first_unread"] { color: #cf0000; }
span.tracker { position: absolute; right: 0; }
span.remove-tracker { position: absolute; right: 0; display: none; }
li:hover > .post-info__title span.remove-tracker { display: inline-block; color: #888; }
.post-info__title:hover span.remove-tracker:hover { color: #444; }
.tracker-refresh-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 oldTrackerBlock = animateRefreshIcon();
fetch('https://habr.com/ru/tracker/')
.then(response => response.text())
.then(response => processTracker(response));
function processTracker(response) {
const m = response.match(/<form[^>]+id="tracker_feed_form"[^>]*>(.*?)</form>/s);
if (!m)
return;
const d = document.querySelector('.sidebar_right');
if (!d)
return;
oldTrackerBlock?.parentNode.removeChild(oldTrackerBlock);
const parseDiv = document.createElement("div");
parseDiv.innerHTML = m[1];
const newDiv = addNewElement(d, 'div', 'default-block default-block_sidebar block-tracker', trackerBlockCss, insertAsFirstBlock);
const headerDiv = addNewElement(newDiv, 'div', 'default-block__header', '<h3 class="default-block__header-title"><a href="https://habr.com/ru/tracker/" target="_blank">Tracker</a> <span class="tracker-refresh-btn" >' + refeshSvg + '</span></h3> ');
headerDiv.querySelector('.tracker-refresh-btn').addEventListener('click', injectTracker);
const newContent = addNewElement(newDiv, 'div', 'default-block__content');
const newUl = addNewElement(newContent, 'ul', 'content-list content-list_most-read');
let currentTrack = 0;
[...parseDiv.querySelectorAll('table.tracker-table tr')].filter(el => {
if (currentTrack >= MAX_TRACKS)
return false;
currentTrack += addTrack(el, newUl);
});
if (currentTrack === 0)
addNewElement(newContent, 'div', '', onlyWithUnseenComments ? 'No articles with new comments' : '<a href="https://habr.com/ru/tracker/" target="_blank">No articles to track</a>');
parseDiv.remove();
}
function addTrack(el, newUl) {
let firstTd = el.querySelector('td:nth-child(1)');
if (!firstTd)
return 0;
const trackerHtml = el.querySelector('td:nth-child(3)')?.innerHTML;
if (onlyWithUnseenComments && !trackerHtml?.includes('+'))
return 0;
const newLi = addNewElement(newUl, 'li', 'content-list__item content-list__item_devided post-info');
const newTopDiv = addNewElement(newLi, '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(newLi, 'div', 'post-info__title', el.querySelector('td:nth-child(2)')?.innerHTML.replaceAll('<a ', '<a target="_blank" ') + `<span class="remove-tracker" title="Remove from tracker">${deleteSvg}</span>`);
newPost.querySelector('.remove-tracker').addEventListener('click', removeTracker);
return 1;
}
function removeTracker(event) {
const arctileEl = event.currentTarget.parentNode.querySelector('* > a');
if (!arctileEl)
return;
const m = arctileEl.getAttribute('href')?.match(//([0-9]+)//);
if (!m)
return;
const postId = m[1];
event.currentTarget.classList.add('icon-anim');
fetch('https://habr.com/json/tracker/feed/remove/',
{
method: 'POST',
body: `post[${postId}]=on`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
}).then(() => injectTracker());
}
function addNewElement(parentElement, tageName, className, html, makeAsFirstChild) {
const newEl = document.createElement(tageName);
if (makeAsFirstChild)
parentElement.insertBefore(newEl, parentElement.firstChild);
else
parentElement.appendChild(newEl);
if (className)
newEl.className = className;
if (html)
newEl.innerHTML = html;
return newEl;
}
function animateRefreshIcon() {
let oldTrackerBlock = document.querySelector('div.block-tracker');
if (oldTrackerBlock)
document.querySelector('.tracker-refresh-icon')?.classList.add('icon-anim');
return oldTrackerBlock;
}
}
injectTracker();
})();
Если вы используете Greasemonkey или подобное расширение, то можете добавить этот скрипт для сайта хабра и на каждой странице у вас будет новая секция "tracker".
Я использую кастомизированный HabrSanitizer и если вы также его поклонник и хотели бы добавить функциональность трекера к нему, то надо загрузить исходники к себе на локальный компьютер, добавить его в качестве локального расширения (Load Unpacked) и вставить код скрипта перед строчкой (на сегодня это строка 397)
const isOnPersonalPage = onPersonalPage(window.location.href);
Можно протестировать функциональность и без установки расширения. На любой странице хабра нужно открыть DevTools и вставить код этого скрипта в Console. Но конечно это единовременное изменение страницы и при навигации со страницы, трэкер блок будет удалён и на новой странице необходимо будет повторить процедуру ещё раз.
Ещё раз - это скрипт работает только для старой версии интерфейса хабра.
Если всё сделано правильно то вы увидете под блоком "Читают сейчас" новый блок, "Tracker".
Что-то типа:
Следующий шаг будет попробовать что-то типа этого для новой версии.
Единственная проблема, это то что продётся читать не одну дополнительную страницу, а десять или более. Так что чтобы не генерировать избыточную нагрузку, может быть Хабру имеет смысл не бороться с пользователями, а прислушаться и мигрировать старый трекер на новый интерфейс. Это должно быть довольно просто.
Всем удачных выходных!
Автор:
hbn3