habrActivity

view user activity in Habr comments for Fx-Opera-Chrome

  1. // ==UserScript==
  2. // @id habrActivity
  3. // @name habrActivity
  4. // @version 5.2014.12.31
  5. // @author spmbt0
  6. // @description view user activity in Habr comments for Fx-Opera-Chrome
  7. // @include http://habrahabr.ru/*
  8. // @include http://geektimes.ru/*
  9. // @exclude http://habrahabr.ru/api/*
  10. // @update 4 win.console; remove panel from Geektimes articles;
  11. // @icon data:image/gif;base64,R0lGODlhIAAgAMMBAG6Wyv///2+NtIucstfY2b/FzpSmvY+QkM3Nzunp6fLy8qGwweDg4MbFxa2trrm6uiwAAAAAIAAgAAAE/xDISau9OM/AOe2edoHBBwqiRZodmrKhRLqXYFfrdmLCQBQGWk62swgOiERAQQgChs9iRZBMKDgEFGnbMi4YDMU1gNBytzSJDcGwXhUD4lmqZofFioZrPqMIDARtYksIAzZ8dAINgngJVgkLUH1qBmBuCgmBYA6SUgKBl0wICA6lk1FdAAIFjngKDAgEpKYgWXIcKH8EDQ0EVwmjsrycIA4FZl2rDwcHDgivow8ODwzEHca3ASgDpMylsrEOzdUkDk59AtOl07wIDcwNkDbzCy7z8xIDD8Ps3Q5hCQqscxBHgw0DbEY1WIbEkRtHZV6oMsAq0wNqrcQ4KihR1Z9YjzUeKjjWcYqABUoaJeBY0k8bAm5ItqxgANjFBnBmTgnTQNw0nVOSNBjQLA1QXdEMATVioGnJCAA7
  12. // @namespace https://greasyfork.org/users/2323
  13. // ==/UserScript==
  14. (function(win, noConsole, css, hActHelp){
  15. var d = document
  16. ,dBody = d.body || d.documentElement
  17. ,$q = function(q, f){return (f||d).querySelector(q)}
  18. ,uHead = $q('.user_header')
  19. ,uName2a = $q('.username a', uHead)
  20. ,uName2 = uName2a && uName2a.innerHTML
  21. ,comms = $q('.comments_list');
  22. if(uName2 && comms && $q('.user_comments')){ //=====(всё работает только на странице комментариев некоторого пользователя)=====
  23.  
  24. try{ //для оповещения об ошибках в Fx
  25. var NOWdate = new Date()
  26. ,lh = location.href
  27. ,HRU =location.protocol +'//'+ location.host
  28. ,lStorRoot ='habrAct_'
  29. ,setLocStor = function(name, hh){ if(!win.localStorage) return;
  30. localStorage[lStorRoot + name] = JSON.stringify({h: hh});
  31. }
  32. ,getLocStor = function(name){
  33. return (JSON.parse(localStorage && localStorage[lStorRoot + name] ||'{"h":{}}')).h;
  34. }
  35. ,removeLocStor = function(name){localStorage.removeItem(lStorRoot + name);}
  36. ,$qA = function(q, f){return (f||d).querySelectorAll(q)}
  37. ,$pd = function(ev){ev.preventDefault();}
  38. ,$sp = function(ev){ev.stopPropagation();}
  39. ,$x = function(el, h){ //===extend===
  40. if(h)
  41. for(var i in h)
  42. el[i] = h[i];
  43. return el;
  44. }
  45. ,$e = function(g){ //===создать или использовать имеющийся элемент===
  46. //g={el|clone,IF+ifA,q|[q,el],cl|(clAdd,clRemove),ht,cs,at,on,revent,apT,prT,bef,aft,f+fA}
  47. if(g.ht || g.at){
  48. var at = g.at ||{}; if(g.ht) at.innerHTML = g.ht;}
  49. if(typeof g.IF =='function')
  50. g.IF = g.IF.apply(g, g.ifA ||[]);
  51. g.el = g.el || g.clone || g.IF && g.IF.attributes && g.IF ||'DIV';
  52. var o = g.clone && g.clone.cloneNode(!0)
  53. || (typeof g.el =='string' ? d.createElement(g.el) : g.el);
  54. if(o && (g.IF===undefined || g.IF) && (!g.q || g.q && (g.dQ = g.q instanceof Array ? $q(g.q[0], g.q[1]) : $q(g.q)) ) ){ //выполнять, если существует; g.dQ - результат селектора для функций IF,f
  55. if(g.cl)
  56. o.className = g.cl;
  57. else{
  58. if(g.clAdd)
  59. o.classList.add(g.clAdd);
  60. if(g.clRemove)
  61. o.classList.remove(g.clRemove);
  62. }
  63. if(g.cs)
  64. $x(o.style, g.cs);
  65. if(at)
  66. for(var i in at){
  67. if(i=='innerHTML') o[i] = at[i];
  68. else o.setAttribute(i, at[i]);}
  69. if(g.on)
  70. for(var i in g.on)
  71. o.addEventListener(i, g.on[i],!1);
  72. if(g.revent)
  73. for(var i in g.revent)
  74. o.removeEventListener(i, g.revent[i],!1);
  75. g.apT && g.apT.appendChild(o); //ставится по ориентации, если новый
  76. g.prT && (g.prT.firstChild
  77. ? g.prT.insertBefore(o, g.prT.firstChild)
  78. : g.prT.appendChild(o) );
  79. g.bef && g.bef.parentNode.insertBefore(o, g.bef);
  80. g.aft && (g.aft.nextSibling
  81. ? g.aft.parentNode.insertBefore(o, g.aft.nextSibling)
  82. : g.aft.parentNode.appendChild(o) );
  83. if(typeof g.f =='function')
  84. g.f.apply(g, g.fA ||[]); //this - это g
  85. }
  86. return o;
  87. }
  88. ,parents = function(cl, elem){
  89. for(var el = elem; el!=null && !RegExp(cl).test(el.className); el = el.parentNode);
  90. return el;
  91. }
  92. ,prev = function(cl, elem){
  93. for(var el = elem; el!=null && !RegExp(cl).test(el.className); el = el.previousSibling);
  94. return el;
  95. }
  96. ,next = function(cl, elem){
  97. for(var el = elem; el!=null && !RegExp(cl).test(el.className); el = el.nextSibling);
  98. return el;
  99. }
  100. ,toTime = function(dat){
  101. var yestDate = new Date(+NOWdate - 86400000)
  102. ,datMonth ={"января":0,"февраля":1,"марта":2,"апреля":3,"мая":4,"июня":5,
  103. "июля":6,"августа":7,"сентября":8,"октября":9,"ноября":10,"декабря":11}
  104. ,datFull = dat.innerHTML
  105. ,datYest = /вчера/.test(datFull)
  106. ,dateText = datFull.replace(/ в /,' ').replace(/(сегодня |вчера )/,'')
  107. ,datArr = dateText.match(/(\d+)\s+([а-яё]+)\s+(\d{4})?/i);
  108. if(!datArr)
  109. datArr =[0,(datYest ? yestDate : NOWdate).getDate(),(datYest ? yestDate : NOWdate).getMonth(),(datYest ? yestDate : NOWdate).getFullYear()];
  110. var altArr = dateText.match(/(\d+)\:(\d+),([а-яё]+)\s*(\d+)\s*([а-яё]+)\s*(\d{4})?/i); //ччммддЧЧММГГГГ?
  111. if(altArr)
  112. datArr =[0,altArr[4],altArr[5],altArr[6]];
  113. //'altArr'.wcl(altArr)
  114. var mon = datArr && datMonth[datArr[2]] || datMonth[datArr[2]] !=0 && datArr[2] || datMonth[datArr[2]];
  115. //'datArr'.wcl(datArr, mon)
  116. if(datArr && !datArr[3])
  117. datArr[3] = NOWdate.getFullYear();
  118. var ret2 = new Date(datArr[3], mon, datArr[1], dateText.replace(/(.*?)(\d{1,2}):(\d\d)(.*)/,'$2'), dateText.replace(/(.*?)(\d{1,2}):(\d\d)(.*)/,'$3') ).getTime();
  119. if(+NOWdate < ret2)
  120. ret2 = new Date(datArr[3] -1, mon, datArr[1], dateText.replace(/(.*?)(\d{1,2}):(\d\d)(.*)/,'$2'), dateText.replace(/(.*?)(\d{1,2}):(\d\d)(.*)/,'$3') ).getTime();
  121. return ret2;
  122. }
  123. ,getYearWeekDay = function(dd){
  124. var d = new Date(dd), jan1 = new Date(d.getFullYear(),0,1);
  125. return [d.getFullYear(), Math.ceil(((dd - jan1) /86400000 + jan1.getDay() -1)/7)
  126. , d.getDay() + 7*(d.getDay()==0), d.getDate(), d.getMonth()];
  127. }
  128. ,wcl = function(a){ a = a ||''; //консоль как метод строки или функция, с отключением по noConsole
  129. if(win.console && (!noConsole || this =='ER_global:'))
  130. win.console.log.apply(win.console, this instanceof String
  131. ? ["'=="+ this +"'"].concat([].slice.call(arguments)) : arguments);
  132. };
  133. String.prototype.wcl = wcl;
  134.  
  135. var hActUsersTmpl ={ //шаблон основной (индексной) записи в хранилище
  136. dataLen: 0 //число записей в хранилище вида habrActDataNNNNN, где NNNNN - число
  137. ,dataCount: 25 //огранчитель цикла чтения, страниц
  138. ,dateStart: 1000 //ограничитель периода чтения, до 365 дней, сравнивает дату начала цикла и посл.
  139. ,userS: [] //массив имён пользователей, для которых имеются данные
  140. }
  141. ,readAutoIntervalMax = 365
  142. ,hActDataTmpl ={ //шаблон записи данных в хранилище; запись имеет вид habrActData[число], от 1 до dataLen
  143. user:'' //пользователь, для которого со страницы комментариев сняли данные
  144. ,data: [] //массив дат (или сложнее)
  145. };
  146. (function(css){ var u = 'undefined'; //addRules
  147. if(typeof GM_addStyle !=u){ GM_addStyle(css);
  148. }else if(typeof addStyle !=u){ addStyle(css);
  149. }else{
  150. var node = d.createElement('style');
  151. node.type ='text/css';
  152. node.appendChild(d.createTextNode(css));
  153. (d.getElementsByTagName('head')[0] || dBody).appendChild(node);}
  154. })(css);
  155.  
  156. var readPage
  157. ,ww =0
  158. ,startButt = $e({el:'button', ht:'Старт'
  159. ,on:{click: readPage = function(ev){ //читать статистику со страницы и переходить к следующей
  160. if(ev){
  161. var handStart =1;
  162. hActUsers = getLocStor('users');
  163. hActUsers.userS = hActUsers.userS ||[];
  164. hActUsers.dataLen = hActUsers.dataLen ||0;
  165. }
  166. var infoS = $qA('.info', comms)
  167. ,datC =[];
  168. clearTimeout(ww);
  169. if(startButt.innerHTML =='Стоп'){
  170. startButt.innerHTML ='Старт';
  171. hActUsers.dataCount =0;
  172. setLocStor('users', hActUsers);
  173. return;
  174. }
  175. hActUsers.dataLen++;
  176. startButt.innerHTML ='Стоп';
  177. if(handStart)
  178. hActUsers.dataCount = hActUsersTmpl.dataCount;
  179. for(var i in infoS){ var iI = infoS[i]; if(iI.attributes){
  180. var sco = $q('.voting .score',iI)
  181. ,apm = sco && sco.title.match(/\d+/g)
  182. ,text = $q('.message', iI.parentNode);
  183. datC.push(//{date:
  184. toTime($q('time',iI))/1000
  185. //,plus: apm && apm[1]
  186. //,minus: apm && apm[2]
  187. //,textLen: text && text.innerHTML.replace(/\t/g,'').length}
  188. );
  189. }}
  190. var pageA = lh.match(/^.+?page(\d+).*/);
  191. 'datC'.wcl(datC, pageA, ( + NOWdate/1000 - datC[datC.length -1]) /86400)
  192. $e({el: $q('.msg', startButt.parentNode) ||'span'
  193. ,cl:'msg'
  194. ,ht:'<br>&nbsp; До '+(0|((NOWdate/1000 - datC[datC.length -1]) /86400))
  195. +' дней от сегодня; интервал - '+ (0|((datC[0] - datC[datC.length -1]) /86400))+' дней'
  196. ,bef: $q('.clear',uHead)
  197. });
  198. if(handStart)
  199. hActUsers.dateStart = datC[0];
  200. if((hActUsers.dateStart - datC[datC.length -1]) /86400 < readAutoIntervalMax)
  201. ww = setTimeout(function(){
  202. location.href = pageA
  203. ? lh.replace(/page(\d+)/,'page'+ (+pageA[1] +1))
  204. : lh +'page2/';
  205. }, 3000);
  206. else{
  207. startButt.innerHTML ='Старт';
  208. hActUsers.dataCount =0;
  209. }
  210. if(!handStart)
  211. hActUsers.dataCount--;
  212. var noNewUser =0;
  213. for(var i in hActUsers.userS){ var uI = hActUsers.userS[i];
  214. if(uI == uName2){
  215. noNewUser =1; break;}
  216. }
  217. if(!noNewUser)
  218. hActUsers.userS.push(uName2);
  219. setLocStor('users', hActUsers); //накапливать статистику в хранилище
  220. wcl('2nd', hActUsers.dataLen);
  221. setLocStor('data'+ hActUsers.dataLen, {user: uName2, data: datC});
  222. }}
  223. ,bef: $q('.clear', uHead)
  224. }),
  225. hActUsers = getLocStor('users');
  226. hActUsers.userS = hActUsers.userS ||[];
  227. hActUsers.dataLen = hActUsers.dataLen ||0;
  228. if(hActUsers.dataCount >0){ //автозапуск анализа страницы
  229. wcl('1st');readPage();}
  230.  
  231. var helpHAct,
  232. helpButt = $e({el:'button', ht:'Подробности'
  233. ,on:{click: function(ev){ if(!helpHAct || helpHAct.style.display=='none'){
  234. if(!helpHAct){
  235. helpHAct = $e({cl:'helpHAct'
  236. ,ht: hActHelp
  237. ,on:{click: function(){this.style.display ='none';}}
  238. ,apT: dBody
  239. });
  240. $q('.in', helpHAct).addEventListener('click', function(ev){$sp(ev);},!1);
  241. }else
  242. helpHAct.style.display ='block';
  243. }else helpHAct.style.display ='none'; } }
  244. ,aft: startButt
  245. }),
  246. eraseButt = $e({el:'button', ht:'Стереть'
  247. ,on:{click: function(ev){
  248. var hActUsers = getLocStor('users');
  249. if(confirm('Удалить всю статистику комментариев пользователей ('+ hActUsers.dataLen +' записей)?')){
  250. if(hActUsers.dataLen){
  251. for(var i = +hActUsers.dataLen; i >=1; i--)
  252. removeLocStor('data'+ i);
  253. removeLocStor('users');
  254. }
  255. $q('.diag', selUser) && $q('.diag', selUser).classList.add('empty');
  256. }
  257. }}
  258. ,aft: startButt
  259. }),
  260. dataButt = $e({el:'button', ht:'Данные'
  261. ,on:{click: function(ev){
  262. hActUsers = getLocStor('users');
  263. selectUser(showData);
  264. this.blur();
  265. }}
  266. ,aft: startButt
  267. }),
  268. selUser,
  269. selectUser = function(f){ //предложить выбор пользователей
  270. if(!selUser || selUser.style.display=='none'){
  271. if(!selUser){
  272. selUser = $e({cl:'selUser'
  273. ,ht: '<div class=under></div><div class=in><h2><input class="inUser" title="перейти -видео на комментарии пользователя; Ctrl-Enter - в новом окне"><span class="titl">Выбрать пользователя</span></h2><div class="diag empty"></div></div>'
  274. ,on:{click: function(){this.style.display ='none';}}
  275. ,apT: dBody
  276. });
  277. $q('.in', selUser).addEventListener('click', function(ev){$sp(ev);},!1);
  278. $q('.inUser',selUser).addEventListener('keyup',function(ev){ if(ev.keyCode ==13){
  279. var lnk = HRU +'/users/'+ this.value +'/comments/';
  280. if(!ev.ctrlKey)
  281. location.href = lnk;
  282. else
  283. window.open(lnk,"_blank")
  284. }},!1);
  285. }else{
  286. selUser.style.display ='block';
  287. var sC = $q('.in', selUser).childNodes;
  288. for(var i = sC.length -1; i >=0; i--){ var sI = sC[i];
  289. 'sI'.wcl(sI.tagName, sI.className)
  290. if(dI.attributes &&(sI.tagName =='BUTTON'|| sI.className =='hADel') )
  291. selUser.removeChild(sI);
  292. }
  293. }
  294. for(var i in hActUsers.userS){ var uI = hActUsers.userS[i];
  295. $e({el:'button'
  296. ,ht: uI
  297. ,on:{click: function(){
  298. var bS = $qA('button', this.parentNode);
  299. for(var j in bS) if(bS[j].attributes)
  300. bS[j].classList.remove('hAActive');
  301. this.classList.add('hAActive');
  302. f(this.innerHTML);
  303. this.blur();
  304. }}
  305. ,bef: $q('.diag', selUser)
  306. });
  307. $e({el:'button',cl:'hADel'
  308. ,ht:'X'
  309. ,at:{title:'Удалить из хранилища','data-user': uI}
  310. ,on:{click: function(){
  311. var t = this, u;
  312. del1user(u = this.getAttribute('data-user'));
  313. t.previousSibling.parentNode.removeChild(t.previousSibling);
  314. setTimeout(function(){t.parentNode.removeChild(t);},1);
  315. for(var i in hActUsers.userS){ var uI = hActUsers.userS[i];
  316. if(u == uI){
  317. hActUsers.userS.splice(i, 1); break;
  318. }}
  319. setLocStor('users', hActUsers);
  320. }}
  321. ,bef: $q('.diag', selUser)
  322. });
  323. }
  324. var hU = hActUsers.userS;
  325. 'hU'.wcl(hU,uName2)
  326. if(hU && hU.length ==1)
  327. f(hU[0]),$q('button',selUser).classList.add('hAActive');
  328. else if(hU && uName2){
  329. var bS = $qA('button', hU[0].parentNode);
  330. for(var i in bS) if(bS[i].attributes){
  331. bS[i].classList.remove('hAActive');
  332. if(bS[i].innerHTML == uName2)
  333. f(uName2),bS[i].classList.add('hAActive');
  334. }
  335. }
  336. }else selUser.style.display ='none';
  337. },
  338. showData = function(user){ //показать диаграмму активности
  339. var dA =[]
  340. ,diag = $q('.diag', selUser)
  341. ,dC = diag.childNodes;
  342. for(var i = dC.length -1; i >=0; i--){ var dI = dC[i]; if(dI.attributes)
  343. diag.removeChild(dI);}
  344. for(var i =1; i <= +hActUsers.dataLen; i++){ //всё ранее прочитанное по юзеру
  345. if(!RegExp(' '+ i +' ').test(hActUsers.removed ||'') ){
  346. var dat = getLocStor('data'+ i);
  347. if(user == dat.user)
  348. dA = dA.concat(dat.data);
  349. }}
  350. diag.style.height ='100px';
  351. dA.sort(function(a, b){return a - b});
  352. var ymd0 = getYearWeekDay(dA[0]*1000), ymdE =[] //год, неделя, день, число, месяц, кол-во комм.
  353. ,diagLeftMargin =7
  354. ,diagTopMargin =7
  355. ,stripeN =1 //число полос для диаграммы
  356. ,stripePeriod = 110
  357. ,nComm =0
  358. ,diagInnerWid = diag.offsetWidth -diagLeftMargin *2 -10 //ограничения ширины 1 полосы
  359. ,wasDoubles =0, dAClean =[]; //слежение за дублями
  360. for(var i in dA){
  361. var ymd = getYearWeekDay(dA[i]*1000);
  362. ymd[5] = ymdE[0]==ymd[0] && ymdE[1]==ymd[1] && ymdE[2]==ymd[2] ? ymdE[5]+1 : 1;
  363. if(dA[i] == ymdE[6]){ //игнор дублей чтения
  364. wasDoubles =1; continue;}
  365. dAClean.push(dA[i]);
  366. nComm++;
  367. ymdE = [ymd[0], ymd[1], ymd[2], ymd[3], ymd[4], ymd[5], dA[i]];
  368. var leftFirstShift = ((ymd[0]-ymd0[0])*52 + (ymd[1]-ymd0[1]) )*10
  369. ,iStripe = Math.floor(leftFirstShift / diagInnerWid);
  370. stripeN = Math.max(stripeN, iStripe +1);
  371. $e({cl:'comm'
  372. ,cs:{left: (leftFirstShift % diagInnerWid) + diagLeftMargin +'px'
  373. ,top: iStripe * stripePeriod + ymd[2] *10 + diagTopMargin +'px'}
  374. ,at:{title: ymd[5] +' / '+ ymd[3]+'янвфевмарапрмайиюниюлавгсеноктноядек'.match(/.../g)[ymd[4]]+(ymd[0] +'').substr(2,2) }
  375. ,apT: diag
  376. });
  377. }
  378. if(wasDoubles){ //удалить юзера и записать его же из очищенного от дублей массива dAClean
  379. del1user(user);
  380. wcl('delU')
  381. setLocStor('data'+ ++hActUsers.dataLen, {user: uName2, data: dAClean});
  382. setLocStor('users', hActUsers); //сохранить .removed
  383. }
  384. if(stripeN >1)
  385. diag.style.height = 110 * stripeN +'px';
  386. diag.classList.remove('empty');
  387. var hActTitle = '%N комментари%W <span title="%T">с %D0 по %D1</span>', days;
  388. if(ymd)
  389. $q('h2 .titl', selUser).innerHTML = hActTitle
  390. .replace(/%N/,nComm).replace(/%W/, nComm % 10 >0 && nComm % 10 <5 && Math.floor(nComm % 100 / 10) !=1 ? (nComm % 10 ==1 ?'й':'я'):'ев')
  391. .replace(/%D0/,ymd0[3] +'.'+ (ymd0[4]+1) +'.'+ (ymd0[0] +'').substr(2,2))
  392. .replace(/%D1/,ymd[3] +'.'+ (ymd[4]+1) +'.'+ (ymd[0] +'').substr(2,2))
  393. .replace('%T', Math.ceil(days = (dA[dA.length-1] - dA[0]) /86400) +' дней, '+ (nComm/days).toFixed(2) +' комм./д.' );
  394. },
  395. del1user = function(user){ //удалить данные 1 пользователя
  396. for(var i =1; i <= +hActUsers.dataLen; i++){ //всё ранее прочитанное по юзеру
  397. if(!RegExp(' '+ i +' ').test(hActUsers.removed ||'') ){
  398. var dat = getLocStor('data'+ i);
  399. if(user == dat.user){
  400. var dat = removeLocStor('data'+ i);
  401. hActUsers.removed = (hActUsers.removed ||' ')+ i +' ';
  402. }}}
  403. };
  404.  
  405.  
  406. }catch(er){
  407. 'ER_global:'.wcl(er +' (line '+(er.lineNumber||'')+')')}; //для оповещения об ошибках в Fx
  408. }} //=====/(конец основных операций)=====
  409. )(typeof unsafeWindow !='undefined'? unsafeWindow: (function(){return this})(), 'noConsole',
  410.  
  411. /*===== css =====*/
  412.  
  413. '.helpHAct,.selUser{position: absolute; z-index: 1201; top: 0; width: 100%; height: 100%;}'
  414. +'.helpHAct .under,.selUser .under{position: fixed; z-index: 1200; width: 100%; height: 100%; opacity:0.1; background:#777}'
  415. +'.helpHAct .in, .selUser .in{position: relative; z-index: 1201;max-width: 48em; margin: 120px auto 280px; padding: 12px; border: 1px solid #bbb; box-shadow: 0 0 7px 4px #a4b39D/*#afb6af*/; background: #fff;}'
  416. +'.selUser .in .inUser{float: right; padding: 1px 1px 3px; border: 1px solid #ccc;}'
  417. +'.selUser .in h2{margin-bottom: 6px;}'
  418. +'.selUser .diag{position: relative; height: 100px; margin-top: 12px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABuAgMAAACsF16hAAAADFBMVEXu7u7x8fHz8/MAAACwIVgQAAAABHRSTlP///8AQCqp9AAAAAFiS0dEAIgFHUgAAAA9SURBVHjaY2AYBYMahCKBsDQkgCITtQoBVuKUWTUqMyozKjMqMyozKjMMZJABoyACCIw2G6gO/uMCI0cGAF7MVC7yox7wAAAAAElFTkSuQmCC) 6px 0;}'
  419. +'.selUser .comm{position: absolute; width: 8px; height: 8px; opacity: 0.2; background: #8b4;}'
  420. +'.selUser .hAActive, .user_header .hAActive{background-color: #f6f8e0/*#fff2d8*/}'
  421. +'.selUser button, .user_header button{height: 1.6em; margin: 10px 0 0 1px; padding: 0 4px 1px; line-height: 1.3em;'
  422. +'box-shadow: 0 0 2px rgba(255, 255, 255, 0.4) inset, 0 0 2px rgba(0, 0, 0, 0.2); transition-duration: 0.2s; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8); border-radius: 3px; border: 1px solid #e9d8e8; border-color: #e9DeD8 #bccbbb #9eae9e; background-color: #c8ead0/*#cfe8c4*/;}'
  423. +'.selUser button{margin-top: 5px}.user_header .rating{margin-right: 14px;}'
  424. +'.selUser button:hover,.user_header button:hover{background-color: #f2fddb/*#fcfcfc*/;}'
  425. +'.selUser .diag.empty{background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAASFBMVEXu7u708uv7+PP7+fP28+z38+z08ev7+PLy8Oz69/H6+PP7+PHx8Oz69/L49Ovs7+749Oz38+v28uz18uz49O328+v49O78+fSGxIqmAAAAAWJLR0QAiAUdSAAAAE9JREFUeNqVzjEOwCAMA0BLWbqEASn0/z8F2lpKIaDW4w22AcaEyb/t0MDku2nUp6vdtDUEVhCYYLIsRPP24NtuHOzC0TpO1tDcF6Kzk1gBDdAFF3BuHgsAAAAASUVORK5CYII=)}'
  426. +'.selUser .hADel{position: relative; height: 14px; line-height: 1px; border-radius: 6px; margin:-0.6em; top:-0.9em; left: -3px; padding:0 0 1px; border: 1px solid #d99; font-size:10px; background-color: #ea7852; opacity: 0.15;}.selUser .hADel:hover{background-color: #E65E30; color: #fbb; opacity: 0.8}'
  427. +'.selUser button:hover +.hADel{opacity: 0.5}',
  428.  
  429. /*===== help string =====*/
  430.  
  431. '<div class=in><h2>Просмотр активности комментариев пользователей</h2><br><br>После установки юзерскрипта <b>habrActivity</b> на страницах комментариев пользователей (и только на них) появляются 4 кнопки: "Старт", "Данные", "Стереть", "Подробности".<br><br>'
  432.  
  433. +'При нажатии на "<b>Старт</b>" запускается цикл чтения данных с перебором страниц комментариев для данного пользователя, а кнопка меняет название на "Стоп". Прочитывается не более 25 страниц в автоматическом режиме и читается интервал комментариев за не более 365 дней. В любой момент цикл автоматического чтения страниц останавливается вручную кнопкой "<b>Стоп</b>". Если нужно читать больше 365 дней, чтение возобновляется по кнопке "Старт".<br><br>'
  434.  
  435. +'С каждой страницы собирается информация о датах создания комментариев и, возможно, впоследствии более сложная, и запоминается в хранилище. Таким образом, накапливается информация о комментариях разных пользователей в разные периоды времени. Хранятся только массивы чисел - например, даты написания комментариев. В последующих версиях возможно расширить статистику на сбор оценок, объёма текстов, количество ссылок и картинок и подобное.<br><br>'
  436.  
  437. +'Чтобы просмотреть накопленные данные, нажимают кнопку "<b>Данные</b>". Если в браузере на данный момент хранится информация о более 1 пользователе, появится список кнопок с именами пользователей. Нажав одну из кнопок, переходим на просмотр статистики.<br><br>'
  438.  
  439. +'Просмотр статистики по датам комментариев организован аналогично инфографике Гитхаба - показ активности комментариев пользователя по дням года и дням недели. Отображается не более 420 последних дней (60 недель). Данные подготовлены для "музыкальной иллюстрации активности" http://habrahabr.ru/post/173085/ .<br><br>'
  440.  
  441. +'В результате, получаются 3 полезные вещи: смотрим активность любого пользователя в комментариях, видим наглядный график, прослушиваем в задумчивости озвучивание его. Делаем выводы.<br><br>'
  442.  
  443. +'Чтобы стереть все данные пользователя, нажимается кнопка "<b>Стереть</b>". Память браузера очищается от накопленных данных.<br><br></div>')