Читаю я вчерашний пост простоквашино на Хабре или письмо Дяди Федора. Мысль интересная, но.
Комментарии пугают.
Поясню почему.
Комментарии там условно можно разделить на два вида
«как много помещается ангелов на конце иглы»«о сортировке дат»,«как забивать гвозди электронным микроскопом»«мы напишем что-то такое большое в энтерпрайзненьком стиле».
Душа поэта не выдержала, нашел полчаса, и нарисовал userscript.
Скрипт прост до ужаса — перебирает все комментарии в поисках специального маркера. Если маркер найден — показывает все комментарии с маркером во всплывающем окошке.
Всем желающим поучаствовать в улучшении — добро пожаловать, так сказать откатаем технологию. Если ваш комментарий надо добавить в пост — пишите внутри комментария вот так [also]. А я как инициатор этого безобразия — по мере сил буду ваши пожелания в пост переносить. И соответственно — улучшать скрипт тоже.
А теперь лирику побоку, и рассмотрим что же мы тут такое делаем.
Стандартное начало — объясняем Greasemonkey что это и для чего. А также заводим парочку переменных — для окошечка и для маркера.
// ==UserScript==
// @name Prostokvashino
// @namespace habrahabr.ru
// @description habrahabr.ru/post/213263
// @include http://*.habrahabr.ru/post/*
// @include http://habrahabr.ru/post/*
var MARKER = '[also]';
var floatingDiv = null;
Теперь рассмотрим основной алгоритм — благо он прост как пять копеек. Для начала найдем где комментарии, подготовим массивчик для результатов (found):
function showMarkedComments () {
var commentsBlock = document.getElementById('comments');
var eltList = commentsBlock.getElementsByTagName('div');
var found = [];
Далее поступим очень просто — переберем все div, с собственно текстами.
for(var j=0; j<eltList.length; ++j) {
var elt = eltList[j];
if(elt.className != 'comment_body' ) continue;
// now elt has two sub-divs - info with author and div with text
// in version 0.1 we're ignoring authors and other technical stuff - just use div with text
for(var k=0; k < elt.childNodes.length; ++k) {
if(elt.childNodes[k].nodeName.toLowerCase() == "div") {
...
}
}
}
Не верх оптимальности, но обойдет всю иерархию с ответами на комментарии, причем в том порядке в котором это описано в странице. Вуаля — о датах думать не придется.
Собственно проверка и запоминание настолько очевидны (и так же неоптимальны), что комментировать тут нечего:
divtxt = elt.childNodes[k].innerHTML;
if(divtxt.indexOf(MARKER) < 0) continue;
found.push(divtxt);
Осталась мелочь — заполнить содержимым всплывающее окошко. Я как старый хардкорщик нарисовал рашпилем из трактора, так что наворачивание плагинов jQuery — самое то тут есть большой простор для улучшения этого безобразия.
// in found, we have list of HTML texts together with marker.
// All we need is to place it in floating div and make it visible
if(found.length < 1) return;
var buf = new StringBuffer();
for(var j = 0; j < found.length; ++j) {
buf.append('<div id="comment_"'+j+' class="comment_item">n');
buf.append('<div class="comment_body">');
buf.append('<div class="message html_format "><hr/>');
buf.append(found[j]);
buf.append('</div>');
buf.append('</div>n');
buf.append('</div>n');
}
buf.append('<hr/>');
floatingDiv.innerHTML = buf.toString();
floatingDiv.style.top = (window.pageYOffset + 10) + 'px';
floatingDiv.style.display = '';
}
Тут в процессе пиления рашпилем я внезапно для самого себя озаботился производительностью, быстренько родив на коленке аналог StringBuffer / StringBuilder. Вот собственно эта запчасть:
// simple string buffer
function StringBuffer() {
this.buffer = [];
}
StringBuffer.prototype.append = function append(string) {
this.buffer.push(string);
return this;
};
StringBuffer.prototype.toString = function toString() {
return this.buffer.join("");
};
Если потребуется ее откомментировать, дайте кто-нибудь знать. По мне так очевидный код, но мало ли. Пятница, вечер, накануне 23 февраля…
Остались мелочи — нарисовать окошечко, повесить события, сходить за соком, и вот он — скрипт.
function makeFloatingDiv() {
if(!floatingDiv) {
floatingDiv = document.createElement('div');
floatingDiv.style.position = "absolute";
//floatingDiv.style.height = '600px';
floatingDiv.style.width = '800px';
floatingDiv.style.backgroundColor = '#f2f2f2';
floatingDiv.style.borderColor = 'red';
floatingDiv.style.borderWidth = '2px';
floatingDiv.style.borderStyle = 'groove';
floatingDiv.style.padding = '2px';
floatingDiv.valign = 'top';
floatingDiv.align = 'left';
floatingDiv.style.display = 'none';
floatingDiv.textAlign = 'left';
floatingDiv.style.overflow = 'auto';
floatingDiv.style.left = '10px';
floatingDiv.style.top = '10px';
document.getElementsByTagName('body')[0].appendChild(floatingDiv);
floatingDiv.style.display = '';
floatingDiv.innerHTML = "...";
}
}
function makeDivWExtraLinks() {
var xdiv = document.createElement('span');
xdiv.appendChild(document.createElement('br'));
xdiv.appendChild(document.createElement('br'));
xdiv.appendChild(mk_Link(1, 'See additions to post'));
return xdiv;
}
function goHide(event) {
if(event.target.style.display != 'none')
event.target.style.display = 'none';
}
Ну и еще парочка таких же функций в том же стиле:
function mk_Link(code, label) {
var newElt = document.createElement('a');
newElt.appendChild(document.createTextNode('[' + label + ']'));
newElt.href= 'javascript:void(-' + code + ')';
return newElt;
}
Однако, внимательный читатель увидит страшную ересь. Мало того что куски HTML склеиваются строчками из кусочков! Там еще и какие-то неочевидные хаки встроились. Вот такие
newElt.href= 'javascript:void(-' + code + ')';
Сам в шоке! Наверное в только что слопанный тортик кто-то подмешал коноплю…
Идея тут в следующем — мы создаем отдельный линк между статьей и комментариями, по нажатию на который и проводится вся работа. Сделано это вот зачем — чтобы можно было пользоваться другими удобными скриптами и кнопками, по обновлению комментариев без перезагрузки страницы. А раз комментарии могут к нам приехать после загрузки и очень даже сильно «после» — то и наш алгоритм должен уметь работать в любое удобное время.
А поскольку это все было нарисовано на коленке, чтобы не стукаться лбом с контекстами выполнения и их принципалами, то ссылку я сформировал так что ни код сайта ни браузер ничего полезного сделать со ссылкой не могут. Но в своем обработчике я легко сориентируюсь что с этим делать. Можно хоть слова матерные написать, лишь бы это не было валидной линкой или валидным кодом.
Однако, остался последний рывок! Никакие происки не помешают… ик! Регистрируем пару обработчиков — на загрузку документа и на обработку ужасного хака:
window.addEventListener("load", goLoad, true);
document.addEventListener('click', goClick, true);
При загрузке странички, между статьей и комментариями добавляем спец-ссылку. По нажатии на нее и будем проводить всю работу:
function goLoad() {
// starting here: extra links plus other stuff
var commentsBlock = document.getElementById('comments');
commentsBlock.parentNode.insertBefore(makeDivWExtraLinks(), commentsBlock);
makeFloatingDiv();
floatingDiv.addEventListener('click', goHide, true);
}
и вот — обработка по нажатию на эту ссылку:
function goClick(event) {
if(event.target.href) {
var j = event.target.href.indexOf('javascript:void(-');
if( j >=0 ) {
// kind is group of operations' prefix: 0..9
var kind = event.target.href.substring(j + 16, j + 18);
if(kind == '-1') showMarkedComments();
}
}
}
Спасибо за внимание.
Автор: viklequick