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

в 17:39, , рубрики: javascript, дядя Федор, коллективное письмо, коллективный пост, ненормальное программирование, Программирование, успеть до 1 апреля, Хабр из Простоквашино, метки: , , , ,

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

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

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

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

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

Душа поэта не выдержала, нашел полчаса, и нарисовал 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 февраля…

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

всякий утиль

Не менее хардкорно, при помощи того же рашпиля, я нарисовал всплывающее окошечко. Да, я знаю — так пишут только старперы, помнящие особенности жавашкрипа в нетшкафе 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();
        }
    }
}

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

Автор: viklequick

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js