...

пятница, 21 февраля 2014 г.

А таки давайте напишем инструмент для написания писем Дяди Федора!

image

Читаю я вчерашний пост простоквашино на Хабре или письмо Дяди Федора. Мысль интересная, но.

Комментарии пугают.


Поясню почему.


Комментарии там условно можно разделить на два вида



  • «как много помещается ангелов на конце иглы» «о сортировке дат»,

  • «как забивать гвозди электронным микроскопом» «мы напишем что-то такое большое в энтерпрайзненьком стиле».


Душа поэта не выдержала, нашел полчаса, и нарисовал userscript.


Скрипт прост до ужаса — перебирает все комментарии в поисках специального маркера. Если маркер найден — показывает все комментарии с маркером во всплывающем окошке.


Всем желающим поучаствовать в улучшении — добро пожаловать, так сказать откатаем технологию. Если ваш комментарий надо добавить в пост — пишите внутри комментария вот так [also]. А я как инициатор этого безобразия — по мере сил буду ваши пожелания в пост переносить. И соответственно — улучшать скрипт тоже.


А теперь лирику побоку, и рассмотрим что же мы тут такое делаем.


Стандартное начало — объясняем Greasemonkey что это и для чего. А также заводим парочку переменных — для окошечка и для маркера.



// ==UserScript==
// @name Prostokvashino
// @namespace habrahabr.ru
// @description http://ift.tt/NizLm8
// @include http://ift.tt/1f46Lof
// @include http://ift.tt/1f46NfR

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 февраля…


Остались мелочи — нарисовать окошечко, повесить события, сходить за соком, и вот он — скрипт.


всякий утиль
Не менее хардкорно, при помощи того же рашпиля, я нарисовал всплывающее окошечко. Да, я знаю — так пишут только старперы, помнящие особенности жавашкрипа в нетшкафе 3.0 .

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();
}
}
}


Спасибо за внимание.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий