Farxial

tabun.utils.HugeThreadHelper v1.0.1.1

Apr 15th, 2016
146
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // http://crocodillon.com/blog/always-catch-localstorage-security-and-quota-exceeded-errors
  2. function isQuotaExceeded(e) {
  3.     var quotaExceeded = false;
  4.     if(e) {
  5.         if(e.code) {
  6.             switch(e.code) {
  7.                 case 22:
  8.                     quotaExceeded = true;
  9.                     break;
  10.                 case 1014:
  11.                     // Firefox
  12.                     if(e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
  13.                         quotaExceeded = true;
  14.                     }
  15.                     break;
  16.             }
  17.         } else if(e.number === -2147024882) {
  18.             // Internet Explorer 8
  19.             quotaExceeded = true;
  20.         }
  21.     }
  22.     return quotaExceeded;
  23. }
  24.  
  25. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString#Polyfill
  26. function pad(number) {
  27.     if(number < 10) {
  28.         return '0' + number;
  29.     }
  30.     return number;
  31. }
  32.  
  33.  
  34.  
  35. // --------------------------------------------------
  36. (function(config) {
  37.     // © Farxiel
  38.    
  39.     var DEFAULT_TIMEZONE = +4.0;
  40.    
  41.     var lang = {
  42.         // запросы
  43.         loginRequest: "Пожалуйста залогиньтесь.",
  44.         startCommentRequest: "Время или ID, комментарии начиная с которого будут помечены как новые:\n\tФормат времени: YYYY-MM-DD hh:mm:ss\n\tФормат ID: число",
  45.         timezoneRequest: "Часовой пояс:\n\tФормат: UTC±hh:mm или MSK±hh:mm\n\tПример / Москва:\tUTC+03:00, UTC+03, MSK+00:00, MSK+00\n\tПример / Киев:\tUTC+02:00, UTC+02, MSK-01:00, MSK-01",
  46.         invalidDonorFixRequest: "В соответствии с настройками, Вы можете использовать сохранённые ранее комментарии. Сделать это?",
  47.         // уведомления
  48.         readyNotify: "Готово.",
  49.         debugElapsedTimeNotifyPrefix: "Время работы: ",
  50.         timezoneReadyNotify: "Часовой пояс получен.",
  51.         logoutNotify: "Выход с аккаунта выполнен.",
  52.         loginNotify: "Вход в аккаунт выполнен.",
  53.         topicReadyNotify: "Источник комментариев загружен.",
  54.         importCommentsNotify: "Импорт комментариев...",
  55.         processCommentsNotify: "Обработка импортированных комментариев...",
  56.         fixAndUtaPageNotify: "Восстановление повреждённой страницы и дополнение до залогиненного состояния...",
  57.         setViewStateNotify: "Установка статусов прочитанности комментариев...",
  58.         loadNewCommentsNotify: "Загрузка новых комментариев...",
  59.         timeReplaceNotifyPrefix: "Замена времени:",
  60.         debugNcSavedNotifyPrefix: "Комментарий сохранён как точка отсчёта, ID=",
  61.         // ошибки
  62.         lsSaveError: "Ошибка при сохранении данных в localStorage.\n",
  63.         lsQuotaExceededErrorPrefix: "Превышен лимит хранилища. Попробуйте увеличить его.\nВ Firefox это можно сделать по адресу about:config, изменив параметр \"dom.storage.default_quota\" (размер в Кб). Текущий размер комментариев — ",
  64.         lsQuotaExceededErrorSuffix: " байт.\n\nOK — Попытаться сохранить комментарии ещё раз\nОтмена — Пропустить и продолжить без сохранения комментариев",
  65.         invalidDonorError: "Ошибка доступа к элементам страницы-донора. Возможно, она не смогла загрузиться из-за того что также сломалась.",
  66.         invalidInputError: "Неверный формат.",
  67.         invalidConfigError: "Неверная конфигурация.",
  68.         unrelatedAddressError: "Ошибка:\nДанный скрипт не предназначен для страниц, расположенных по адресам, таким, как текущий.",
  69.         commonError: "Что-то пошло не так.",
  70.         // прочее
  71.         daysShort: "д",
  72.         months_gen: [
  73.             "января",
  74.             "февраля",
  75.             "марта",
  76.             "апреля",
  77.             "мая",
  78.             "июня",
  79.             "июля",
  80.             "августа",
  81.             "сентября",
  82.             "октября",
  83.             "ноября",
  84.             "декабря"
  85.         ]
  86.     };
  87.    
  88.     var loc = location.pathname.match(/^\/blog(\/[A-Za-z\d\-_]+)?\/(\d+)\.html/);
  89.     if(loc == null) {
  90.         return alert(config.messageHeader + lang.unrelatedAddressError);
  91.     }
  92.     var tidStr = loc[2];
  93.     var tid = parseInt(tidStr);
  94.    
  95.     var lsItemName = {
  96.         "lastViewedComment": "tabun.utils.HugeThreadHelper.lastViewedComment,topic"+tidStr,
  97.         "timezone": "tabun.utils.HugeThreadHelper.timezone",
  98.         "commentsBak": "tabun.utils.HugeThreadHelper.commentsBak,topic"+tidStr,
  99.         "footerBak": "tabun.utils.HugeThreadHelper.footerBak",
  100.         "commentsScriptBak": "tabun.utils.HugeThreadHelper.commentsScriptBak,topic"+tidStr
  101.     };
  102.    
  103.     var context = {
  104.         "state":0,
  105.         "lastCommentId":0,
  106.         "handler2_mo":null,
  107.         "timezone":null,
  108.         "h1_control":0,
  109.         "h2_control":0,
  110.         "debugLTF_count":0
  111.     };
  112.     var time0 = Date.now();
  113.    
  114.     // создание временного фрейма, в котором мы разлогинимся, загрузим пост и возьмём из него 1-ю часть комментов и в котором залогинимся обратно
  115.     var workFrame = document.createElement("iframe");
  116.     with(workFrame.style) {
  117.         position = "absolute";
  118.         left = "0px";
  119.         top = "0px";
  120.         width = "100%";
  121.         height = "100%";
  122.         border = "2px dashed black";
  123.     }
  124.     document.body.appendChild(workFrame);
  125.    
  126.     function workFrameLoadHandler(e, important) {
  127.         if(important !== true && context.h1_control > 0) {
  128.             if(context.h1_control & 1) setTimeout(arguments.callee, 10, e, important);
  129.             return;
  130.         }
  131.        
  132.         context.state++;
  133.         switch(context.state) {
  134.             case 1:
  135.                 console.log(config.logHeader + lang.logoutNotify);
  136.                 // загружаем страницу поста ...
  137.                 workFrame.contentWindow.location.replace(location.href);
  138.                 break;
  139.             case 2:
  140.                 console.log(config.logHeader + lang.topicReadyNotify);
  141.                 // ... копируем оттуда комменты на основную страницу ...
  142.                 importComments(workFrame.contentDocument);
  143.                 // ... восстанавливаем элементы, не загруженные из-за отказа скрипта на сервере (некоторые делаем уже такими, какими их видит залогиненный пользователь)
  144.                 fixAndUtaPage(workFrame.contentDocument);
  145.                 // данные во фрейме больше не нужны, для уменьшения занимаемой памяти удалим из фрейма весь груз
  146.                 workFrame.contentDocument.body.innerHTML = "";
  147.                 // обрабатываем скопированные комменты:
  148.                 // 1. дополняем их до версий, какие видит залогиненный пользователь
  149.                 // 2. ищем коммент с максмальным ID
  150.                 processComments();
  151.                 // сохраняем максимальный ID коммента в спец. поле, с которым работает Табун
  152.                 document.getElementById("new_comments_counter").setAttribute("data-id-comment-last", context.lastCommentId);
  153.                 // переходим на страницу логина
  154.                 workFrame.contentWindow.location.replace("/login/");
  155.                 break;
  156.             default:
  157.                 // ещё не залогинились
  158.                 if(workFrame.contentDocument.getElementById("dropdown-user") == null) {
  159.                     // если разрешено, отображаем приглашение залогиниться
  160.                     if(config.enableLoginNotify) alert(config.messageHeader + lang.loginRequest);
  161.                 // уже залогинились
  162.                 } else {
  163.                     console.log(config.logHeader + lang.loginNotify);
  164.                     // удаляем этот фрейм с основной страницы
  165.                     document.body.removeChild(workFrame);
  166.                    
  167.                     // симулируем событие, на которое повешена инициализация поддержки комментов в табунском скрипте комментов
  168.                     document.dispatchEvent(new Event("DOMContentLoaded"));
  169.                     // продолжим в handler2 после окончания загрузки новых комментов
  170.                     window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  171.                     context.handler2_mo = new MutationObserver(function(mutations) {
  172.                         context.handler2_mo.disconnect();
  173.                         context.handler2_mo = null;
  174.                         handler2();
  175.                     });
  176.                     context.handler2_mo.observe(document.getElementById("new_comments_counter"), {"attributes": true});
  177.                     // подгружаем новые комменты
  178.                     loadNewComments();
  179.                 }
  180.         }
  181.     }
  182.    
  183.     function start1() {
  184.         // отслеживаем, когда загрузится страница (каждый раз после обновления адреса фрейма) для запуска обработки страницы по каждому адресу
  185.         window.addEventListener("DOMFrameContentLoaded", function(e) {
  186.             if(e.target !== workFrame) return;
  187.             if(context.h2_control & 2) return;
  188.             context.h1_control = 1;
  189.             workFrame.removeEventListener("load", workFrameLoadHandler);
  190.             workFrameLoadHandler(e, true);
  191.             context.h1_control++;
  192.         });
  193.         workFrame.addEventListener("load", workFrameLoadHandler);
  194.        
  195.         // разлогиниваемся и запускаем цикл переходов внутри фрейма
  196.         workFrame.src = document.querySelector(".item-signout>a").href;
  197.     }
  198.    
  199.     // проверка [и получение] сохранённого часового пояса
  200.     if(config.timezoneUseCache) {
  201.         var tzSpec = localStorage.getItem(lsItemName.timezone);
  202.         if(tzSpec != null) {
  203.             tzSpec = tzSpec.split(":");
  204.             if(parseFloat(tzSpec[0]) == config.timezoneRevision) {
  205.                 context.timezone = parseFloat(tzSpec[1]);
  206.                 console.log(config.logHeader + lang.timezoneReadyNotify);
  207.             }
  208.         }
  209.     }
  210.    
  211.     // запрос часового пояса у пользователя
  212.     function requestTimezone() {
  213.         var tzText = prompt(config.messageHeader + lang.timezoneRequest, "UTC+03:00"), match;
  214.         if(tzText != undefined && (match = tzText.match(/^(UTC|MSK)([\+\-]\d+)(\:(\d+))?$/)) != null) {
  215.             var tzHours = parseInt(match[2]);
  216.             var tzMinutes = parseInt(match[4] || 0) * (tzHours > 0 ? 1 : -1);
  217.             if(match[1] == "MSK") tzHours += 3;
  218.             return tzHours + (tzMinutes / 60);
  219.         } else {
  220.             if(tzText != undefined) alert(config.messageHeader + lang.invalidInputError);
  221.             return requestTimezone();
  222.         }
  223.     }
  224.    
  225.     // выбрать и (если разрешено) сохранить часовой пояс
  226.     function setAndSaveTimezone(timezone) {
  227.         if(isNaN(timezone)) return alert(config.messageHeader + lang.commonError);
  228.         context.timezone = timezone;
  229.         if(config.timezoneUseCache) {
  230.             var tzSpec = config.timezoneRevision.toString() + ":" + timezone.toString();
  231.             localStorage.setItem(lsItemName.timezone, tzSpec);
  232.         }
  233.         console.log(config.logHeader + lang.timezoneReadyNotify);
  234.     }
  235.    
  236.     if(context.timezone == null) {
  237.         // если часовой пояс ещё не известен (или недействителен) и если он должен загрузиться из настроек сайта - сначала откроем их
  238.         switch(config.timezoneSource) {
  239.             case "frame":
  240.                 var settingsPageFrame = document.createElement("iframe");
  241.                 with(settingsPageFrame.style) {
  242.                     position = "absolute";
  243.                     right = "0px";
  244.                     top = "0px";
  245.                     width = "50%";
  246.                     height = "100%";
  247.                     border = "2px dashed blue";
  248.                 }
  249.                 document.body.appendChild(settingsPageFrame);
  250.                 settingsPageFrame.addEventListener("load", function(e) {
  251.                     settingsPageFrame.removeEventListener(e.type, arguments.callee);
  252.                     setAndSaveTimezone(parseFloat(settingsPageFrame.contentDocument.getElementsByName("settings_general_timezone")[0].value));
  253.                     document.body.removeChild(settingsPageFrame);
  254.                     // запукаем цикл переходов
  255.                     start1();
  256.                 });
  257.                 settingsPageFrame.src = "/settings/tuning/";
  258.                 break;
  259.             default:
  260.                 alert(config.messageHeader + lang.invalidConfigError);
  261.             case "input":
  262.                 setAndSaveTimezone(requestTimezone());
  263.                 start1();
  264.                 break;
  265.         }
  266.     // если ч.п. известен и действителен
  267.     } else start1();
  268.    
  269.     function handler2() {
  270.         // указываем, какие комменты считать прочитанными, а какие новыми
  271.         setViewState(false);
  272.         // подготавливаем и отображаем сообщение о готовности
  273.         // вычисление времени работы скрипта (включая время загрузки данных и задержки на сообщениях/запросах), преобразование в строку
  274.         var timeElapsed = Date.now() - time0;
  275.         var timeElapsedDays = Math.floor(timeElapsed / 86400000);
  276.         var timeElapsedStr = (timeElapsedDays > 0 ? timeElapsedDays.toString() + lang.daysShort + ", " : "") + new Date(timeElapsed).toISOString().substr(11, 8);
  277.         console.log(config.logHeader + lang.readyNotify + config.messageInlineSep + lang.debugElapsedTimeNotifyPrefix + timeElapsedStr);
  278.         // если разрешено, также отображаем в окне
  279.         if(config.enableReadyNotify) alert(config.messageHeader + lang.readyNotify + config.messageSep + lang.debugElapsedTimeNotifyPrefix + timeElapsedStr);
  280.     }
  281.    
  282.     function setCommentsHTML(commentsHTML) {
  283.         var tempEl1;
  284.         // создание контейнера, копирование в него всего HTML-кода
  285.         tempEl1 = createElement("div", "comments", "comments", {
  286.             "innerHTML": commentsHTML
  287.         });
  288.         document.getElementById("content").appendChild(tempEl1);
  289.     }
  290.    
  291.     function importComments(sourceDocument) {
  292.         console.log(config.logHeader + lang.importCommentsNotify);
  293.        
  294.         var comments = sourceDocument.getElementById("comments");
  295.         if(comments != null) {
  296.             var commentsHTML = comments.innerHTML;
  297.             setCommentsHTML(commentsHTML);
  298.             if(config.autoBackupDonor) (function() {
  299.                 try {
  300.                     localStorage.setItem(lsItemName.commentsBak, commentsHTML);
  301.                 } catch(e) {
  302.                     if(isQuotaExceeded(e)) {
  303.                         if(confirm(config.messageHeader + lang.lsQuotaExceededErrorPrefix + commentsHTML.length.toString() + lang.lsQuotaExceededErrorSuffix)) arguments.callee();
  304.                     } else alert(config.messageHeader + lang.lsSaveError + config.messageSep + e.toString());
  305.                 }
  306.             })();
  307.         } else {
  308.             var commentsHTML = localStorage.getItem(lsItemName.commentsBak);
  309.             if(commentsHTML != null && commentsHTML.length > 0) {
  310.                 if(confirm(config.messageHeader + lang.invalidDonorError + config.messageSep + lang.invalidDonorFixRequest)) {
  311.                     setCommentsHTML(commentsHTML);
  312.                 } else {
  313.                     alert(config.messageHeader + lang.commonError);
  314.                 }
  315.             } else alert(config.messageHeader + lang.invalidDonorError);
  316.         }
  317.     }
  318.    
  319.     function processComments() {
  320.         console.log(config.logHeader + lang.processCommentsNotify);
  321.         var tempEl1, tempEl2;
  322.        
  323.         var lastCommentId = 0;
  324.         var tzFixOffset = context.timezone - DEFAULT_TIMEZONE;
  325.        
  326.         var commentList = document.getElementById("comments").getElementsByClassName("comment");
  327.         for(var i=0; i<commentList.length; i++) {
  328.             var comment = commentList[i];
  329.             var commentInfo = comment.querySelector("ul.comment-info");
  330.            
  331.             var idStr = comment.getAttribute("data-id");
  332.             var id = parseInt(idStr);
  333.            
  334.             // если у коммента нет блока comment-info (удалённый или заминусованный коммент), добавляем пустой
  335.             if(commentInfo == null) {
  336.                 commentInfo = createElement("ul", "comment-info", null);
  337.                 // добавляем ссылку на комментарий
  338.                 tempEl1 = createElement("li", "comment-link", null);
  339.                 tempEl2 = createElement("a", null, null, {
  340.                     "href": "#comment"+idStr,
  341.                     "title": "Ссылка на комментарий"
  342.                 });
  343.                 tempEl3 = createElement("i", "icon-synio-link", null);
  344.                 tempEl2.appendChild(tempEl3);
  345.                 tempEl1.appendChild(tempEl2);
  346.                 commentInfo.appendChild(tempEl1);
  347.                 comment.appendChild(commentInfo);
  348.             }
  349.            
  350.             // ищем коммент с максимальным ID
  351.             if(id > lastCommentId) lastCommentId = id;
  352.            
  353.             // добавление кнопки "В избранное"
  354.             tempEl1 = createElement("li", "comment-favourite");
  355.             tempEl2 = createElement("div", "favourite", null, {
  356.                 "onclick": "return ls.favourite.toggle("+idStr+", this, 'comment');",
  357.                 "innerHTML": "В избранное"
  358.             });
  359.             tempEl1.appendChild(tempEl2);
  360.             tempEl2 = createElement("span", "favourite-count", "fav_count_comment_"+idStr, {"hidden":"hidden"});
  361.             tempEl1.appendChild(tempEl2);
  362.             commentInfo.insertBefore(tempEl1, commentInfo.querySelector("li.comment-link"));
  363.            
  364.             // добавление кнопки "Ответить"
  365.             tempEl1 = document.createElement("li");
  366.             tempEl2 = createElement("a", "reply-link link-dotted", null, {
  367.                 "href": "#",
  368.                 "onclick": "ls.comments.toggleCommentForm("+idStr+"); return false;",
  369.                 "innerHTML": "Ответить"
  370.             });
  371.             tempEl1.appendChild(tempEl2);
  372.             commentInfo.appendChild(tempEl1);
  373.  
  374.             // если наш часовой пояс не московский - исправляем время в московских метках времени
  375.             if(context.timezone != DEFAULT_TIMEZONE) {
  376.                 var timeEl = commentInfo.querySelector("li.comment-date>time");
  377.                 if(timeEl != null) {
  378.                     // преобразуем компоненты времени в числа
  379.                     var timeC = timeEl.getAttribute("datetime").split(/[^\d]/).map(function(v) {return parseInt(v);});
  380.                     // 0..5 - компоненты времени
  381.                     var newTime = new Date(timeC[0], timeC[1]-1, timeC[2], timeC[3], timeC[4], timeC[5], 0);
  382.                     newTime.setTime(newTime.getTime()+(3600000*tzFixOffset));
  383.                     var oldTimeLocaleString = timeEl.innerHTML;
  384.                     var newTimeDisplayStr = newTime.getDate().toString() + " " + lang.months_gen[newTime.getMonth()] + " " + newTime.getFullYear().toString() + ", " + pad(newTime.getHours()) + ":" + pad(newTime.getMinutes()) + ":" + pad(newTime.getSeconds());
  385.                     timeEl.innerHTML = newTimeDisplayStr;
  386.                     timeEl.setAttribute("title", newTimeDisplayStr);
  387.                     timeEl.setAttribute("datetime", newTime.toISOString());
  388.                     timeEl.setAttribute("data-timestamp", newTime.getTime().toString());
  389.                     if(config.debugLTF && (config.debugLTF_JFN == -1 || config.debugLTF_JFN > context.debugLTF_count)) {
  390.                         console.log(config.logHeader + lang.timeReplaceNotifyPrefix + " " + oldTimeLocaleString.trim() + " → " + timeEl.innerHTML);
  391.                         context.debugLTF_count++;
  392.                     }
  393.                 }
  394.             }
  395.         }
  396.         context.lastCommentId = lastCommentId;
  397.     }
  398.    
  399.     function fixAndUtaPage(sourceDocument) {
  400.         console.log(config.logHeader + lang.fixAndUtaPageNotify);
  401.         var tempEl1, tempEl2, tempEl3, tempEl4, tempEl5;
  402.        
  403.         // добавление блока подписки на комменты
  404.         tempEl1 = document.querySelector("#comments>.comments-header");
  405.         tempEl2 = createElement("div", "subscribe");
  406.         tempEl3 = createElement("input", "input-checkbox", "comment_subscribe", {
  407.             "type": "checkbox",
  408.             "onchange": "ls.subscribe.toggle('topic_new_comment', '"+tidStr+"', '', this.checked);"
  409.         });
  410.         tempEl2.appendChild(tempEl3);
  411.         tempEl3 = createElement("label", null, null, {
  412.             "for": "comment_subscribe",
  413.             "innerHTML": "подписаться на новые комментарии"
  414.         });
  415.         tempEl2.appendChild(tempEl3);
  416.         tempEl1.insertBefore(tempEl2, tempEl1.lastElementChild);
  417.        
  418.         // добавление формы загрузки картинок
  419.         tempEl1 = createElement("div", "modal modal-image-upload", "window_upload_img");
  420.         tempEl2 = createElement("header", "modal-header", null);
  421.         tempEl3 = createElement("h3", null, null, {"innerHTML": "Вставка изображения"});
  422.         tempEl2.appendChild(tempEl3);
  423.         tempEl3 = createElement("a", "close jqmClose", null, {"href": "#"});
  424.         tempEl2.appendChild(tempEl3);
  425.         tempEl1.appendChild(tempEl2);
  426.         tempEl2 = createElement("div", "modal-content");
  427.         tempEl3 = createElement("ul", "nav nav-pills nav-pills-tabs");
  428.         tempEl4 = createElement("li", "active js-block-upload-img-item", null, {"data-type": "pc"});
  429.         tempEl5 = createElement("a", null, null, {
  430.             "href": "#",
  431.             "innerHTML": "С компьютера"
  432.         });
  433.         tempEl4.appendChild(tempEl5);
  434.         tempEl3.appendChild(tempEl4);
  435.         tempEl4 = createElement("li", "js-block-upload-img-item", null, {"data-type": "link"});
  436.         tempEl5 = createElement("a", null, null, {
  437.             "href": "#",
  438.             "innerHTML": "Из интернета"
  439.         });
  440.         tempEl4.appendChild(tempEl5);
  441.         tempEl3.appendChild(tempEl4);
  442.         tempEl2.appendChild(tempEl3);
  443.         tempEl3 = createElement("form", "tab-content js-block-upload-img-content", "block_upload_img_content_pc", {
  444.             "method": "POST",
  445.             "action": "",
  446.             "enctype": "multipart/form-data",
  447.             "onsubmit": "return false;",
  448.             "data-type": "pc"
  449.         });
  450.         tempEl4 = document.createElement("p");
  451.         tempEl5 = createElement("label", null, null, {
  452.             "innerHTML": "Файл:",
  453.             "for": "img_file"
  454.         });
  455.         tempEl4.appendChild(tempEl5);
  456.         tempEl5 = createElement("input", "input-text input-width-full", "img_file", {
  457.             "type": "file",
  458.             "name": "img_file",
  459.             "value": ""
  460.         });
  461.         tempEl4.appendChild(tempEl5);
  462.         tempEl3.appendChild(tempEl4);
  463.         tempEl4 = document.createElement("p");
  464.         tempEl5 = createElement("label", null, null, {
  465.             "innerHTML": "Описание:",
  466.             "for": "form-image-title"
  467.         });
  468.         tempEl4.appendChild(tempEl5);
  469.         tempEl5 = createElement("input", "input-text input-width-full", "form-image-title", {
  470.             "type": "text",
  471.             "name": "title",
  472.             "value": ""
  473.         });
  474.         tempEl4.appendChild(tempEl5);
  475.         tempEl3.appendChild(tempEl4);
  476.         tempEl4 = createElement("button", "button button-primary main-upl-btn", null, {
  477.             "type": "submit",
  478.             "onclick": "ls.tools.uploadImg('block_upload_img_content_pc','form_comment_text');",
  479.             "innerHTML": "Загрузить"
  480.         });
  481.         tempEl3.appendChild(tempEl4);
  482.         tempEl4 = createElement("button", "button jqmClose", null, {
  483.             "type": "submit",
  484.             "innerHTML": "Отмена"
  485.         });
  486.         tempEl3.appendChild(tempEl4);
  487.         tempEl2.appendChild(tempEl3);
  488.         tempEl3 = createElement("form", "tab-content js-block-upload-img-content", "block_upload_img_content_link", {
  489.             "method": "POST",
  490.             "action": "",
  491.             "enctype": "multipart/form-data",
  492.             "onsubmit": "return false;",
  493.             "data-type": "link"
  494.         });
  495.         tempEl3.style.display = "none";
  496.         tempEl4 = document.createElement("p");
  497.         tempEl5 = createElement("label", null, null, {
  498.             "innerHTML": "Ссылка на изображение:",
  499.             "for": "img_file"
  500.         });
  501.         tempEl4.appendChild(tempEl5);
  502.         tempEl5 = createElement("input", "input-text input-width-full", "img_url", {
  503.             "type": "text",
  504.             "name": "img_url",
  505.             "value": "http://"
  506.         });
  507.         tempEl4.appendChild(tempEl5);
  508.         tempEl3.appendChild(tempEl4);
  509.         tempEl4 = document.createElement("p");
  510.         tempEl5 = createElement("label", null, null, {
  511.             "innerHTML": "Описание:",
  512.             "for": "form-image-url-title"
  513.         });
  514.         tempEl4.appendChild(tempEl5);
  515.         tempEl5 = createElement("input", "input-text input-width-full", "form-image-url-title", {
  516.             "type": "text",
  517.             "name": "title",
  518.             "value": ""
  519.         });
  520.         tempEl4.appendChild(tempEl5);
  521.         tempEl3.appendChild(tempEl4);
  522.         tempEl4 = createElement("button", "button button-primary", null, {
  523.             "type": "submit",
  524.             "onclick": "ls.tools.uploadImg('block_upload_img_content_link','form_comment_text');",
  525.             "innerHTML": "Загрузить"
  526.         });
  527.         tempEl3.appendChild(tempEl4);
  528.         tempEl4 = createElement("button", "button jqmClose", null, {
  529.             "type": "submit",
  530.             "innerHTML": "Отмена"
  531.         });
  532.         tempEl3.appendChild(tempEl4);
  533.         tempEl2.appendChild(tempEl3);
  534.         tempEl1.appendChild(tempEl2);
  535.         document.getElementById("content").appendChild(tempEl1);
  536.        
  537.         // добавление кнопки и формы добавления комментария
  538.         tempEl1 = createElement("h4", "reply-header", "comment_id_0");
  539.         tempEl2 = createElement("a", "link-dotted", null, {
  540.             "href": "#",
  541.             "innerHTML": "Оставить комментарий",
  542.             "onclick": "ls.comments.toggleCommentForm(0); return false;"
  543.         });
  544.         tempEl1.appendChild(tempEl2);
  545.         document.getElementById("content").appendChild(tempEl1);
  546.         tempEl1 = createElement("div", "reply", "reply");
  547.         tempEl2 = createElement("form", null, "form_comment", {
  548.             "method": "POST",
  549.             "enctype": "multipart/form-data",
  550.             "onsubmit": "return false;"
  551.         });
  552.         tempEl3 = createElement("textarea", "markitup-editor input-width-full", "form_comment_text", {"name": "comment_text"});
  553.         tempEl2.appendChild(tempEl3);
  554.         tempEl3 = createElement("button", "button button-primary", "comment-button-submit", {
  555.             "type": "submit",
  556.             "name": "submit_comment",
  557.             "onclick": "ls.comments.add('form_comment', "+tidStr+", 'topic'); return false;",
  558.             "innerHTML": "добавить"
  559.         });
  560.         tempEl2.appendChild(tempEl3);
  561.         tempEl3 = createElement("button", "button", null, {
  562.             "type": "button",
  563.             "onclick": "ls.comments.preview();",
  564.             "innerHTML": "предпросмотр"
  565.         });
  566.         tempEl2.appendChild(tempEl3);
  567.         tempEl3 = createElement("input", null, "form_comment_reply", {
  568.             "type": "hidden",
  569.             "name": "reply",
  570.             "value": "0"
  571.         });
  572.         tempEl2.appendChild(tempEl3);
  573.         tempEl3 = createElement("input", null, null, {
  574.             "type": "hidden",
  575.             "name": "cmt_target_id",
  576.             "value": tidStr
  577.         });
  578.         tempEl2.appendChild(tempEl3);
  579.         tempEl1.appendChild(tempEl2);
  580.         document.getElementById("content").appendChild(tempEl1);
  581.        
  582.         // копирование конца страницы
  583.         // копирование footer
  584.         tempEl1 = createElement("footer", null, "footer", {"innerHTML": (function() {
  585.             var el = sourceDocument.getElementById("footer");
  586.             if(el != null) {
  587.                 if(config.autoBackupDonor) localStorage.setItem(lsItemName.footerBak, el.innerHTML);
  588.                 return el.innerHTML;
  589.             } else return localStorage.getItem(lsItemName.footerBak);
  590.         })()});
  591.         document.getElementById("container").appendChild(tempEl1);
  592.         // добавление toolbar
  593.         tempEl1 = createElement("aside", "toolbar", "toolbar");
  594.         // заполнение toolbar
  595.         tempEl2 = createElement("section", "toolbar-update", "update");
  596.         tempEl3 = createElement("a", "update-comments", "update-comments", {
  597.             "href": "#",
  598.             "onclick": "ls.comments.load("+tidStr+", 'topic'); return false;"
  599.         });
  600.         tempEl4 = document.createElement("i");
  601.         tempEl3.appendChild(tempEl4);
  602.         tempEl2.appendChild(tempEl3);
  603.         tempEl3 = createElement("div", "new-comments h-hidden", "new_comments_counter", {
  604.             "title": "Число новых комментариев",
  605.             "onclick": "ls.comments.goToNextComment(); return false;"/*,
  606.             "data-id-comment-last": context.lastCommentId*/
  607.         });
  608.         tempEl2.appendChild(tempEl3);
  609.         tempEl1.appendChild(tempEl2);
  610.         document.body.appendChild(tempEl1);
  611.         // копирование скрипта комментов
  612.         tempEl1 = createElement("script", null, null, {
  613.             "src": (function() {
  614.                 var temp = sourceDocument.querySelectorAll("script[src*=comments]"), src;
  615.                 if(temp.length > 0) {
  616.                     src = temp[temp.length-1].src;
  617.                     if(config.autoBackupDonor) localStorage.setItem(lsItemName.commentsScriptBak, src);
  618.                     return src;
  619.                 } else {
  620.                     return localStorage.getItem(lsItemName.commentsScriptBak) || "";
  621.                 }
  622.             })(),
  623.             "type": "text/javascript"
  624.         });
  625.         document.body.appendChild(tempEl1);
  626.     }
  627.    
  628.     function loadNewComments() {
  629.         console.log(config.logHeader + lang.loadNewCommentsNotify);
  630.         try {
  631.             ls.comments.load(tid, "topic");
  632.         } catch(err) {
  633.         }
  634.     }
  635.        
  636.     function compareAgeAndSetViewState(comment, required, current) {
  637.         if(current > required || (current == required && config.ncInclusiveLogic)) {
  638.             comment.classList.add("comment-new");
  639.             return 1;
  640.         } else {
  641.             comment.classList.remove("comment-new");
  642.             return 0;
  643.         }
  644.     }
  645.    
  646.     function setViewState(ncForceRequest) {
  647.         console.log(config.logHeader + lang.setViewStateNotify);
  648.         // спрашиваем у юзера время, комменты начиная с которого будут помечены как новые, предварительно загрузив предыдущее введённое значение (если было сохранено)
  649.         var ncSpecDefault = "2015-04-07 00:00:00";
  650.         var ncSpecPrev = localStorage.getItem(lsItemName.lastViewedComment) || ncSpecDefault;
  651.         var ncSpec = (config.ncDisableRequest && !ncForceRequest) ? ncSpecPrev : (prompt(config.messageHeader + lang.startCommentRequest, ncSpecPrev) || ncSpecPrev);
  652.         var match;
  653.         var newCommentsCount = 0;
  654.        
  655.         // проверка ввода/сохранения на соответствие формату времени
  656.         if((match = ncSpec.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})(\:(\d{2}))?$/)) != null) {
  657.             match[6] = (match[7] == undefined) ? 0 : match[7];
  658.             match[7] = 0;
  659.         // преобразуем компоненты времени в числа
  660.             var ncTC = match.slice(1).map(function(v) {return parseInt(v);});
  661.         // 0..5 - компоненты времени
  662.             var ncTime = new Date(ncTC[0], ncTC[1]-1, ncTC[2], ncTC[3], ncTC[4], ncTC[5], 0).getTime();
  663.            
  664.             var comments = document.getElementsByClassName("comment");
  665.         // проходимся по комментам
  666.         // +считаем комменты, которым мы поставили статус "новый"
  667.             for(var i=0; i<comments.length; i++) {
  668.                 var comment = comments[i];
  669.                 var cTimeEl = comment.querySelector("ul.comment-info>li.comment-date>time");
  670.                 if(cTimeEl != null) {
  671.                     var cTime;
  672.                     var cTimeSS = cTimeEl.getAttribute("data-timestamp");
  673.                     if(cTimeSS != null) {
  674.                         cTime = parseFloat(cTimeSS);
  675.                     } else {
  676.                         // преобразуем компоненты времени в числа
  677.                         var cTimeC = cTimeEl.getAttribute("datetime").split(/[^\d]/).map(function(v) {return parseInt(v);});
  678.                         // 0..5 - компоненты времени
  679.                         cTime = new Date(cTimeC[0], cTimeC[1]-1, cTimeC[2], cTimeC[3], cTimeC[4], cTimeC[5], 0).getTime();
  680.                     }
  681.                     newCommentsCount += compareAgeAndSetViewState(comment, ncTime, cTime);
  682.                 }
  683.                 // если у коммента нет информации о времени, не обрабатываем его
  684.             }
  685.         // проверка ввода/сохранения на соответствие формату ID
  686.         } else if((match = ncSpec.match(/^(\d+)$/)) != null) {
  687.             var ncId = parseInt(match[1]);
  688.             var comments = document.getElementsByClassName("comment");
  689.             for(var i=0; i<comments.length; i++) {
  690.                 var comment = comments[i];
  691.                 newCommentsCount += compareAgeAndSetViewState(comment, ncId, parseInt(comment.getAttribute("data-id")));
  692.             }
  693.         } else {
  694.             alert(config.messageHeader + lang.invalidInputError);
  695.             setViewState(ncForceRequest);
  696.             return;
  697.         }
  698.         // ввод был успешным
  699.         // если разрешено - сохраняем
  700.         if(config.ncSaveInput) localStorage.setItem(lsItemName.lastViewedComment, ncSpec);
  701.        
  702.         // ставим счётчик новых комментов и показываем/скрываем его
  703.         var new_comments_counter = document.getElementById("new_comments_counter");
  704.         new_comments_counter.innerHTML = newCommentsCount.toString();
  705.         new_comments_counter.classList[newCommentsCount > 0 ? "remove" : "add"]("h-hidden");
  706.        
  707.         // При выполнении ls.comments.init Табун внесёт все новые комменты в какую-то свою структуру, откуда он будет брать их при прокрутке. Я так и не понял, как мне до неё добраться, поэтому ls.comments.init().
  708.         try {
  709.             ls.comments.init();
  710.         } catch(err) {
  711.         }
  712.        
  713.         if(config.ncUpdateFromView) {
  714.             // отслеживаем прокрутку комментов
  715.             var goToNextComment_orig = ls.comments.goToNextComment;
  716.             ls.comments.goToNextComment = function() {
  717.                 goToNextComment_orig();
  718.                 try {
  719.                     var comment = document.getElementsByClassName("comment-current")[0];
  720.                     var cid = comment.getAttribute("data-id");
  721.                     localStorage.setItem(lsItemName.lastViewedComment, cid);
  722.                     if(config.debugNcSaved) console.log(config.logHeader + lang.debugNcSavedNotifyPrefix + cid);
  723.                 } catch(err) {
  724.                 }
  725.             }
  726.         }
  727.     }
  728.    
  729.     // экспорт setViewState
  730.     window.setViewState = setViewState;
  731.     // Для запроса ID/времени точки отсчёта при ncDisableRequest=true следует вызывать setViewState(true).
  732.    
  733.     // функция-макрос добавления элементов и установки им атрибутов
  734.     function createElement(tagName, className, id, attributes) {
  735.         var el = document.createElement(tagName);
  736.         if(id != null) el.id = id;
  737.         if(className != null) el.className = className;
  738.         if(attributes != undefined) for(var name in attributes) {
  739.             var value = attributes[name];
  740.             if(name == "innerHTML") el[name] = value;
  741.             else el.setAttribute(name, value);
  742.         }
  743.         return el;
  744.     }
  745. })({
  746.     // Настройки
  747.     enableLoginNotify: true,    // Отображать приглашение залогиниться.
  748.     enableReadyNotify: true,    // Отображать сообщение о готовности.
  749.     ncSaveInput: true,  // Хранить последнее введённое пользователем значение ID/времени.
  750.     ncUpdateFromView: false,    // Каждый комментарий, прокрученный кнопкой числа под кнопкой обновления комментариев, сохраняется как последний просмотренный и точка отсчёта новых комментариев. Переопределяет поведение ncSaveInput.
  751.     ncDisableRequest: false,    // Не запрашивать ID/время отсчёта новых комментариев. Имеет смысл при ncUpdateFromView=true.
  752.     ncInclusiveLogic: true,
  753.         // false: комментарий новый = ID/время комментария > ID/времени из запроса или ID последнего просмотренного комментария
  754.         // true: комментарий новый = ID/время комментария ≥ ID/времени из запроса или ID последнего просмотренного комментария
  755.     timezoneSource: "frame",    // Источник часового пояса. Запрос у пользователя (input) или страница настроек Табуна (frame).
  756.     timezoneUseCache: true, // Сохранить часовой пояс в localStorage и в дальнейшем брать оттуда.
  757.     timezoneRevision: 1,
  758.         // 1..999999999 - Версия ч.п. в настройках Табуна при timezoneUseCache=true. После изменения ч.п. в настройках Табуна, для синхронизации, измените этот параметр — кэш станет недействительным и скрипт получит новое значение часового пояса.
  759.         // [примечание] Не смотря на формат, предполагающий номер, история изменений не сохраняется, проверяется только совпадение/несовпадение.
  760.     autoBackupDonor: false, // При копировании комментариев из разлогиненной версии сохранить их (HTML-код). После того как перестанет открываться даже разлогиненная версия, можно будет использовать этот бекап.
  761.    
  762.     messageSep: "\n",
  763.     messageInlineSep: " ",
  764.     messageHeader: "",
  765.     logHeader: "[HugeThreadHelper] ",
  766.    
  767.     // Отладка
  768.     debugLTF: false,    // Для каждого заменённого элемента времени вывести в консоль, что на что заменилось.
  769.     debugLTF_JFN: 10,   // Только первые N элементов времени для debugLTF=true.
  770.     debugNcSaved: false // Для каждого сохранения точки отсчёта новых комментариев при прокрутке вывести уведомление в консоль.
  771. });
Add Comment
Please, Sign In to add comment