SHARE
TWEET

Untitled

a guest Oct 11th, 2014 429 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name            Dollchan Extension Tools
  3. // @version         14.10.9.0
  4. // @namespace       http://www.freedollchan.org/scripts/*
  5. // @author          Sthephan Shinkufag @ FreeDollChan
  6. // @copyright       (C)2084, Bender Bending Rodriguez
  7. // @description     Doing some profit for imageboards
  8. // @icon            https://raw.github.com/SthephanShinkufag/Dollchan-Extension-Tools/master/Icon.png
  9. // @updateURL       https://raw.github.com/SthephanShinkufag/Dollchan-Extension-Tools/master/Dollchan_Extension_Tools.meta.js
  10. // @run-at          document-start
  11. // @grant           GM_getValue
  12. // @grant           GM_setValue
  13. // @grant           GM_deleteValue
  14. // @grant           GM_openInTab
  15. // @grant           GM_xmlhttpRequest
  16. // @grant           unsafeWindow
  17. // @include         *
  18. // ==/UserScript==
  19.  
  20. // Copyright (c) 2014 Dollchan Extension Tools Team. See the LICENSE file for license rights and limitations (MIT).
  21.  
  22. (function de_main_func(scriptStorage) {
  23. 'use strict';
  24.  
  25. var version = '14.10.9.0',
  26. defaultCfg = {
  27.         'disabled':         0,      // script enabled by default
  28.         'language':         0,      // script language [0=ru, 1=en]
  29.         'hideBySpell':      1,      // hide posts by spells
  30.         'spells':           '',     // user defined spells
  31.         'sortSpells':       0,      // sort spells when applying
  32.         'menuHiddBtn':      1,      // menu on hide button
  33.         'hideRefPsts':      0,      // hide post with references to hidden posts
  34.         'delHiddPost':      0,      // delete hidden posts
  35.         'ajaxUpdThr':       1,      // auto update threads
  36.         'updThrDelay':      60,     //    threads update interval in sec
  37.         'noErrInTitle':     0,      //    don't show error number in title except 404
  38.         'favIcoBlink':      0,      //    favicon blinking, if new posts detected
  39.         'markNewPosts':     1,      //    new posts marking on page focus
  40.         'desktNotif':       0,      //    desktop notifications, if new posts detected
  41.         'expandPosts':      2,      // expand shorted posts [0=off, 1=auto, 2=on click]
  42.         'postBtnsCSS':      2,      // post buttons style [0=text, 1=classic, 2=solid grey]
  43.         'noSpoilers':       1,      // open spoilers
  44.         'noPostNames':      0,      // hide post names
  45.         'noPostScrl':       1,      // no scroll in posts
  46.         'correctTime':      0,      // correct time in posts
  47.         'timeOffset':       '+0',   //    offset in hours
  48.         'timePattern':      '',     //    find pattern
  49.         'timeRPattern':     '',     //    replace pattern
  50.         'expandImgs':       2,      // expand images by click [0=off, 1=in post, 2=by center]
  51.         'imgNavBtns':       1,      //    add image navigation for full images
  52.         'resizeDPI':        0,      //    honor dpi settings
  53.         'resizeImgs':       1,      //    resize large images
  54.         'minImgSize':       100,    //    minimal image's size
  55.         'zoomFactor':       25,     //    zoom images by this factor on every wheel event
  56.         'webmControl':      1,      //    control bar fow webm files
  57.         'webmVolume':       100,    //    default volume for webm files
  58.         'maskImgs':         0,      // mask images
  59.         'preLoadImgs':      0,      // pre-load images
  60.         'findImgFile':      0,      //    detect built-in files in images
  61.         'openImgs':         0,      // open images in posts
  62.         'openGIFs':         0,      //    open only GIFs in posts
  63.         'imgSrcBtns':       1,      // add image search buttons
  64.         'linksNavig':       2,      // navigation by >>links [0=off, 1=no map, 2=+refmap]
  65.         'linksOver':        100,    //    delay appearance in ms
  66.         'linksOut':         1500,   //    delay disappearance in ms
  67.         'markViewed':       0,      //    mark viewed posts
  68.         'strikeHidd':       0,      //    strike >>links to hidden posts
  69.         'noNavigHidd':      0,      //    don't show previews for hidden posts
  70.         'crossLinks':       0,      // replace http: to >>/b/links
  71.         'insertNum':        1,      // insert >>link on postnumber click
  72.         'addMP3':           1,      // embed mp3 links
  73.         'addImgs':          0,      // embed links to images
  74.         'addYouTube':       3,      // embed YouTube links [0=off, 1=onclick, 2=player, 3=preview+player, 4=only preview]
  75.         'YTubeType':        0,      //    player type [0=flash, 1=HTML5]
  76.         'YTubeWidth':       360,    //    player width
  77.         'YTubeHeigh':       270,    //    player height
  78.         'YTubeHD':          0,      //    hd video quality
  79.         'YTubeTitles':      0,      //    convert links to titles
  80.         'addVimeo':         1,      //    embed vimeo links
  81.         'ajaxReply':        2,      // posting with AJAX (0=no, 1=iframe, 2=HTML5)
  82.         'postSameImg':      1,      //    ability to post same images
  83.         'removeEXIF':       1,      //    remove EXIF data from JPEGs
  84.         'removeFName':      0,      //    remove file name
  85.         'sendErrNotif':     1,      //    inform about post send error if page is blurred
  86.         'scrAfterRep':      0,      //    scroll to the bottom after reply
  87.         'addPostForm':      2,      // postform displayed [0=at top, 1=at bottom, 2=hidden]
  88.         'hangQReply':       1,      // quick reply type [0=inline, 1=hanging]
  89.         'favOnReply':       1,      // add thread to favorites on reply
  90.         'warnSubjTrip':     0,      // warn if subject field contains tripcode
  91.         'fileThumb':        1,      // file preview area instead of file button
  92.         'addSageBtn':       1,      // email field -> sage button
  93.         'saveSage':         1,      // remember sage
  94.         'sageReply':        0,      //    reply with sage
  95.         'captchaLang':      1,      // language input in captcha [0=off, 1=en, 2=ru]
  96.         'addTextBtns':      1,      // text format buttons [0=off, 1=graphics, 2=text, 3=usual]
  97.         'txtBtnsLoc':       1,      //    located at [0=top, 1=bottom]
  98.         'passwValue':       '',     // user password value
  99.         'userName':         0,      // user name
  100.         'nameValue':        '',     //    value
  101.         'noBoardRule':      1,      // hide board rules
  102.         'noGoto':           1,      // hide goto field
  103.         'noPassword':       1,      // hide password field
  104.         'scriptStyle':      0,      // script style [0=glass black, 1=glass blue, 2=solid grey]
  105.         'userCSS':          0,      // user style
  106.         'userCSSTxt':       '',     //    css text
  107.         'expandPanel':      0,      // show full main panel
  108.         'attachPanel':      1,      // attach main panel
  109.         'panelCounter':     1,      // posts/images counter in script panel
  110.         'rePageTitle':      1,      // replace page title in threads
  111.         'animation':        1,      // CSS3 animation in script
  112.         'closePopups':      0,      // auto-close popups
  113.         'hotKeys':          1,      // enable hotkeys
  114.         'loadPages':        1,      //    number of pages that are loaded on F5
  115.         'updScript':        1,      // check for script's update
  116.         'scrUpdIntrv':      1,      //    check interval in days (every val+1 day)
  117.         'turnOff':          0,      // enable script only for this site
  118.         'textaWidth':       300,    // textarea size
  119.         'textaHeight':      115,
  120.         'qreplyRight':      0,      // hanging quick reply position
  121.         'qreplyBottom':     25
  122. },
  123.  
  124. Lng = {
  125.         cfg: {
  126.                 'hideBySpell':  ['Заклинания: ', 'Magic spells: '],
  127.                 'sortSpells':   ['Сортировать спеллы и удалять дубликаты', 'Sort spells and delete duplicates'],
  128.                 'menuHiddBtn':  ['Дополнительное меню кнопок скрытия ', 'Additional menu of hide buttons'],
  129.                 'hideRefPsts':  ['Скрывать ответы на скрытые посты*', 'Hide replies to hidden posts*'],
  130.                 'delHiddPost':  ['Удалять скрытые посты', 'Delete hidden posts'],
  131.  
  132.                 'ajaxUpdThr':   ['AJAX обновление треда ', 'AJAX thread update '],
  133.                 'updThrDelay':  [' (сек)', ' (sec)'],
  134.                 'noErrInTitle': ['Не показывать номер ошибки в заголовке', 'Don\'t show error number in title'],
  135.                 'favIcoBlink':  ['Мигать фавиконом при новых постах', 'Favicon blinking on new posts'],
  136.                 'markNewPosts': ['Выделять новые посты при переключении на тред', 'Mark new posts on page focus'],
  137.                 'desktNotif':   ['Уведомления на рабочем столе', 'Desktop notifications'],
  138.                 'expandPosts': {
  139.                         sel:        [['Откл.', 'Авто', 'По клику'], ['Disable', 'Auto', 'On click']],
  140.                         txt:        ['AJAX загрузка сокращенных постов*', 'AJAX upload of shorted posts*']
  141.                 },
  142.                 'postBtnsCSS': {
  143.                         sel:        [['Text', 'Classic', 'Solid grey'], ['Text', 'Classic', 'Solid grey']],
  144.                         txt:        ['Стиль кнопок постов*', 'Post buttons style*']
  145.                 },
  146.                 'noSpoilers':   ['Открывать текстовые спойлеры', 'Open text spoilers'],
  147.                 'noPostNames':  ['Скрывать имена в постах', 'Hide names in posts'],
  148.                 'noPostScrl':   ['Без скролла в постах', 'No scroll in posts'],
  149.                 'hotKeys':      ['Горячие клавиши ', 'Keyboard hotkeys '],
  150.                 'loadPages':    [' Количество страниц, загружаемых по F5', ' Number of pages that are loaded on F5 '],
  151.                 'correctTime':  ['Корректировать время в постах* ', 'Correct time in posts* '],
  152.                 'timeOffset':   [' Разница во времени', ' Time difference'],
  153.                 'timePattern':  [' Шаблон поиска', ' Find pattern'],
  154.                 'timeRPattern': [' Шаблон замены', ' Replace pattern'],
  155.  
  156.                 'expandImgs': {
  157.                         sel:        [['Откл.', 'В посте', 'По центру'], ['Disable', 'In post', 'By center']],
  158.                         txt:        ['раскрывать картинки по клику', 'expand images on click']
  159.                 },
  160.                 'imgNavBtns':   ['Добавлять кнопки навигации по картинкам', 'Add buttons for images navigation'],
  161.                 'resizeDPI':    ['Отображать картинки пиксель в пиксель', 'Don\'t upscale images on retina displays'],
  162.                 'resizeImgs':   ['Уменьшать в экран большие картинки', 'Resize large images to fit screen'],
  163.                 'minImgSize':   [' Минимальный размер картинок (px)', ' Minimal image\'s size (px)'],
  164.                 'zoomFactor':   [' Чувствительность зума картинок [1-100]', ' Sensibility of the images zoom [1-100]'],
  165.                 'webmControl':  ['Показывать контрол-бар для webm-файлов', 'Show control bar for webm files'],
  166.                 'webmVolume':   [' Громкость webm-файлов [0-100]', ' Default volume for webm files [0-100]'],
  167.                 'preLoadImgs':  ['Предварительно загружать картинки*', 'Pre-load images*'],
  168.                 'findImgFile':  ['Распознавать встроенные файлы в картинках*', 'Detect built-in files in images*'],
  169.                 'openImgs':     ['Скачивать полные версии картинок*', 'Download full version of images*'],
  170.                 'openGIFs':     ['Скачивать только GIFы*', 'Download GIFs only*'],
  171.                 'imgSrcBtns':   ['Добавлять кнопки для поиска картинок*', 'Add image search buttons*'],
  172.  
  173.                 'linksNavig': {
  174.                         sel:        [['Откл.', 'Без карты', 'С картой'], ['Disable', 'No map', 'With map']],
  175.                         txt:        ['навигация по >>ссылкам* ', 'navigation by >>links* ']
  176.                 },
  177.                 'linksOver':    [' задержка появления (мс)', ' delay appearance (ms)'],
  178.                 'linksOut':     [' задержка пропадания (мс)', ' delay disappearance (ms)'],
  179.                 'markViewed':   ['Отмечать просмотренные посты*', 'Mark viewed posts*'],
  180.                 'strikeHidd':   ['Зачеркивать >>ссылки на скрытые посты', 'Strike >>links to hidden posts'],
  181.                 'noNavigHidd':  ['Не отображать превью для скрытых постов', 'Don\'t show previews for hidden posts'],
  182.                 'crossLinks':   ['Преобразовывать http:// в >>/b/ссылки*', 'Replace http:// with >>/b/links*'],
  183.                 'insertNum':    ['Вставлять >>ссылку по клику на №поста*', 'Insert >>link on №postnumber click*'],
  184.                 'addMP3':       ['Добавлять плеер к mp3 ссылкам* ', 'Add player to mp3 links* '],
  185.                 'addVimeo':     ['Добавлять плеер к Vimeo ссылкам* ', 'Add player to Vimeo links* '],
  186.                 'addImgs':      ['Загружать картинки к jpg, png, gif ссылкам*', 'Load images to jpg, png, gif links*'],
  187.                 'addYouTube': {
  188.                         sel:        [
  189.                                 ['Ничего', 'Плеер по клику', 'Авто плеер', 'Превью+плеер', 'Только превью'],
  190.                                 ['Nothing', 'On click player', 'Auto player', 'Preview+player', 'Only preview']
  191.                         ],
  192.                         txt:        ['к YouTube-ссылкам* ', 'to YouTube-links* ']
  193.                 },
  194.                 'YTubeType': {
  195.                         sel:        [['Flash', 'HTML5'], ['Flash', 'HTML5']],
  196.                         txt:        ['', '']
  197.                 },
  198.                 'YTubeHD':      ['HD ', 'HD '],
  199.                 'YTubeTitles':  ['Загружать названия к YouTube-ссылкам*', 'Load titles into YouTube-links*'],
  200.  
  201.                 'ajaxReply': {
  202.                         sel:        [['Откл.', 'Iframe', 'HTML5'], ['Disable', 'Iframe', 'HTML5']],
  203.                         txt:        ['AJAX отправка постов*', 'posting with AJAX*']
  204.                 },
  205.                 'postSameImg':  ['Возможность отправки одинаковых картинок', 'Ability to post same images'],
  206.                 'removeEXIF':   ['Удалять EXIF из JPEG ', 'Remove EXIF from JPEG '],
  207.                 'removeFName':  ['Удалять имя из файлов', 'Clear file names'],
  208.                 'sendErrNotif': ['Оповещать в заголовке об ошибке отправки поста', 'Inform in title about post send error'],
  209.                 'scrAfterRep':  ['Перемещаться в конец треда после отправки', 'Scroll to the bottom after reply'],
  210.                 'addPostForm': {
  211.                         sel:        [['Сверху', 'Внизу', 'Скрытая'], ['At top', 'At bottom', 'Hidden']],
  212.                         txt:        ['форма ответа в треде', 'reply form in thread']
  213.                 },
  214.                 'favOnReply':   ['Добавлять тред в избранное при ответе', 'Add thread to favorites on reply'],
  215.                 'warnSubjTrip': ['Предупреждать при наличии трип-кода в поле "Тема"', 'Warn if "Subject" field contains trip-code'],
  216.                 'fileThumb':    ['Область превью картинок вместо кнопки "Файл"', 'File thumbnail area instead of "File" button'],
  217.                 'addSageBtn':   ['Кнопка Sage вместо "E-mail"* ', 'Sage button instead of "E-mail"* '],
  218.                 'saveSage':     ['запоминать сажу', 'remember sage'],
  219.                 'captchaLang': {
  220.                         sel:        [['Откл.', 'Eng', 'Rus'], ['Disable', 'Eng', 'Rus']],
  221.                         txt:        ['язык ввода капчи', 'language input in captcha']
  222.                 },
  223.                 'addTextBtns': {
  224.                         sel:        [['Откл.', 'Графич.', 'Упрощ.', 'Стандарт.'], ['Disable', 'As images', 'As text', 'Standard']],
  225.                         txt:        ['кнопки форматирования текста ', 'text format buttons ']
  226.                 },
  227.                 'txtBtnsLoc':   ['внизу', 'at bottom'],
  228.                 'userPassw':    [' Постоянный пароль ', ' Fixed password '],
  229.                 'userName':     ['Постоянное имя', 'Fixed name'],
  230.                 'noBoardRule':  ['правила', 'rules'],
  231.                 'noGoto':       ['поле goto', 'goto field'],
  232.                 'noPassword':   ['пароль', 'password'],
  233.  
  234.                 'scriptStyle': {
  235.                         sel:        [['Glass black', 'Glass blue', 'Solid grey'], ['Glass black', 'Glass blue', 'Solid grey']],
  236.                         txt:        ['стиль скрипта', 'script style']
  237.                 },
  238.                 'userCSS':      ['Пользовательский CSS ', 'User CSS '],
  239.                 'attachPanel':  ['Прикрепить главную панель', 'Attach main panel'],
  240.                 'panelCounter': ['Счетчик постов/картинок на главной панели', 'Counter of posts/images on main panel'],
  241.                 'rePageTitle':  ['Название треда в заголовке вкладки*', 'Thread title in page tab*'],
  242.                 'animation':    ['CSS3 анимация в скрипте', 'CSS3 animation in script'],
  243.                 'closePopups':  ['Автоматически закрывать уведомления', 'Close popups automatically'],
  244.                 'updScript':    ['Автоматически проверять обновления скрипта', 'Check for script update automatically'],
  245.                 'turnOff':      ['Включать скрипт только на этом сайте', 'Enable script only on this site'],
  246.                 'scrUpdIntrv': {
  247.                         sel:        [
  248.                                 ['Каждый день', 'Каждые 2 дня', 'Каждую неделю', 'Каждые 2 недели', 'Каждый месяц'],
  249.                                 ['Every day', 'Every 2 days', 'Every week', 'Every 2 week', 'Every month']
  250.                         ],
  251.                         txt:        ['', '']
  252.                 },
  253.  
  254.                 'language': {
  255.                         sel:        [['Ru', 'En'], ['Ru', 'En']],
  256.                         txt:        ['', '']
  257.                 }
  258.         },
  259.  
  260.         txtBtn: [
  261.                 ['Жирный', 'Bold'],
  262.                 ['Наклонный', 'Italic'],
  263.                 ['Подчеркнутый', 'Underlined'],
  264.                 ['Зачеркнутый', 'Strike'],
  265.                 ['Спойлер', 'Spoiler'],
  266.                 ['Код', 'Code'],
  267.                 ['Верхний индекс', 'Superscript'],
  268.                 ['Нижний индекс', 'Subscript'],
  269.                 ['Цитировать выделенное', 'Quote selected']
  270.         ],
  271.  
  272.         cfgTab: {
  273.                 'filters':      ['Фильтры', 'Filters'],
  274.                 'posts':        ['Посты', 'Posts'],
  275.                 'images':       ['Картинки', 'Images'],
  276.                 'links':        ['Ссылки', 'Links'],
  277.                 'form':         ['Форма', 'Form'],
  278.                 'common':       ['Общее', 'Common'],
  279.                 'info':         ['Инфо', 'Info']
  280.         },
  281.  
  282.         panelBtn: {
  283.                 'attach':       ['Прикрепить/Открепить', 'Attach/Detach'],
  284.                 'settings':     ['Настройки', 'Settings'],
  285.                 'hidden':       ['Скрытое', 'Hidden'],
  286.                 'favor':        ['Избранное', 'Favorites'],
  287.                 'video':        ['Видео-ссылки', 'Video links'],
  288.                 'refresh':      ['Обновить', 'Refresh'],
  289.                 'goback':       ['Назад', 'Go back'],
  290.                 'gonext':       ['Следующая', 'Next'],
  291.                 'goup':         ['Наверх', 'To the top'],
  292.                 'godown':       ['В конец', 'To the bottom'],
  293.                 'expimg':       ['Раскрыть картинки', 'Expand images'],
  294.                 'preimg':       [
  295.                         'Предзагрузка картинок ([Ctrl+Click] только для новых постов)',
  296.                         'Preload images ([Ctrl+Click] for new posts only)'
  297.                 ],
  298.                 'maskimg':      ['Маскировать картинки', 'Mask images'],
  299.                 'upd-on':       ['Выключить автообновление треда', 'Disable thread autoupdate'],
  300.                 'upd-off':      ['Включить автообновление треда', 'Enable thread autoupdate'],
  301.                 'audio-off':    ['Звуковое оповещение о новых постах', 'Sound notification about new posts'],
  302.                 'catalog':      ['Каталог', 'Catalog'],
  303.                 'counter':      ['Постов/картинок в треде', 'Posts/Images in thread'],
  304.                 'savethr':      ['Сохранить на диск', 'Save to disk'],
  305.                 'enable':       ['Включить/выключить скрипт', 'Turn on/off the script']
  306.         },
  307.  
  308.         selHiderMenu: {
  309.                 'sel':          ['Скрывать выделенное', 'Hide selected text'],
  310.                 'name':         ['Скрывать имя', 'Hide name'],
  311.                 'trip':         ['Скрывать трип-код', 'Hide with trip-code'],
  312.                 'img':          ['Скрывать картинку', 'Hide with image'],
  313.                 'ihash':        ['Скрывать схожие картинки', 'Hide similar images'],
  314.                 'text':         ['Скрыть схожий текст', 'Hide similar text'],
  315.                 'noimg':        ['Скрывать без картинок', 'Hide without images'],
  316.                 'notext':       ['Скрывать без текста', 'Hide without text']
  317.         },
  318.         selExpandThr: [
  319.                 ['5 постов', '15 постов', '30 постов', '50 постов', '100 постов'],
  320.                 ['5 posts', '15 posts', '30 posts', '50 posts', '100 posts']
  321.         ],
  322.         selAjaxPages: [
  323.                 ['1 страница', '2 страницы', '3 страницы', '4 страницы', '5 страниц'],
  324.                 ['1 page', '2 pages', '3 pages', '4 pages', '5 pages']
  325.         ],
  326.         selSaveThr: [
  327.                 ['Скачать весь тред', 'Скачать картинки'],
  328.                 ['Download thread', 'Download images']
  329.         ],
  330.         selAudioNotif: [
  331.                 ['Каждые 30 сек.', 'Каждую минуту', 'Каждые 2 мин.', 'Каждые 5 мин.'],
  332.                 ['Every 30 sec.', 'Every minute', 'Every 2 min.', 'Every 5 min.']
  333.         ],
  334.  
  335.         hotKeyEdit: [[
  336.                 '%l%i24 – предыдущая страница/картинка%/l',
  337.                 '%l%i217 – следующая страница/картинка%/l',
  338.                 '%l%i23 – скрыть текущий пост/тред%/l',
  339.                 '%l%i33 – раскрыть текущий тред%/l',
  340.                 '%l%i22 – быстрый ответ%/l',
  341.                 '%l%i25t – отправить пост%/l',
  342.                 '%l%i21 – тред (на доске)/пост (в треде) ниже%/l',
  343.                 '%l%i20 – тред (на доске)/пост (в треде) выше%/l',
  344.                 '%l%i31 – пост (на доске) ниже%/l',
  345.                 '%l%i30 – пост (на доске) выше%/l',
  346.                 '%l%i32 – открыть тред%/l',
  347.                 '%l%i210 – открыть/закрыть настройки%/l',
  348.                 '%l%i26 – открыть/закрыть избранное%/l',
  349.                 '%l%i27 – открыть/закрыть скрытые посты%/l',
  350.                 '%l%i28 – открыть/закрыть панель%/l',
  351.                 '%l%i29 – включить/выключить маскировку картинок%/l',
  352.                 '%l%i40 – обновить тред%/l',
  353.                 '%l%i211 – раскрыть картинку в текущем посте%/l',
  354.                 '%l%i212t – жирный%/l',
  355.                 '%l%i213t – курсив%/l',
  356.                 '%l%i214t – зачеркнутый%/l',
  357.                 '%l%i215t – спойлер%/l',
  358.                 '%l%i216t – код%/l'], [
  359.                 '%l%i24 – previous page/image%/l',
  360.                 '%l%i217 – next page/image%/l',
  361.                 '%l%i23 – hide current post/thread%/l',
  362.                 '%l%i33 – expand current thread%/l',
  363.                 '%l%i22 – quick reply%/l',
  364.                 '%l%i25t – send post%/l',
  365.                 '%l%i21 – thread (on board)/post (in thread) below%/l',
  366.                 '%l%i20 – thread (on board)/post (in thread) above%/l',
  367.                 '%l%i31 – on board post below%/l',
  368.                 '%l%i30 – on board post above%/l',
  369.                 '%l%i32 – open thread%/l',
  370.                 '%l%i210 – open/close Settings%/l',
  371.                 '%l%i26 – open/close Favorites%/l',
  372.                 '%l%i27 – open/close Hidden Posts Table%/l',
  373.                 '%l%i28 – open/close the main panel%/l',
  374.                 '%l%i29 – turn on/off masking images%/l',
  375.                 '%l%i40 – update thread%/l',
  376.                 '%l%i211 – expand current post\'s images%/l',
  377.                 '%l%i212t – bold%/l',
  378.                 '%l%i213t – italic%/l',
  379.                 '%l%i214t – strike%/l',
  380.                 '%l%i215t – spoiler%/l',
  381.                 '%l%i216t – code%/l'
  382.         ]],
  383.  
  384.         month: [
  385.                 ['янв', 'фев', 'мар', 'апр', 'мая', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'],
  386.                 ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  387.         ],
  388.         fullMonth: [
  389.                 ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'],
  390.                 ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
  391.         ],
  392.         week: [
  393.                 ['Вск', 'Пнд', 'Втр', 'Срд', 'Чтв', 'Птн', 'Сбт'],
  394.                 ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
  395.         ],
  396.  
  397.         editor: {
  398.                 cfg:        ['Редактирование настроек:', 'Edit settings:'],
  399.                 hidden:     ['Редактирование скрытых тредов:', 'Edit hidden threads:'],
  400.                 favor:      ['Редактирование избранного:', 'Edit favorites:'],
  401.                 css:        ['Редактирование CSS', 'Edit CSS']
  402.         },
  403.  
  404.         newPost: [
  405.                 [' новый пост', ' новых поста', ' новых постов', '. Последний:'],
  406.                 [' new post', ' new posts', ' new posts', '. Latest: ']
  407.         ],
  408.  
  409.         add:            ['Добавить', 'Add'],
  410.         apply:          ['Применить', 'Apply'],
  411.         clear:          ['Очистить', 'Clear'],
  412.         refresh:        ['Обновить', 'Refresh'],
  413.         load:           ['Загрузить', 'Load'],
  414.         save:           ['Сохранить', 'Save'],
  415.         edit:           ['Правка', 'Edit'],
  416.         reset:          ['Сброс', 'Reset'],
  417.         remove:         ['Удалить', 'Remove'],
  418.         info:           ['Инфо', 'Info'],
  419.         undo:           ['Отмена', 'Undo'],
  420.         change:         ['Сменить', 'Change'],
  421.         reply:          ['Ответ', 'Reply'],
  422.         loading:        ['Загрузка...', 'Loading...'],
  423.         checking:       ['Проверка...', 'Checking...'],
  424.         deleting:       ['Удаление...', 'Deleting...'],
  425.         error:          ['Ошибка', 'Error'],
  426.         noConnect:      ['Ошибка подключения', 'Connection failed'],
  427.         thrNotFound:    ['Тред недоступен (№', 'Thread is unavailable (№'],
  428.         succDeleted:    ['Успешно удалено!', 'Succesfully deleted!'],
  429.         errDelete:      ['Не могу удалить:\n', 'Can\'t delete:\n'],
  430.         cTimeError:     ['Неправильные настройки времени', 'Invalid time settings'],
  431.         noGlobalCfg:    ['Глобальные настройки не найдены', 'Global config not found'],
  432.         postNotFound:   ['Пост не найден', 'Post not found'],
  433.         dontShow:       ['Скрыть: ', 'Hide: '],
  434.         checkNow:       ['Проверить сейчас', 'Check now'],
  435.         updAvail:       ['Доступно обновление!', 'Update available!'],
  436.         haveLatest:     ['У вас стоит самая последняя версия!', 'You have latest version!'],
  437.         storage:        ['Хранение: ', 'Storage: '],
  438.         thrViewed:      ['Тредов просмотрено: ', 'Threads viewed: '],
  439.         thrCreated:     ['Тредов создано: ', 'Threads created: '],
  440.         thrHidden:      ['Тредов скрыто: ', 'Threads hidden: '],
  441.         postsSent:      ['Постов отправлено: ', 'Posts sent: '],
  442.         total:          ['Всего: ', 'Total: '],
  443.         debug:          ['Отладка', 'Debug'],
  444.         infoDebug:      ['Информация для отладки', 'Information for debugging'],
  445.         loadGlobal:     ['Загрузить глобальные настройки', 'Load global settings'],
  446.         saveGlobal:     ['Сохранить настройки как глобальные', 'Save settings as global'],
  447.         editInTxt:      ['Правка в текстовом формате', 'Edit in text format'],
  448.         resetCfg:       ['Сбросить в настройки по умолчанию', 'Reset settings to defaults'],
  449.         conReset: [
  450.                 'Данное действие удалит все ваши настройки и закладки. Продолжить?',
  451.                 'This will delete all your preferences and favourites. Continue?'
  452.         ],
  453.         clrSelected:    ['Удалить выделенные записи', 'Remove selected notes'],
  454.         saveChanges:    ['Сохранить внесенные изменения', 'Save your changes'],
  455.         infoCount:      ['Обновить счетчики постов', 'Refresh posts counters'],
  456.         infoPage:       ['Проверить актуальность тредов (до 5 страницы)', 'Check for threads actuality (up to 5 page)'],
  457.         clrDeleted:     ['Очистить недоступные (404) треды', 'Clear inaccessible (404) threads'],
  458.         oldPosts:       ['Постов при последнем посещении', 'Posts at the last visit'],
  459.         newPosts:       ['Количество новых постов', 'Number of new posts'],
  460.         thrPage:        ['Тред на @странице', 'Thread on @page'],
  461.         findThrd:       ['Найти/Загрузить тред', 'Find/Load thread'],
  462.         hiddenPosts:    ['Скрытые посты на странице', 'Hidden posts on the page'],
  463.         hiddenThrds:    ['Скрытые треды', 'Hidden threads'],
  464.         noHidPosts:     ['На этой странице нет скрытых постов...', 'No hidden posts on this page...'],
  465.         noHidThrds:     ['Нет скрытых тредов...', 'No hidden threads...'],
  466.         expandAll:      ['Раскрыть все', 'Expand all'],
  467.         invalidData:    ['Некорректный формат данных', 'Incorrect data format'],
  468.         favThrds:       ['Избранные треды:', 'Favorite threads:'],
  469.         noFavThrds:     ['Нет избранных тредов...', 'Favorites is empty...'],
  470.         noVideoLinks:   ['Нет ссылок на видео...', 'No video links...'],
  471.         hideLnkList:    ['Скрыть/Показать список ссылок', 'Hide/Unhide list of links'],
  472.         prevVideo:      ['Предыдущее видео', 'Previous video'],
  473.         nextVideo:      ['Следующее видео', 'Next video'],
  474.         toggleQReply:   ['Поместить под пост / Открепить', 'Move under post / Unattach'],
  475.         closeQReply:    ['Закрыть форму', 'Close form'],
  476.         replies:        ['Ответы:', 'Replies:'],
  477.         postsOmitted:   ['Пропущено ответов: ', 'Posts omitted: '],
  478.         collapseThrd:   ['Свернуть тред', 'Collapse thread'],
  479.         deleted:        ['удалён', 'deleted'],
  480.         getNewPosts:    ['Получить новые посты', 'Get new posts'],
  481.         page:           ['Страница', 'Page'],
  482.         hiddenThrd:     ['Скрытый тред:', 'Hidden thread:'],
  483.         makeThrd:       ['Создать тред', 'Create thread'],
  484.         makeReply:      ['Ответить', 'Make reply'],
  485.         hideForm:       ['Скрыть форму', 'Hide form'],
  486.         search:         ['Искать в ', 'Search in '],
  487.         wait:           ['Ждите', 'Wait'],
  488.         noFile:         ['Нет файла', 'No file'],
  489.         clickToAdd:     ['Выберите, либо перетащите файл', 'Select or drag and drop file'],
  490.         removeFile:     ['Удалить файл', 'Remove file'],
  491.         helpAddFile:    ['Встроить .ogg, .rar, .zip или .7z в картинку', 'Pack .ogg, .rar, .zip or .7z into image'],
  492.         downloadFile:   ['Скачать содержащийся в картинке файл', 'Download existing file from image'],
  493.         fileCorrupt:    ['Файл повреждён: ', 'File is corrupted: '],
  494.         subjHasTrip:    ['Поле "Тема" содержит трипкод', '"Subject" field contains a tripcode'],
  495.         loadImage:      ['Загружаются картинки: ', 'Loading images: '],
  496.         loadFile:       ['Загружаются файлы: ', 'Loading files: '],
  497.         cantLoad:       ['Не могу загрузить ', 'Can\'t load '],
  498.         willSavePview:  ['Будет сохранено превью', 'Thumbnail will be saved'],
  499.         loadErrors:     ['Во время загрузки произошли ошибки:', 'An error occurred during the loading:'],
  500.         errCorruptData: ['Ошибка: сервер отправил повреждённые данные', 'Error: server sent corrupted data'],
  501.         expImgInline:   ['[Click] открыть в посте, [Ctrl+Click] в центре', '[Click] expand in post, [Ctrl+Click] by center'],
  502.         expImgFull:     ['[Click] открыть в центре, [Ctrl+Click] в посте', '[Click] expand by center, [Ctrl+Click] in post'],
  503.         nextImg:        ['Следующая картинка', 'Next image'],
  504.         prevImg:        ['Предыдущая картинка', 'Previous image'],
  505.         togglePost:     ['Скрыть/Раскрыть пост', 'Hide/Unhide post'],
  506.         replyToPost:    ['Ответить на пост', 'Reply to post'],
  507.         expandThrd:     ['Раскрыть весь тред', 'Expand all thread'],
  508.         addFav:         ['Добавить тред в Избранное', 'Add thread to Favorites'],
  509.         delFav:         ['Убрать тред из Избранного', 'Remove thread from Favorites'],
  510.         attachPview:    ['Закрепить превью', 'Attach preview'],
  511.         author:         ['автор: ', 'author: '],
  512.         views:          ['просмотров: ', 'views: '],
  513.         published:      ['опубликовано: ', 'published: '],
  514.  
  515.         seSyntaxErr:    ['синтаксическая ошибка в аргументе спелла: %s', 'syntax error in argument of spell: %s'],
  516.         seUnknown:      ['неизвестный спелл: %s', 'unknown spell: %s'],
  517.         seMissOp:       ['пропущен оператор', 'missing operator'],
  518.         seMissArg:      ['пропущен аргумент спелла: %s', 'missing argument of spell: %s'],
  519.         seMissSpell:    ['пропущен спелл', 'missing spell'],
  520.         seErrRegex:     ['синтаксическая ошибка в регулярном выражении: %s', 'syntax error in regular expression: %s'],
  521.         seUnexpChar:    ['неожиданный символ: %s', 'unexpected character: %s'],
  522.         seMissClBkt:    ['пропущена закрывающаяся скобка', 'missing ) in parenthetical'],
  523.         seRepsInParens: ['спелл $s не должен располагаться в скобках', 'spell %s shouldn\'t be in parens'],
  524.         seOpInReps:     [
  525.                 'недопустимо использовать оператор %s со спеллами #rep и #outrep',
  526.                 'don\'t use operator %s with spells #rep & #outrep'
  527.         ],
  528.         seRow:          [' (строка ', ' (row '],
  529.         seCol:          [', столбец ', ', column ']
  530. },
  531.  
  532. doc = window.document, aProto = Array.prototype, locStorage, sesStorage,
  533. Cfg, comCfg, hThr, pByNum, sVis, bUVis, needScroll,
  534. aib, nav, brd, TNum, pageNum, updater, hKeys, firstThr, lastThr, visPosts = 2, dTime,
  535. YouTube, WebmParser, Logger,
  536. pr, dForm, dummy, spells,
  537. Images_ = {preloading: false, afterpreload: null, progressId: null, canvas: null},
  538. ajaxInterval, lang, quotetxt = '', liteMode, localRun, isExpImg, isPreImg, chromeCssUpd,
  539. $each = Function.prototype.call.bind(aProto.forEach),
  540. emptyFn = function () {};
  541.  
  542.  
  543. // UTILS
  544. // ===========================================================================================================
  545.  
  546. function $Q(path, root) {
  547.         return root.querySelectorAll(path);
  548. }
  549.  
  550. function $q(path, root) {
  551.         return root.querySelector(path);
  552. }
  553.  
  554. function $C(id, root) {
  555.         return root.getElementsByClassName(id);
  556. }
  557.  
  558. function $c(id, root) {
  559.         return root.getElementsByClassName(id)[0];
  560. }
  561.  
  562. function $id(id) {
  563.         return doc.getElementById(id);
  564. }
  565.  
  566. function $T(id, root) {
  567.         return root.getElementsByTagName(id);
  568. }
  569.  
  570. function $t(id, root) {
  571.         return root.getElementsByTagName(id)[0];
  572. }
  573.  
  574. function $parent(el, tagName) {
  575.         do {
  576.                 el = el.parentElement;
  577.         } while (el && el.tagName !== tagName);
  578.         return el;
  579. }
  580.  
  581. function $before(el, node) {
  582.         el.parentNode.insertBefore(node, el);
  583. }
  584.  
  585. function $after(el, node) {
  586.         el.parentNode.insertBefore(node, el.nextSibling);
  587. }
  588.  
  589. function $add(html) {
  590.         dummy.innerHTML = html;
  591.         return dummy.firstChild;
  592. }
  593.  
  594. function $new(tag, attr, events) {
  595.         var key, el = doc.createElement(tag);
  596.         if (attr) {
  597.                 for (key in attr) {
  598.                         if (key === 'text') {
  599.                                 el.textContent = attr[key];
  600.                         } else if (key === 'value') {
  601.                                 el.value = attr[key];
  602.                         } else if (attr.hasOwnProperty(key)) {
  603.                                 el.setAttribute(key, attr[key]);
  604.                         }
  605.                 }
  606.         }
  607.         if (events) {
  608.                 for (key in events) {
  609.                         if (events.hasOwnProperty(key)) {
  610.                                 el.addEventListener(key, events[key], false);
  611.                         }
  612.                 }
  613.         }
  614.         return el;
  615. }
  616.  
  617. function $New(tag, attr, nodes) {
  618.         var el = $new(tag, attr, null);
  619.         for (var i = 0, len = nodes.length; i < len; i++) {
  620.                 if (nodes[i]) {
  621.                         el.appendChild(nodes[i]);
  622.                 }
  623.         }
  624.         return el;
  625. }
  626.  
  627. function $txt(el) {
  628.         return doc.createTextNode(el);
  629. }
  630.  
  631. function $btn(val, ttl, Fn) {
  632.         return $new('input', {'type': 'button', 'value': val, 'title': ttl}, {'click': Fn});
  633. }
  634.  
  635. function $script(text) {
  636.         $del(doc.head.appendChild($new('script', {'type': 'text/javascript', 'text': text}, null)));
  637. }
  638.  
  639. function $css(text) {
  640.         return doc.head.appendChild($new('style', {'type': 'text/css', 'text': text}, null));
  641. }
  642.  
  643. function $if(cond, el) {
  644.         return cond ? el : null;
  645. }
  646.  
  647. function $disp(el) {
  648.         el.style.display = el.style.display === 'none' ? '' : 'none';
  649. }
  650.  
  651. function $del(el) {
  652.         if (el) {
  653.                 el.parentNode.removeChild(el);
  654.         }
  655. }
  656.  
  657. function $DOM(html) {
  658.         var myDoc = doc.implementation.createHTMLDocument('');
  659.         myDoc.documentElement.innerHTML = html;
  660.         return myDoc;
  661. }
  662.  
  663. function $pd(e) {
  664.         e.preventDefault();
  665. }
  666.  
  667. function $txtInsert(el, txt) {
  668.         var scrtop = el.scrollTop,
  669.                 start = el.selectionStart;
  670.         el.value = el.value.substr(0, start) + txt + el.value.substr(el.selectionEnd);
  671.         el.setSelectionRange(start + txt.length, start + txt.length);
  672.         el.focus();
  673.         el.scrollTop = scrtop;
  674. }
  675.  
  676. function $txtSelect() {
  677.         return (nav.Presto ? doc.getSelection() : window.getSelection()).toString();
  678. }
  679.  
  680. function $isEmpty(obj) {
  681.         for (var i in obj) {
  682.                 if (obj.hasOwnProperty(i)) {
  683.                         return false;
  684.                 }
  685.         }
  686.         return true;
  687. }
  688.  
  689. Logger = new function () {
  690.         var instance, oldTime, initTime, timeLog;
  691.         function LoggerSingleton() {
  692.                 if (instance) {
  693.                         return instance;
  694.                 }
  695.                 instance = this;
  696.         }
  697.         LoggerSingleton.prototype = {
  698.                 finish: function () {
  699.                         timeLog.push(Lng.total[lang] + (Date.now() - initTime) + 'ms');
  700.                 },
  701.                 get: function () {
  702.                         return timeLog;
  703.                 },
  704.                 init: function () {
  705.                         oldTime = initTime = Date.now();
  706.                         timeLog = [];
  707.                 },
  708.                 log: function realLog(text) {
  709.                         var newTime = Date.now(),
  710.                                 time = newTime - oldTime;
  711.                         if (time > 1) {
  712.                                 timeLog.push(text + ': ' + time + 'ms');
  713.                                 oldTime = newTime;
  714.                         }
  715.                 }
  716.         };
  717.         return LoggerSingleton;
  718. };
  719.  
  720. function $xhr(obj) {
  721.         var h, xhr = new XMLHttpRequest();
  722.         if (obj.onreadystatechange) {
  723.                 xhr.onreadystatechange = obj.onreadystatechange.bind(window, xhr);
  724.         }
  725.         if (obj.onload) {
  726.                 xhr.onload = obj.onload.bind(window, xhr);
  727.         }
  728.         xhr.open(obj.method, obj.url, true);
  729.         if (obj.responseType) {
  730.                 xhr.responseType = obj.responseType;
  731.         }
  732.         for (h in obj.headers) {
  733.                 xhr.setRequestHeader(h, obj.headers[h]);
  734.         }
  735.         xhr.send(obj.data || null);
  736.         return xhr;
  737. }
  738.  
  739. function $queue(maxNum, Fn, endFn) {
  740.         this.array = [];
  741.         this.length = this.index = this.running = 0;
  742.         this.num = 1;
  743.         this.fn = Fn;
  744.         this.endFn = endFn;
  745.         this.max = maxNum;
  746.         this.freeSlots = [];
  747.         while (maxNum--) {
  748.                 this.freeSlots.push(maxNum);
  749.         }
  750.         this.completed = this.paused = false;
  751. }
  752. $queue.prototype = {
  753.         run: function (data) {
  754.                 if (this.paused || this.running === this.max) {
  755.                         this.array.push(data);
  756.                         this.length++;
  757.                 } else {
  758.                         this.fn(this.freeSlots.pop(), this.num++, data);
  759.                         this.running++;
  760.                 }
  761.         },
  762.         end: function (qIdx) {
  763.                 if (!this.paused && this.index < this.length) {
  764.                         this.fn(qIdx, this.num++, this.array[this.index++]);
  765.                         return;
  766.                 }
  767.                 this.running--;
  768.                 this.freeSlots.push(qIdx);
  769.                 if (!this.paused && this.completed && this.running === 0) {
  770.                         this.endFn();
  771.                 }
  772.         },
  773.         complete: function () {
  774.                 if (this.index >= this.length && this.running === 0) {
  775.                         this.endFn();
  776.                 } else {
  777.                         this.completed = true;
  778.                 }
  779.         },
  780.         pause: function () {
  781.                 this.paused = true;
  782.         },
  783.         'continue': function () {
  784.                 this.paused = false;
  785.                 if (this.index >= this.length) {
  786.                         if (this.completed) {
  787.                                 this.endFn();
  788.                         }
  789.                         return;
  790.                 }
  791.                 while (this.index < this.length && this.running !== this.max) {
  792.                         this.fn(this.freeSlots.pop(), this.num++, this.array[this.index++]);
  793.                         this.running++;
  794.                 }
  795.         }
  796. };
  797.  
  798. function $tar() {
  799.         this._data = [];
  800. }
  801. $tar.prototype = {
  802.         addFile: function (filepath, input) {
  803.                 var i, checksum, nameLen, fileSize = input.length,
  804.                         header = new Uint8Array(512);
  805.                 for (i = 0, nameLen = Math.min(filepath.length, 100); i < nameLen; ++i) {
  806.                         header[i] = filepath.charCodeAt(i) & 0xFF;
  807.                 }
  808.                 // fileMode
  809.                 this._padSet(header, 100, '100777', 8);
  810.                 // uid
  811.                 this._padSet(header, 108, '0', 8);
  812.                 // gid
  813.                 this._padSet(header, 116, '0', 8);
  814.                 // fileSize
  815.                 this._padSet(header, 124, fileSize.toString(8), 13);
  816.                 // mtime
  817.                 this._padSet(header, 136, Math.floor(Date.now() / 1000).toString(8), 12);
  818.                 // checksum
  819.                 this._padSet(header, 148, '        ', 8);
  820.                 // type ('0')
  821.                 header[156] = 0x30;
  822.                 for (i = checksum = 0; i < 157; i++) {
  823.                         checksum += header[i];
  824.                 }
  825.                 // checksum
  826.                 this._padSet(header, 148, checksum.toString(8), 8);
  827.                 this._data.push(header);
  828.                 this._data.push(input);
  829.                 if ((i = Math.ceil(fileSize / 512) * 512 - fileSize) !== 0) {
  830.                         this._data.push(new Uint8Array(i));
  831.                 }
  832.         },
  833.         addString: function (filepath, str) {
  834.                 var i, len, data, sDat = unescape(encodeURIComponent(str));
  835.                 for (i = 0, len = sDat.length, data = new Uint8Array(len); i < len; ++i) {
  836.                         data[i] = sDat.charCodeAt(i) & 0xFF;
  837.                 }
  838.                 this.addFile(filepath, data);
  839.         },
  840.         get: function () {
  841.                 this._data.push(new Uint8Array(1024));
  842.                 return new Blob(this._data, {'type': 'application/x-tar'});
  843.         },
  844.  
  845.         _padSet: function (data, offset, num, len) {
  846.                 var i = 0, nLen = num.length;
  847.                 len -= 2;
  848.                 while (nLen < len) {
  849.                         data[offset++] = 0x20; // ' '
  850.                         len--;
  851.                 }
  852.                 while (i < nLen) {
  853.                         data[offset++] = num.charCodeAt(i++);
  854.                 }
  855.                 data[offset] = 0x20; // ' '
  856.         }
  857. };
  858.  
  859. function $workers(source, count) {
  860.         var i, wrk, wUrl;
  861.         if (nav.Firefox) {
  862.                 wUrl = 'data:text/javascript,' + source;
  863.                 wrk = unsafeWindow.Worker;
  864.         } else {
  865.                 wUrl = window.URL.createObjectURL(new Blob([source], {'type': 'text/javascript'}));
  866.                 this.url = wUrl;
  867.                 wrk = Worker;
  868.         }
  869.         for (i = 0; i < count; ++i) {
  870.                 this[i] = new wrk(wUrl);
  871.         }
  872. }
  873. $workers.prototype = {
  874.         url: null,
  875.         clear: function () {
  876.                 if (this.url !== null) {
  877.                         window.URL.revokeObjectURL(this.url);
  878.                 }
  879.         }
  880. };
  881.  
  882. function regQuote(str) {
  883.         return (str + '').replace(/([.?*+^$[\]\\(){}|\-])/g, '\\$1');
  884. }
  885.  
  886. function fixBrd(b) {
  887.         return '/' + b + (b ? '/' : '');
  888. }
  889.  
  890. function getAbsLink(url) {
  891.         return url[1] === '/' ? aib.prot + url :
  892.                 url[0] === '/' ? aib.prot + '//' + aib.host + url : url;
  893. }
  894.  
  895. function getErrorMessage(eCode, eMsg) {
  896.         return eCode === 0 ? eMsg || Lng.noConnect[lang] : 'HTTP [' + eCode + '] ' + eMsg;
  897. }
  898.  
  899. function getPrettyErrorMessage(e) {
  900.         return e.stack ? (nav.WebKit ? e.stack :
  901.                         e.name + ': ' + e.message + '\n' +
  902.                         (nav.Firefox ? e.stack.replace(/^([^@]*).*\/(.+)$/gm, function (str, fName, line) {
  903.                                 return '    at ' + (fName ? fName + ' (' + line + ')' : line);
  904.                         }) : e.stack)
  905.                 ) : e.name + ': ' + e.message;
  906. }
  907.  
  908. function toRegExp(str, noG) {
  909.         var l = str.lastIndexOf('/'),
  910.                 flags = str.substr(l + 1);
  911.         return new RegExp(str.substr(1, l - 1), noG ? flags.replace('g', '') : flags);
  912. }
  913.  
  914. function setImageSize(size, idx, nVal) {
  915.         size[idx] = nVal;
  916.         if (idx === 0) {
  917.                 size[1] = nVal / size[2];
  918.         } else {
  919.                 size[0] = nVal * size[2];
  920.         }
  921. }
  922.  
  923. function resizeImage(size, minSize, maxSize) {
  924.         var idx = size[2] > 1 ? 1 : 0;
  925.         if (+size[idx] < +minSize) {
  926.                 setImageSize(size, idx, minSize);
  927.         }
  928.         if (maxSize) {
  929.                 idx = size[2] > maxSize[2] ? 0 : 1;
  930.                 if (+size[idx] > +maxSize[idx]) {
  931.                         setImageSize(size, idx, +maxSize[idx]);
  932.                 }
  933.         }
  934.         return size;
  935. }
  936.  
  937.  
  938. // STORAGE
  939. // ===========================================================================================================
  940.  
  941. function getStored(id, Fn) {
  942.         if (nav.isGM) {
  943.                 Fn(GM_getValue(id));
  944.         } else if (nav.isChromeStorage) {
  945.                 chrome.storage.local.get(id, function (obj) {
  946.                         if (Object.keys(obj).length) {
  947.                                 // console.log('Read local ' + id);
  948.                                 Fn(obj[id]);
  949.                         } else {
  950.                                 chrome.storage.sync.get(id, function (obj) {
  951.                                         // console.log('Read sync ' + id);
  952.                                         Fn(obj[id]);
  953.                                 });
  954.                         }
  955.                 });
  956.         } else if (nav.isScriptStorage) {
  957.                 Fn(scriptStorage.getItem(id));
  958.         } else {
  959.                 Fn(locStorage.getItem(id));
  960.         }
  961. }
  962.  
  963. function setStored(id, value) {
  964.         if (nav.isGM) {
  965.                 GM_setValue(id, value);
  966.         } else if (nav.isChromeStorage) {
  967.                 var obj = {};
  968.                 obj[id] = value;
  969.                 if (value.toString().length < 4095) {
  970.                         chrome.storage.sync.set(obj, emptyFn);
  971.                         chrome.storage.local.remove(id, emptyFn);
  972.                 } else {
  973.                         chrome.storage.local.set(obj, emptyFn);
  974.                         chrome.storage.sync.remove(id, emptyFn);
  975.                 }
  976.         } else if (nav.isScriptStorage) {
  977.                 scriptStorage.setItem(id, value);
  978.         } else {
  979.                 locStorage.setItem(id, value);
  980.         }
  981. }
  982.  
  983. function delStored(id) {
  984.         if (nav.isGM) {
  985.                 GM_deleteValue(id);
  986.         } else if (nav.isChromeStorage) {
  987.                 chrome.storage.sync.remove(id, emptyFn);
  988.         } else if (nav.isScriptStorage) {
  989.                 scriptStorage.removeItem(id);
  990.         } else {
  991.                 locStorage.removeItem(id);
  992.         }
  993. }
  994.  
  995. function getStoredObj(id, Fn) {
  996.         getStored(id, function (Fn, val) {
  997.                 var data;
  998.                 try {
  999.                         data = JSON.parse(val || '{}');
  1000.                 } finally {
  1001.                         Fn(data || {});
  1002.                 }
  1003.         }.bind(null, Fn));
  1004. }
  1005.  
  1006. function saveComCfg(dm, obj) {
  1007.         getStoredObj('DESU_Config', function (dm, obj, val) {
  1008.                 comCfg = val;
  1009.                 if (obj) {
  1010.                         comCfg[dm] = obj;
  1011.                 } else {
  1012.                         delete comCfg[dm];
  1013.                 }
  1014.                 setStored('DESU_Config', JSON.stringify(comCfg) || '');
  1015.         }.bind(null, dm, obj));
  1016. }
  1017.  
  1018. function saveCfg(id, val) {
  1019.         if (Cfg[id] !== val) {
  1020.                 Cfg[id] = val;
  1021.                 saveComCfg(aib.dm, Cfg);
  1022.         }
  1023. }
  1024.  
  1025. function Config(obj) {
  1026.         for (var i in obj) {
  1027.                 this[i] = obj[i];
  1028.         }
  1029. }
  1030. Config.prototype = defaultCfg;
  1031.  
  1032. function readCfg(Fn) {
  1033.         getStoredObj('DESU_Config', function (Fn, val) {
  1034.                 var obj;
  1035.                 comCfg = val;
  1036.                 if (!(aib.dm in comCfg) || $isEmpty(obj = comCfg[aib.dm])) {
  1037.                         if (nav.isChromeStorage && (obj = locStorage.getItem('DESU_Config'))) {
  1038.                                 obj = JSON.parse(obj)[aib.dm];
  1039.                                 locStorage.removeItem('DESU_Config');
  1040.                         } else {
  1041.                                 obj = nav.isGlobal ? comCfg.global || {} : {};
  1042.                         }
  1043.                         obj.captchaLang = aib.ru ? 2 : 1;
  1044.                         obj.correctTime = 0;
  1045.                 }
  1046.                 Cfg = new Config(obj);
  1047.                 if (!Cfg.timeOffset) {
  1048.                         Cfg.timeOffset = '+0';
  1049.                 }
  1050.                 if (!Cfg.timePattern) {
  1051.                         Cfg.timePattern = aib.timePattern;
  1052.                 }
  1053.                 if ((nav.Opera11 || aib.fch || aib.tiny) && Cfg.ajaxReply === 2) {
  1054.                         Cfg.ajaxReply = 1;
  1055.                 }
  1056.                 if (aib.tiny) {
  1057.                         Cfg.fileThumb = 0;
  1058.                 }
  1059.                 if (!('Notification' in window)) {
  1060.                         Cfg.desktNotif = 0;
  1061.                 }
  1062.                 if (nav.Presto) {
  1063.                         if (nav.Opera11) {
  1064.                                 if (!nav.isGM) {
  1065.                                         Cfg.YTubeTitles = 0;
  1066.                                 }
  1067.                                 Cfg.animation = 0;
  1068.                         }
  1069.                         if (Cfg.YTubeType === 2) {
  1070.                                 Cfg.YTubeType = 1;
  1071.                         }
  1072.                         Cfg.preLoadImgs = 0;
  1073.                         Cfg.findImgFile = 0;
  1074.                         if (!nav.isGM) {
  1075.                                 Cfg.updScript = 0;
  1076.                         }
  1077.                         Cfg.fileThumb = 0;
  1078.                 }
  1079.                 if (nav.isChromeStorage) {
  1080.                         Cfg.updScript = 0;
  1081.                 }
  1082.                 if (Cfg.updThrDelay < 10) {
  1083.                         Cfg.updThrDelay = 10;
  1084.                 }
  1085.                 if (!Cfg.saveSage) {
  1086.                         Cfg.sageReply = 0;
  1087.                 }
  1088.                 if (!Cfg.passwValue) {
  1089.                         Cfg.passwValue = Math.round(Math.random() * 1e15).toString(32);
  1090.                 }
  1091.                 if (!Cfg.stats) {
  1092.                         Cfg.stats = {'view': 0, 'op': 0, 'reply': 0};
  1093.                 }
  1094.                 if (TNum) {
  1095.                         Cfg.stats.view++;
  1096.                 }
  1097.                 if (aib.dobr) {
  1098.                         aib.hDTFix = new dateTime(
  1099.                                 'yyyy-nn-dd-hh-ii-ss',
  1100.                                 '_d _M _Y (_w) _h:_i ',
  1101.                                 Cfg.timeOffset || 0,
  1102.                                 Cfg.correctTime ? lang : 1,
  1103.                                 null
  1104.                         );
  1105.                 }
  1106.                 if (aib.synch) {
  1107.                         Cfg.timePattern = 'w+dd+m+yyyy+hh+ii+ss';
  1108.                         Cfg.timeOffset = 4;
  1109.                         Cfg.correctTime = 1;
  1110.                 }
  1111.                 saveComCfg(aib.dm, Cfg);
  1112.                 lang = Cfg.language;
  1113.                 if (Cfg.correctTime) {
  1114.                         dTime = new dateTime(Cfg.timePattern, Cfg.timeRPattern, Cfg.timeOffset, lang, function (rp) {
  1115.                                 saveCfg('timeRPattern', rp);
  1116.                         });
  1117.                 }
  1118.                 Fn();
  1119.         }.bind(null, Fn));
  1120. }
  1121.  
  1122. function toggleCfg(id) {
  1123.         saveCfg(id, +!Cfg[id]);
  1124. }
  1125.  
  1126. function readPosts() {
  1127.         var data, str = TNum ? sesStorage['de-hidden-' + brd + TNum] : null;
  1128.         if (typeof str === 'string') {
  1129.                 data = str.split(',');
  1130.                 if (data.length === 4 && +data[0] === (Cfg.hideBySpell ? spells.hash : 0) &&
  1131.                         (data[1] in pByNum) && pByNum[data[1]].count === +data[2])
  1132.                 {
  1133.                         sVis = data[3].split('');
  1134.                         return;
  1135.                 }
  1136.         }
  1137.         sVis = [];
  1138. }
  1139.  
  1140. function readUserPosts() {
  1141.         getStoredObj('DESU_Posts_' + aib.dm, function (val) {
  1142.                 bUVis = val;
  1143.                 getStoredObj('DESU_Threads_' + aib.dm, function (val) {
  1144.                         hThr = val;
  1145.                         if (nav.isChromeStorage && (val = locStorage.getItem('DESU_Posts_' + aib.dm))) {
  1146.                                 bUVis = JSON.parse(val);
  1147.                                 val = locStorage.getItem('DESU_Threads_' + aib.dm);
  1148.                                 hThr = JSON.parse(val);
  1149.                                 locStorage.removeItem('DESU_Posts_' + aib.dm);
  1150.                                 locStorage.removeItem('DESU_Threads_' + aib.dm);
  1151.                         }
  1152.                         var uVis, vis, num, post, date = Date.now(),
  1153.                                 update = false;
  1154.                         if (brd in bUVis) {
  1155.                                 uVis = bUVis[brd];
  1156.                         } else {
  1157.                                 uVis = bUVis[brd] = {};
  1158.                         }
  1159.                         if (!(brd in hThr)) {
  1160.                                 hThr[brd] = {};
  1161.                         }
  1162.                         if (!firstThr) {
  1163.                                 return;
  1164.                         }
  1165.                         for (post = firstThr.op; post; post = post.next) {
  1166.                                 num = post.num;
  1167.                                 if (num in uVis) {
  1168.                                         if (post.isOp) {
  1169.                                                 uVis[num][0] = +!(num in hThr[brd]);
  1170.                                         }
  1171.                                         if (uVis[num][0] === 0) {
  1172.                                                 post.setUserVisib(true, date, false);
  1173.                                         } else {
  1174.                                                 uVis[num][1] = date;
  1175.                                                 post.btns.firstChild.className = 'de-btn-hide-user';
  1176.                                                 post.userToggled = true;
  1177.                                         }
  1178.                                 } else {
  1179.                                         vis = sVis[post.count];
  1180.                                         if (post.isOp) {
  1181.                                                 if (num in hThr[brd]) {
  1182.                                                         vis = '0';
  1183.                                                 } else if (vis === '0') {
  1184.                                                         vis = null;
  1185.                                                 }
  1186.                                         }
  1187.                                         if (vis === '0') {
  1188.                                                 if (!post.hidden) {
  1189.                                                         post.setVisib(true);
  1190.                                                         post.hideRefs();
  1191.                                                 }
  1192.                                                 post.spellHidden = true;
  1193.                                         } else if (vis !== '1') {
  1194.                                                 spells.check(post);
  1195.                                         }
  1196.                                 }
  1197.                         }
  1198.                         spells.end(savePosts);
  1199.                         if (update) {
  1200.                                 bUVis[brd] = uVis;
  1201.                                 saveUserPosts(false);
  1202.                         }
  1203.                 });
  1204.         });
  1205. }
  1206.  
  1207. function savePosts() {
  1208.         if (TNum) {
  1209.                 var lPost = firstThr.lastNotDeleted;
  1210.                 sesStorage['de-hidden-' + brd + TNum] = (Cfg.hideBySpell ? spells.hash : '0') +
  1211.                         ',' + lPost.num + ',' + lPost.count + ',' + sVis.join('');
  1212.         }
  1213.         saveHiddenThreads(false);
  1214.         toggleContent('hid', true);
  1215. }
  1216.  
  1217. function saveUserPosts(clear) {
  1218.         var minDate, b, vis, key, str = JSON.stringify(bUVis);
  1219.         if (clear && str.length > 1e6) {
  1220.                 minDate = Date.now() - 5 * 24 * 3600 * 1000;
  1221.                 for (b in bUVis) {
  1222.                         if (bUVis.hasOwnProperty(b)) {
  1223.                                 vis = bUVis[b];
  1224.                                 for (key in vis) {
  1225.                                         if (vis.hasOwnProperty(key) && vis[key][1] < minDate) {
  1226.                                                 delete vis[key];
  1227.                                         }
  1228.                                 }
  1229.                         }
  1230.                 }
  1231.                 str = JSON.stringify(bUVis);
  1232.         }
  1233.         setStored('DESU_Posts_' + aib.dm, str);
  1234.         toggleContent('hid', true);
  1235. }
  1236.  
  1237. function saveHiddenThreads(updContent) {
  1238.         setStored('DESU_Threads_' + aib.dm, JSON.stringify(hThr));
  1239.         if (updContent) {
  1240.                 toggleContent('hid', true);
  1241.         }
  1242. }
  1243.  
  1244. function readFavoritesPosts() {
  1245.         getStoredObj('DESU_Favorites', function (fav) {
  1246.                 var thr, temp, num, update = false;
  1247.                 if (nav.isChromeStorage && (temp = locStorage.getItem('DESU_Favorites'))) {
  1248.                         temp = JSON.parse(temp);
  1249.                         locStorage.removeItem('DESU_Favorites');
  1250.                         if ($isEmpty(temp)) {
  1251.                                 return;
  1252.                         }
  1253.                         temp = temp[aib.host];
  1254.                         fav[aib.host] = temp;
  1255.                         temp = temp[brd];
  1256.                 } else {
  1257.                         if (!(aib.host in fav)) {
  1258.                                 return;
  1259.                         }
  1260.                         temp = fav[aib.host];
  1261.                         if (!(brd in temp)) {
  1262.                                 return;
  1263.                         }
  1264.                         temp = temp[brd];
  1265.                 }
  1266.                 for (thr = firstThr; thr; thr = thr.next) {
  1267.                         if ((num = thr.num) in temp) {
  1268.                                 thr.setFavBtn(true);
  1269.                                 if (TNum) {
  1270.                                         temp[num].cnt = thr.pcount;
  1271.                                         temp[num]['new'] = 0;
  1272.                                 } else {
  1273.                                         temp[num]['new'] = thr.pcount - temp[num].cnt;
  1274.                                 }
  1275.                                 update = true;
  1276.                         }
  1277.                 }
  1278.                 if (update) {
  1279.                         saveFavorites(fav);
  1280.                 }
  1281.         });
  1282. }
  1283.  
  1284. function saveFavorites(fav) {
  1285.         setStored('DESU_Favorites', JSON.stringify(fav));
  1286.         toggleContent('fav', true, fav);
  1287. }
  1288.  
  1289. function removeFavoriteEntry(fav, h, b, num, clearPage) {
  1290.         function _isEmpty(f) {
  1291.                 for (var i in f) {
  1292.                         if (i !== 'url' && f.hasOwnProperty(i)) {
  1293.                                 return false;
  1294.                         }
  1295.                 }
  1296.                 return true;
  1297.         }
  1298.         if ((h in fav) && (b in fav[h]) && (num in fav[h][b])) {
  1299.                 delete fav[h][b][num];
  1300.                 if (_isEmpty(fav[h][b])) {
  1301.                         delete fav[h][b];
  1302.                         if ($isEmpty(fav[h])) {
  1303.                                 delete fav[h];
  1304.                         }
  1305.                 }
  1306.         }
  1307.         if (clearPage && h === aib.host && b === brd && (num in pByNum)) {
  1308.                 pByNum[num].thr.setFavBtn(false);
  1309.         }
  1310. }
  1311.  
  1312. function readViewedPosts() {
  1313.         if (Cfg.markViewed) {
  1314.                 var data = sesStorage['de-viewed'];
  1315.                 if (data) {
  1316.                         data.split(',').forEach(function (pNum) {
  1317.                                 var post = pByNum[pNum];
  1318.                                 if (post) {
  1319.                                         post.el.classList.add('de-viewed');
  1320.                                         post.viewed = true;
  1321.                                 }
  1322.                         });
  1323.                 }
  1324.         }
  1325. }
  1326.  
  1327.  
  1328. // PANEL
  1329. // ===========================================================================================================
  1330.  
  1331. function pButton(id, href, hasHotkey) {
  1332.         return '<li><a id="de-btn-' + id + '" class="de-abtn" ' + (hasHotkey ? 'de-' : '') + 'title="' +
  1333.                 Lng.panelBtn[id][lang] +'" href="' + href + '"></a></li>';
  1334. }
  1335.  
  1336. function addPanel() {
  1337.         var panel, evtObject, imgLen = $Q(aib.qThumbImages, dForm).length;
  1338.         (pr && pr.pArea[0] || dForm).insertAdjacentHTML('beforebegin',
  1339.                 '<div id="de1-main" lang="' + getThemeLang() + '">' +
  1340.                         '<div id="de-panel">' +
  1341.                                 '<span id="de-btn-logo" title="' + Lng.panelBtn.attach[lang] + '"></span>' +
  1342.                                 '<ul id="de-panel-btns"' + (Cfg.expandPanel ? '>' : ' style="display: none">') +
  1343.                                 (Cfg.disabled ? pButton('enable', '#', false) :
  1344.                                         pButton('settings', '#', true) +
  1345.                                         pButton('hidden', '#', true) +
  1346.                                         pButton('favor', '#', true) +
  1347.                                         (!Cfg.addYouTube ? '' : pButton('video', '#', false)) +
  1348.                                         (aib.arch || localRun ? '' :
  1349.                                                 pButton('refresh', '#', false) +
  1350.                                                 (!TNum && (pageNum === aib.firstPage) ? '' :
  1351.                                                         pButton('goback', aib.getPageUrl(brd, pageNum - 1), true)) +
  1352.                                                 (TNum || pageNum === aib.lastPage ? '' :
  1353.                                                         pButton('gonext', aib.getPageUrl(brd, pageNum + 1), true))
  1354.                                         ) + pButton('goup', '#', false) +
  1355.                                         pButton('godown', '#', false) +
  1356.                                         (imgLen === 0 ? '' :
  1357.                                                 pButton('expimg', '#', false) +
  1358.                                                 pButton('maskimg', '#', true) +
  1359.                                                 (nav.Presto || localRun ? '' :
  1360.                                                         (Cfg.preLoadImgs ? '' : pButton('preimg', '#', false)) +
  1361.                                                         (!TNum && !aib.arch ? '' : pButton('savethr', '#', false)))) +
  1362.                                         (!TNum || localRun ? '' :
  1363.                                                 pButton(Cfg.ajaxUpdThr ? 'upd-on' : 'upd-off', '#', false) +
  1364.                                                 (nav.Safari ? '' : pButton('audio-off', '#', false))) +
  1365.                                         (!aib.abu && !aib.mak && (!aib.fch || aib.arch) ? '' :
  1366.                                                 pButton('catalog', aib.prot + '//' + aib.host + '/' + (aib.mak ?
  1367.                                                         'makaba/makaba.fcgi?task=catalog&board=' + brd : brd + '/catalog.html'), false)) +
  1368.                                         pButton('enable', '#', false) +
  1369.                                         (!TNum && !aib.arch ? '' :
  1370.                                                 '<span id="de-panel-info" title="' + Lng.panelBtn.counter[lang] + '">' +
  1371.                                                 firstThr.pcount + '/' + imgLen + '</span>')
  1372.                                 ) +
  1373.                                 '</ul>' +
  1374.                         '</div><div class="de-content"></div>' +
  1375.                 (Cfg.disabled ? '' : '<div id="de-alert"></div><hr style="clear: both;">') +
  1376.                 '</div>'
  1377.         );
  1378.         panel = $id('de-panel');
  1379.         evtObject = {
  1380.                 attach: false,
  1381.                 odelay: 0,
  1382.                 panel: panel,
  1383.                 handleEvent: function (e) {
  1384.                         switch (e.type) {
  1385.                         case 'click':
  1386.                                 switch (e.target.id) {
  1387.                                 case 'de-btn-logo':
  1388.                                         if (Cfg.expandPanel) {
  1389.                                                 this.panel.lastChild.style.display = 'none';
  1390.                                                 this.attach = false;
  1391.                                         } else {
  1392.                                                 this.attach = true;
  1393.                                         }
  1394.                                         toggleCfg('expandPanel');
  1395.                                         return;
  1396.                                 case 'de-btn-settings': this.attach = toggleContent('cfg', false); break;
  1397.                                 case 'de-btn-hidden': this.attach = toggleContent('hid', false); break;
  1398.                                 case 'de-btn-favor': this.attach = toggleContent('fav', false); break;
  1399.                                 case 'de-btn-video': this.attach = toggleContent('vid', false); break;
  1400.                                 case 'de-btn-refresh': window.location.reload(); break;
  1401.                                 case 'de-btn-goup': scrollTo(0, 0); break;
  1402.                                 case 'de-btn-godown': scrollTo(0, doc.body.scrollHeight || doc.body.offsetHeight); break;
  1403.                                 case 'de-btn-expimg':
  1404.                                         isExpImg = !isExpImg;
  1405.                                         $del($c('de-img-center', doc));
  1406.                                         for (var post = firstThr.op; post; post = post.next) {
  1407.                                                 post.toggleImages(isExpImg);
  1408.                                         }
  1409.                                         break;
  1410.                                 case 'de-btn-preimg':
  1411.                                         isPreImg = !isPreImg;
  1412.                                         if (!e.ctrlKey) {
  1413.                                                 preloadImages(null);
  1414.                                         }
  1415.                                 break;
  1416.                                 case 'de-btn-maskimg':
  1417.                                         toggleCfg('maskImgs');
  1418.                                         updateCSS();
  1419.                                         break;
  1420.                                 case 'de-btn-upd-on':
  1421.                                 case 'de-btn-upd-off':
  1422.                                 case 'de-btn-upd-warn':
  1423.                                         if (updater.enabled) {
  1424.                                                 updater.disable();
  1425.                                         } else {
  1426.                                                 updater.enable();
  1427.                                         }
  1428.                                         break;
  1429.                                 case 'de-btn-audio-on':
  1430.                                 case 'de-btn-audio-off':
  1431.                                         if (updater.toggleAudio(0)) {
  1432.                                                 updater.enable();
  1433.                                                 e.target.id = 'de-btn-audio-on';
  1434.                                         } else {
  1435.                                                 e.target.id = 'de-btn-audio-off';
  1436.                                         }
  1437.                                         $del($c('de-menu', doc));
  1438.                                         break;
  1439.                                 case 'de-btn-savethr': break;
  1440.                                 case 'de-btn-enable':
  1441.                                         toggleCfg('disabled');
  1442.                                         window.location.reload();
  1443.                                         break;
  1444.                                 default: return;
  1445.                                 }
  1446.                                 $pd(e);
  1447.                                 return;
  1448.                         case 'mouseover':
  1449.                                 if (!Cfg.expandPanel) {
  1450.                                         clearTimeout(this.odelay);
  1451.                                         this.panel.lastChild.style.display = '';
  1452.                                 }
  1453.                                 switch (e.target.id) {
  1454.                                 case 'de-btn-settings': KeyEditListener.setTitle(e.target, 10); break;
  1455.                                 case 'de-btn-hidden': KeyEditListener.setTitle(e.target, 7); break;
  1456.                                 case 'de-btn-favor': KeyEditListener.setTitle(e.target, 6); break;
  1457.                                 case 'de-btn-goback': KeyEditListener.setTitle(e.target, 4); break;
  1458.                                 case 'de-btn-gonext': KeyEditListener.setTitle(e.target, 17); break;
  1459.                                 case 'de-btn-maskimg': KeyEditListener.setTitle(e.target, 9); break;
  1460.                                 case 'de-btn-refresh':
  1461.                                         if (TNum) {
  1462.                                                 return;
  1463.                                         }
  1464.                                 case 'de-btn-savethr':
  1465.                                 case 'de-btn-audio-off': addMenu(e);
  1466.                                 }
  1467.                                 return;
  1468.                         default: // mouseout
  1469.                                 if (!Cfg.expandPanel && !this.attach) {
  1470.                                         this.odelay = setTimeout(function (obj) {
  1471.                                                 obj.panel.lastChild.style.display = 'none';
  1472.                                                 obj.attach = false;
  1473.                                         }, 500, this);
  1474.                                 }
  1475.                                 switch (e.target.id) {
  1476.                                 case 'de-btn-refresh':
  1477.                                 case 'de-btn-savethr':
  1478.                                 case 'de-btn-audio-off': removeMenu(e); break;
  1479.                                 }
  1480.                         }
  1481.                 }
  1482.         };
  1483.         panel.addEventListener('click', evtObject, true);
  1484.         panel.addEventListener('mouseover', evtObject, false);
  1485.         panel.addEventListener('mouseout', evtObject, false);
  1486. }
  1487.  
  1488. function toggleContent(name, isUpd, data) {
  1489.         if (liteMode) {
  1490.                 return false;
  1491.         }
  1492.         var remove, el = $c('de-content', doc),
  1493.                 id = 'de-content-' + name;
  1494.         if (!el) {
  1495.                 return false;
  1496.         }
  1497.         if (isUpd && el.id !== id) {
  1498.                 return true;
  1499.         }
  1500.         remove = !isUpd && el.id === id;
  1501.         if (el.hasChildNodes() && Cfg.animation) {
  1502.                 nav.animEvent(el, function (node) {
  1503.                         showContent(node, id, name, remove, data);
  1504.                         id = name = remove = data = null;
  1505.                 });
  1506.                 el.className = 'de-content de-cfg-close';
  1507.                 return !remove;
  1508.         } else {
  1509.                 showContent(el, id, name, remove, data);
  1510.                 return !remove;
  1511.         }
  1512. }
  1513.  
  1514. function addContentBlock(parent, title) {
  1515.         return parent.appendChild($New('div', {'class': 'de-content-block'}, [
  1516.                 $new('input', {'type': 'checkbox'}, {'click': function () {
  1517.                         var el, res = this.checked, i = 0, els = $Q('.de-entry > input', this.parentNode);
  1518.                         for (; el = els[i++];) {
  1519.                                 el.checked = res;
  1520.                         }
  1521.                 }}),
  1522.                 title
  1523.         ]));
  1524. }
  1525.  
  1526. function showContent(cont, id, name, remove, data) {
  1527.         var tNum, i, b, els, el, post, cln, block, temp, cfgTabId;
  1528.         if (name === 'cfg' && !remove && (temp = $q('.de-cfg-tab-back[selected="true"] > .de-cfg-tab', cont))) {
  1529.                 cfgTabId = temp.getAttribute('info');
  1530.         }
  1531.         cont.innerHTML = cont.style.backgroundColor = '';
  1532.         if (remove) {
  1533.                 cont.removeAttribute('id');
  1534.                 return;
  1535.         }
  1536.         cont.id = id;
  1537.         if (name === 'cfg') {
  1538.                 addSettings(cont, cfgTabId);
  1539.         } else if (Cfg.attachPanel) {
  1540.                 cont.style.backgroundColor = getComputedStyle(doc.body).getPropertyValue('background-color');
  1541.         }
  1542.  
  1543.         if (name === 'fav') {
  1544.                 if (data) {
  1545.                         showFavoriteTable(cont, data);
  1546.                 } else {
  1547.                         // TODO: show load message
  1548.                         getStoredObj('DESU_Favorites', function (fav) {
  1549.                                 showFavoriteTable(this, fav);
  1550.                         }.bind(cont));
  1551.                 }
  1552.                 return;
  1553.         }
  1554.  
  1555.         if (name === 'hid') {
  1556.                 for (i = 0, els = $C('de-post-hide', dForm); post = els[i++];) {
  1557.                         if (post.isOp) {
  1558.                                 continue;
  1559.                         }
  1560.                         (cln = post.cloneNode(true)).removeAttribute('id');
  1561.                         cln.style.display = '';
  1562.                         if (cln.classList.contains(aib.cRPost)) {
  1563.                                 cln.classList.add('de-cloned-post');
  1564.                         } else {
  1565.                                 cln.className = aib.cReply + ' de-cloned-post';
  1566.                         }
  1567.                         cln.post = Object.create(cln.clone = post.post);
  1568.                         cln.post.el = cln;
  1569.                         cln.btn = $q('.de-btn-hide, .de-btn-hide-user', cln);
  1570.                         cln.btn.parentNode.className = 'de-post-btns';
  1571.                         cln.btn.onclick = function () { // doesn't work properly. TODO: Fix
  1572.                                 this.hideContent(this.hidden = !this.hidden);
  1573.                         }.bind(cln);
  1574.                         (block || (block = cont.appendChild(
  1575.                                 $add('<div class="de-content-block"><b>' + Lng.hiddenPosts[lang] + ':</b></div>')
  1576.                         ))).appendChild($New('div', {'class': 'de-entry'}, [cln]));
  1577.                 }
  1578.                 if (block) {
  1579.                         cont.appendChild($btn(Lng.expandAll[lang], '', function () {
  1580.                                 $each($Q('.de-cloned-post', this.parentNode), function (el) {
  1581.                                         var post = el.post;
  1582.                                         post.hideContent(post.hidden = !post.hidden);
  1583.                                 });
  1584.                                 this.value = this.value === Lng.undo[lang] ? Lng.expandAll[lang] : Lng.undo[lang];
  1585.                         }));
  1586.                         cont.appendChild($btn(Lng.save[lang], '', function () {
  1587.                                 $each($Q('.de-cloned-post', this.parentNode), function (date, el) {
  1588.                                         if (!el.post.hidden) {
  1589.                                                 el.clone.setUserVisib(false, date, true);
  1590.                                         }
  1591.                                 }.bind(null, Date.now()));
  1592.                                 saveUserPosts(true);
  1593.                         }));
  1594.                 } else {
  1595.                         cont.insertAdjacentHTML('beforeend', '<b>' + Lng.noHidPosts[lang] + '</b>');
  1596.                 }
  1597.                 cont.insertAdjacentHTML('beforeend', '<hr><b>' +
  1598.                         ($isEmpty(hThr) ? Lng.noHidThrds[lang] : Lng.hiddenThrds[lang] + ':') + '</b>');
  1599.                 for (b in hThr) {
  1600.                         if (!$isEmpty(hThr[b])) {
  1601.                                 block = addContentBlock(cont, $new('b', {'text': '/' + b}, null));
  1602.                                 for (tNum in hThr[b]) {
  1603.                                         block.insertAdjacentHTML('beforeend', '<div class="de-entry ' + aib.cReply +
  1604.                                                 '" info="' + b + ';' + tNum + '"><input type="checkbox"><a href="' +
  1605.                                                 aib.getThrdUrl(b, tNum) + '" target="_blank">№' + tNum + '</a> - ' +
  1606.                                                 hThr[b][tNum] + '</div>');
  1607.                                 }
  1608.                         }
  1609.                 }
  1610.                 cont.insertAdjacentHTML('beforeend', '<hr>');
  1611.                 cont.appendChild(addEditButton('hidden', function (Fn) {
  1612.                         Fn(hThr, true, function (data) {
  1613.                                 hThr = data;
  1614.                                 if (!(brd in hThr)) {
  1615.                                         hThr[brd] = {};
  1616.                                 }
  1617.                                 firstThr.updateHidden(hThr[brd]);
  1618.                                 saveHiddenThreads(true);
  1619.                                 locStorage['__de-threads'] = JSON.stringify(hThr);
  1620.                                 locStorage.removeItem('__de-threads');
  1621.                         });
  1622.                 }));
  1623.                 cont.appendChild($btn(Lng.clear[lang], Lng.clrDeleted[lang], function () {
  1624.                         $each($Q('.de-entry[info]', this.parentNode), function (el) {
  1625.                                 var arr = el.getAttribute('info').split(';');
  1626.                                 ajaxLoad(aib.getThrdUrl(arr[0], arr[1]), false, null, function (eCode, eMsg, xhr) {
  1627.                                         if (eCode === 404) {
  1628.                                                 delete hThr[this[0]][this[1]];
  1629.                                                 saveHiddenThreads(true);
  1630.                                         }
  1631.                                 }.bind(arr));
  1632.                         });
  1633.                 }));
  1634.                 cont.appendChild($btn(Lng.remove[lang], Lng.clrSelected[lang], function () {
  1635.                         $each($Q('.de-entry[info]', this.parentNode), function (date, el) {
  1636.                                 var post, arr = el.getAttribute('info').split(';');
  1637.                                 if ($t('input', el).checked) {
  1638.                                         if (arr[1] in pByNum) {
  1639.                                                 pByNum[arr[1]].setUserVisib(false, date, true);
  1640.                                         } else {
  1641.                                                 locStorage['__de-post'] = JSON.stringify({
  1642.                                                         'brd': arr[0],
  1643.                                                         'date': date,
  1644.                                                         'isOp': true,
  1645.                                                         'num': arr[1],
  1646.                                                         'hide': false
  1647.                                                 });
  1648.                                                 locStorage.removeItem('__de-post');
  1649.                                         }
  1650.                                         delete hThr[arr[0]][arr[1]];
  1651.                                 }
  1652.                         }.bind(null, Date.now()));
  1653.                         saveHiddenThreads(true);
  1654.                 }));
  1655.         }
  1656.  
  1657.         if (name === 'vid') {
  1658.                 els = $C('de-video-link', dForm);
  1659.                 if (els.length) {
  1660.                         !$id('de-ytube-api') && doc.head.appendChild(
  1661.                                 $new('script', {'id': 'de-ytube-api', 'src': aib.prot + '//www.youtube.com/player_api'}, null));
  1662.                         cont.insertAdjacentHTML('beforeend', '<div class="de-video-obj"></div><center>' +
  1663.                                 '<a class="de-abtn" id="de-video-btn-prev" href="#" title="' + Lng.prevVideo[lang] +
  1664.                                 '">&#x25C0;</a> <a class="de-abtn" id="de-video-btn-hide" href="#" title="' + Lng.hideLnkList[lang] +
  1665.                                 '">&#x25B2;</a> <a class="de-abtn" id="de-video-btn-next" href="#" title="' + Lng.nextVideo[lang] +
  1666.                                 '">&#x25B6;</a></center><div id="de-video-list"></div>');
  1667.                         post = {};
  1668.                         post.ytInfo = null;
  1669.                         post.ytObj = cont.firstChild;
  1670.                         post.msg = cont.lastChild;
  1671.                         post.el = cont;
  1672.                         post.ytObj.nextSibling.onclick = function (e) {
  1673.                                 $pd(e);
  1674.                                 var node;
  1675.                                 switch (e.target.id) {
  1676.                                 case 'de-video-btn-hide':
  1677.                                         node = this.el.lastChild;
  1678.                                         if (node.style.display === 'none') {
  1679.                                                 node.style.display = '';
  1680.                                                 e.target.textContent = '\u25B2';
  1681.                                         } else {
  1682.                                                 node.style.display = 'none';
  1683.                                                 e.target.textContent = '\u25BC';
  1684.                                         }
  1685.                                         return;
  1686.                                 case 'de-video-btn-prev':
  1687.                                         node = this.ytLink.parentNode,
  1688.                                         (node.previousSibling || node.parentNode.lastChild).firstChild.click();
  1689.                                         break;
  1690.                                 case 'de-video-btn-next':
  1691.                                         node = this.ytLink.parentNode,
  1692.                                         (node.nextSibling || node.parentNode.firstChild).firstChild.click();
  1693.                                 }
  1694.                         }.bind(post);
  1695.                         post.msg.onclick = function (e) {
  1696.                                 $pd(e);
  1697.                                 var c = e.target.classList;
  1698.                                 if (!c.contains('de-video-link')) {
  1699.                                         return;
  1700.                                 }
  1701.                                 if (c.contains('de-current')) {
  1702.                                         post.ytInfo = null;
  1703.                                 }
  1704.                                 new YouTube().clickLink(this, e.target, 2);
  1705.                                 if (!c.contains('de-ytube')) {
  1706.                                         return;
  1707.                                 }
  1708.                                 this.ytObj.firstChild.id = 'de-ytplayer';
  1709.                                 $script(
  1710.                                         'if ("YT" in window && "Player" in window.YT) {\
  1711.                                                 initPlayer();\
  1712.                                         }\
  1713.                                         function onYouTubePlayerAPIReady() {\
  1714.                                                 initPlayer();\
  1715.                                         }\
  1716.                                         function initPlayer() {\
  1717.                                                 var ytplayer = new YT.Player("de-ytplayer", { events: {\
  1718.                                                         "onError": gotoNextVideo,\
  1719.                                                         "onReady": function (e) {\
  1720.                                                                 ' + (post.firstCall ? '' : 'e.target.playVideo();') + '\
  1721.                                                         },\
  1722.                                                         "onStateChange": function (e) {\
  1723.                                                                 if (e.data === 0) {\
  1724.                                                                         gotoNextVideo();\
  1725.                                                                 }\
  1726.                                                         }\
  1727.                                                 }});\
  1728.                                         }\
  1729.                                         function gotoNextVideo() {\
  1730.                                                 document.getElementById("de-video-btn-next").click();\
  1731.                                         }'
  1732.                                 );
  1733.                                 post.firstCall = false;
  1734.                         }.bind(post);
  1735.                         temp = new YouTube();
  1736.                         for (i = 0; el = els[i++];) {
  1737.                                 el = el.cloneNode(true);
  1738.                                 post.msg.insertAdjacentHTML('beforeend', '<div class="de-entry ' + aib.cReply + '"></div>');
  1739.                                 post.msg.lastChild.appendChild(el);
  1740.                                 b = el.classList.contains('de-ytube');
  1741.                                 el.ytInfo = el.href.match(b ? temp.ytReg : temp.vimReg);
  1742.                                 if (i === 1) {
  1743.                                         post.ytLink = el;
  1744.                                         post.firstCall = true;
  1745.                                         el.click();
  1746.                                 } else {
  1747.                                         el.classList.remove('de-current');
  1748.                                 }
  1749.                         }
  1750.                 } else {
  1751.                         cont.insertAdjacentHTML('beforeend', '<b>' + Lng.noVideoLinks[lang] + '</b>');
  1752.                 }
  1753.         }
  1754.  
  1755.         if (Cfg.animation) {
  1756.                 cont.className = 'de-content de-cfg-open';
  1757.         }
  1758. }
  1759.  
  1760. function clearFavoriteTable() {
  1761.         var els = $Q('.de-entry[de-removed]', doc),
  1762.                 len = els.length;
  1763.         if (len > 0) {
  1764.                 getStoredObj('DESU_Favorites', function (fav) {
  1765.                         for (var el, i = 0; i < len; ++i) {
  1766.                                 el = els[i];
  1767.                                 removeFavoriteEntry(fav, el.getAttribute('de-host'), el.getAttribute('de-board'),
  1768.                                         el.getAttribute('de-num'), true);
  1769.                         }
  1770.                         saveFavorites(fav);
  1771.                 });
  1772.         }
  1773. }
  1774.  
  1775. function showFavoriteTable(cont, data) {
  1776.         var h, b, i, block, tNum;
  1777.         for (h in data) {
  1778.                 for (b in data[h]) {
  1779.                         i = data[h][b];
  1780.                         block = addContentBlock(cont, i.url ?
  1781.                                 $new('a', {'href': i.url, 'text': h + '/' + b}, null) :
  1782.                                 $new('b', {'text': h + '/' + b}, null));
  1783.                         if (h === aib.host && b === brd) {
  1784.                                 block.classList.add('de-fav-current');
  1785.                         }
  1786.                         for (tNum in data[h][b]) {
  1787.                                 if (tNum === 'url') {
  1788.                                         continue;
  1789.                                 }
  1790.                                 i = data[h][b][tNum];
  1791.                                 if (!i.url.startsWith('http')) {
  1792.                                         i.url = (h === aib.host ? aib.prot + '//' : 'http://') + h + i.url;
  1793.                                 }
  1794.                                 block.insertAdjacentHTML('beforeend', '<div class="de-entry ' + aib.cReply +
  1795.                                         '" de-host="' + h + '" de-board="' + b + '" de-num="' + tNum + '" de-url="' + i.url +
  1796.                                         '"><input type="checkbox"><span class="de-btn-expthr" title="' + Lng.findThrd[lang] +
  1797.                                         '"></span><a href="' + i.url + '">№' + tNum + '</a><span class="de-fav-title"> - ' +
  1798.                                         i.txt + '</span><span class="de-fav-inf-posts">[<span class="de-fav-inf-old" title="' +
  1799.                                         Lng.oldPosts[lang] + '">' + i.cnt + '</span>] <span class="de-fav-inf-new" title="' +
  1800.                                         Lng.newPosts[lang] + '"' + (i['new'] ? '>' : ' style="display: none;">') +
  1801.                                         (i['new'] || 0) + '</span> <span class="de-fav-inf-page" title="' +
  1802.                                         Lng.thrPage[lang] + '"></span></span></div>');
  1803.                                 block.lastChild.firstChild.nextSibling.onclick = loadFavorThread;
  1804.                         }
  1805.                 }
  1806.         }
  1807.         cont.insertAdjacentHTML('afterbegin', '<b>' + (Lng[block ? 'favThrds' : 'noFavThrds'][lang]) + '</b>');
  1808.         cont.insertAdjacentHTML('beforeend', '<hr>');
  1809.         cont.appendChild(addEditButton('favor', function (Fn) {
  1810.                 getStoredObj('DESU_Favorites', function (Fn, val) {
  1811.                         Fn(val, true, saveFavorites);
  1812.                 }.bind(null, Fn));
  1813.         }));
  1814.         cont.appendChild($btn(Lng.refresh[lang], Lng.infoCount[lang], function () {
  1815.                 getStoredObj('DESU_Favorites', function (fav) {
  1816.                         var i, els, len, update = false;
  1817.                         var queue = new $queue(4, function (qIdx, num, el) {
  1818.                                 var c, host = el.getAttribute('de-host'),
  1819.                                         b = el.getAttribute('de-board'),
  1820.                                         num = el.getAttribute('de-num'),
  1821.                                         f = fav[host][b][num];
  1822.                                 if (host !== aib.host) {
  1823.                                         queue.end(qIdx);
  1824.                                         return;
  1825.                                 }
  1826.                                 el = $c('de-fav-inf-new', el);
  1827.                                 el.style.display = '';
  1828.                                 el.textContent = '';
  1829.                                 el.className = 'de-wait';
  1830.                                 ajaxLoad(aib.getThrdUrl(b, num), true, function (form, xhr) {
  1831.                                         var cnt = aib.getPosts(form).length + 1 - this.previousElementSibling.textContent;
  1832.                                         this.textContent = cnt;
  1833.                                         this.className = 'de-fav-inf-new';
  1834.                                         if (cnt === 0) {
  1835.                                                 this.style.display = 'none';
  1836.                                         } else {
  1837.                                                 f['new'] = cnt;
  1838.                                                 update = true;
  1839.                                         }
  1840.                                         queue.end(qIdx);
  1841.                                         f = qIdx = null;
  1842.                                 }.bind(el), function (eCode, eMsg, xhr) {
  1843.                                         this.textContent = getErrorMessage(eCode, eMsg);
  1844.                                         this.classList.remove('de-wait');
  1845.                                         queue.end(qIdx);
  1846.                                         qIdx = null;
  1847.                                 }.bind(el));
  1848.                         }, function () {
  1849.                                 if (update) {
  1850.                                         setStored('DESU_Favorites', JSON.stringify(fav));
  1851.                                 }
  1852.                                 fav = queue = update = null;
  1853.                         });
  1854.                         for (i = 0, els = $C('de-entry', doc), len = els.length; i < len; ++i) {
  1855.                                 queue.run(els[i]);
  1856.                         }
  1857.                         queue.complete();
  1858.                 });
  1859.         }));
  1860.         cont.appendChild($btn(Lng.page[lang], Lng.infoPage[lang], function () {
  1861.                 var els = $C('de-entry', doc),
  1862.                         i = 6,
  1863.                         loaded = 0;
  1864.                 $alert(Lng.loading[lang], 'load-pages', true);
  1865.                 while (i--) {
  1866.                         ajaxLoad(aib.getPageUrl(brd, i), true, function (idx, form, xhr) {
  1867.                                 for (var inf, el, len = this.length, i = 0; i < len; ++i) {
  1868.                                         el = this[i];
  1869.                                         if (el.getAttribute('de-host') === aib.host && el.getAttribute('de-board') === brd) {
  1870.                                                 inf = $c('de-fav-inf-page', el);
  1871.                                                 if ((new RegExp('(?:№|No.|>)\\s*' + el.getAttribute('de-num') + '\\s*<'))
  1872.                                                         .test(form.innerHTML))
  1873.                                                 {
  1874.                                                         inf.innerHTML = '@' + idx;
  1875.                                                 } else if (loaded === 5 && !inf.textContent.contains('@')) {
  1876.                                                         inf.innerHTML = '@?';
  1877.                                                 }
  1878.                                         }
  1879.                                 }
  1880.                                 if (loaded === 5) {
  1881.                                         closeAlert($id('de-alert-load-pages'));
  1882.                                 }
  1883.                                 loaded++;
  1884.                         }.bind(els, i), function (eCode, eMsg, xhr) {
  1885.                                 if (loaded === 5) {
  1886.                                         closeAlert($id('de-alert-load-pages'));
  1887.                                 }
  1888.                                 loaded++;
  1889.                         });
  1890.                 }
  1891.         }));
  1892.         cont.appendChild($btn(Lng.clear[lang], Lng.clrDeleted[lang], function () {
  1893.                 var i, len, els, queue = new $queue(4, function (qIdx, num, el) {
  1894.                         var node = $c('de-fav-inf-page', el);
  1895.                         node.classList.add('de-wait');
  1896.                         ajaxLoad(el.getAttribute('de-url'), false, function () {
  1897.                                 this.classList.remove('de-wait');
  1898.                                 queue.end(qIdx);
  1899.                                 qIdx = null;
  1900.                         }.bind(node), function (eCode, eMsg, xhr) {
  1901.                                 if (eCode === 404) {
  1902.                                         this.textContent = getErrorMessage(eCode, eMsg);
  1903.                                         this.classList.remove('de-wait');
  1904.                                         el.setAttribute('de-removed', '');
  1905.                                 }
  1906.                                 queue.end(qIdx);
  1907.                                 qIdx = el = null;
  1908.                         }.bind(node));
  1909.                 }, function () {
  1910.                         queue = null;
  1911.                         clearFavoriteTable();
  1912.                 });
  1913.                 for (i = 0, els = $C('de-entry', doc), len = els.length; i < len; ++i) {
  1914.                         queue.run(els[i]);
  1915.                 }
  1916.                 queue.complete();
  1917.         }));
  1918.         cont.appendChild($btn(Lng.remove[lang], Lng.clrSelected[lang], function () {
  1919.                 $each($C('de-entry', doc), function (el) {
  1920.                         if ($t('input', el).checked) {
  1921.                                 el.setAttribute('de-removed', '');
  1922.                         }
  1923.                 });
  1924.                 clearFavoriteTable();
  1925.         }));
  1926.         if (Cfg.animation) {
  1927.                 cont.className = 'de-content de-cfg-open';
  1928.         }
  1929. }
  1930.  
  1931.  
  1932. // SETTINGS
  1933. // ===========================================================================================================
  1934.  
  1935. function fixSettings() {
  1936.         function toggleBox(state, arr) {
  1937.                 var i = arr.length,
  1938.                         nState = !state;
  1939.                 while (i--) {
  1940.                         ($q(arr[i], doc) || {}).disabled = nState;
  1941.                 }
  1942.         }
  1943.         toggleBox(Cfg.ajaxUpdThr, [
  1944.                 'input[info="noErrInTitle"]', 'input[info="favIcoBlink"]',
  1945.                 'input[info="markNewPosts"]', 'input[info="desktNotif"]'
  1946.         ]);
  1947.         toggleBox(Cfg.expandImgs, [
  1948.                 'input[info="imgNavBtns"]', 'input[info="resizeDPI"]', 'input[info="resizeImgs"]',
  1949.                 'input[info="minImgSize"]', 'input[info="zoomFactor"]',
  1950.                 'input[info="webmControl"]', 'input[info="webmVolume"]'
  1951.         ]);
  1952.         toggleBox(Cfg.preLoadImgs, ['input[info="findImgFile"]']);
  1953.         toggleBox(Cfg.openImgs, ['input[info="openGIFs"]']);
  1954.         toggleBox(Cfg.linksNavig, [
  1955.                 'input[info="linksOver"]', 'input[info="linksOut"]', 'input[info="markViewed"]',
  1956.                 'input[info="strikeHidd"]', 'input[info="noNavigHidd"]'
  1957.         ]);
  1958.         toggleBox(Cfg.addYouTube && Cfg.addYouTube !== 4, [
  1959.                 'select[info="YTubeType"]', 'input[info="YTubeHD"]', 'input[info="addVimeo"]'
  1960.         ]);
  1961.         toggleBox(Cfg.addYouTube, [
  1962.                 'input[info="YTubeWidth"]', 'input[info="YTubeHeigh"]', 'input[info="YTubeTitles"]'
  1963.         ]);
  1964.         toggleBox(Cfg.ajaxReply, ['input[info="sendErrNotif"]', 'input[info="scrAfterRep"]']);
  1965.         toggleBox(Cfg.ajaxReply === 2, [
  1966.                 'input[info="postSameImg"]', 'input[info="removeEXIF"]', 'input[info="removeFName"]'
  1967.         ]);
  1968.         toggleBox(Cfg.addTextBtns, ['input[info="txtBtnsLoc"]']);
  1969.         toggleBox(Cfg.updScript, ['select[info="scrUpdIntrv"]']);
  1970.         toggleBox(Cfg.hotKeys, ['input[info="loadPages"]']);
  1971. }
  1972.  
  1973. function lBox(id, isBlock, Fn) {
  1974.         var el = $new('input', {'info': id, 'type': 'checkbox'}, {'click': function () {
  1975.                 toggleCfg(this.getAttribute('info'));
  1976.                 fixSettings();
  1977.                 if (Fn) {
  1978.                         Fn(this);
  1979.                 }
  1980.         }});
  1981.         el.checked = Cfg[id];
  1982.         return $New('label', isBlock ? {'class': 'de-block'} : null, [el, $txt(' ' + Lng.cfg[id][lang])]);
  1983. }
  1984.  
  1985. function inpTxt(id, size, Fn) {
  1986.         return $new('input', {'info': id, 'type': 'text', 'size': size, 'value': Cfg[id]}, {
  1987.                 'keyup': Fn ? Fn : function () {
  1988.                         saveCfg(this.getAttribute('info'), this.value);
  1989.                 }
  1990.         });
  1991. }
  1992.  
  1993. function optSel(id, isBlock, Fn) {
  1994.         for (var i = 0, x = Lng.cfg[id], len = x.sel[lang].length, el, opt = ''; i < len; i++) {
  1995.                 opt += '<option value="' + i + '">' + x.sel[lang][i] + '</option>';
  1996.         }
  1997.         el = $add('<select info="' + id + '">' + opt + '</select>');
  1998.         el.addEventListener('change', Fn || function () {
  1999.                 saveCfg(this.getAttribute('info'), this.selectedIndex);
  2000.                 fixSettings();
  2001.         }, false);
  2002.         el.selectedIndex = Cfg[id];
  2003.         return $New('label', isBlock ? {'class': 'de-block'} : null, [el, $txt(' ' + x.txt[lang])]);
  2004. }
  2005.  
  2006. function cfgTab(name) {
  2007.         return $New('div', {'class': aib.cReply + ' de-cfg-tab-back', 'selected': false}, [$new('div', {
  2008.                 'class': 'de-cfg-tab',
  2009.                 'text': Lng.cfgTab[name][lang],
  2010.                 'info': name}, {
  2011.                 'click': function () {
  2012.                         var el, id, pN = this.parentNode;
  2013.                         if (pN.getAttribute('selected') === 'true') {
  2014.                                 return;
  2015.                         }
  2016.                         if (el = $c('de-cfg-body', doc)) {
  2017.                                 el.className = 'de-cfg-unvis';
  2018.                                 $q('.de-cfg-tab-back[selected="true"]', doc).setAttribute('selected', false);
  2019.                         }
  2020.                         pN.setAttribute('selected', true);
  2021.                         if (!(el = $id('de-cfg-' + (id = this.getAttribute('info'))))) {
  2022.                                 $after($id('de-cfg-bar'), el =
  2023.                                         id === 'filters' ? getCfgFilters() :
  2024.                                         id === 'posts' ? getCfgPosts() :
  2025.                                         id === 'images' ? getCfgImages() :
  2026.                                         id === 'links' ? getCfgLinks() :
  2027.                                         id === 'form' ? getCfgForm() :
  2028.                                         id === 'common' ? getCfgCommon() :
  2029.                                         getCfgInfo()
  2030.                                 );
  2031.                                 if (id === 'filters') {
  2032.                                         updRowMeter.call($id('de-spell-edit'));
  2033.                                 }
  2034.                         }
  2035.                         el.className = 'de-cfg-body';
  2036.                         if (id === 'filters') {
  2037.                                 $id('de-spell-edit').value = spells.list;
  2038.                         }
  2039.                         fixSettings();
  2040.                 }
  2041.         })]);
  2042. }
  2043.  
  2044. function updRowMeter() {
  2045.         var str, top = this.scrollTop,
  2046.                 el = this.parentNode.previousSibling.firstChild,
  2047.                 num = el.numLines || 1,
  2048.                 i = 15;
  2049.         if (num - i < ((top / 12) | 0 + 1)) {
  2050.                 str = '';
  2051.                 while (i--) {
  2052.                         str += num++ + '<br>';
  2053.                 }
  2054.                 el.insertAdjacentHTML('beforeend', str);
  2055.                 el.numLines = num;
  2056.         }
  2057.         el.scrollTop = top;
  2058. }
  2059.  
  2060. function getCfgFilters() {
  2061.         return $New('div', {'class': 'de-cfg-unvis', 'id': 'de-cfg-filters'}, [
  2062.                 lBox('hideBySpell', false, toggleSpells),
  2063.                 $New('div', {'id': 'de-spell-panel'}, [
  2064.                         $new('a', {
  2065.                                 'id': 'de-btn-addspell',
  2066.                                 'text': Lng.add[lang],
  2067.                                 'href': '#',
  2068.                                 'class': 'de-abtn'}, {
  2069.                                 'click': $pd,
  2070.                                 'mouseover': addMenu,
  2071.                                 'mouseout': removeMenu
  2072.                         }),
  2073.                         $new('a', {'text': Lng.apply[lang], 'href': '#', 'class': 'de-abtn'}, {'click': function (e) {
  2074.                                 $pd(e);
  2075.                                 saveCfg('hideBySpell', 1);
  2076.                                 $q('input[info="hideBySpell"]', doc).checked = true;
  2077.                                 toggleSpells();
  2078.                         }}),
  2079.                         $new('a', {'text': Lng.clear[lang], 'href': '#', 'class': 'de-abtn'}, {'click': function (e) {
  2080.                                 $pd(e);
  2081.                                 $id('de-spell-edit').value = '';
  2082.                                 toggleSpells();
  2083.                         }}),
  2084.                         $add('<a href="https://github.com/SthephanShinkufag/Dollchan-Extension-Tools/wiki/Spells-' +
  2085.                                 (lang ? 'en' : 'ru') + '" class="de-abtn" target="_blank">[?]</a>')
  2086.                 ]),
  2087.                 $New('div', {'id': 'de-spell-div'}, [
  2088.                         $add('<div><div id="de-spell-rowmeter"></div></div>'),
  2089.                         $New('div', null, [$new('textarea', {'id': 'de-spell-edit', 'wrap': 'off'}, {
  2090.                                 'keydown': updRowMeter,
  2091.                                 'scroll': updRowMeter
  2092.                         })])
  2093.                 ]),
  2094.                 lBox('sortSpells', true, function () {
  2095.                         if (Cfg.sortSpells) {
  2096.                                 toggleSpells();
  2097.                         }
  2098.                 }),
  2099.                 lBox('menuHiddBtn', true, null),
  2100.                 lBox('hideRefPsts', true, null),
  2101.                 lBox('delHiddPost', true, function () {
  2102.                         $each($C('de-post-hide', dForm), function (el) {
  2103.                                 var wrap = el.post.wrap,
  2104.                                         hide = !wrap.classList.contains('de-hidden');
  2105.                                 if (hide) {
  2106.                                         wrap.insertAdjacentHTML('beforebegin',
  2107.                                                 '<span style="counter-increment: de-cnt 1;"></span>');
  2108.                                 } else {
  2109.                                         $del(wrap.previousSibling);
  2110.                                 }
  2111.                                 wrap.classList.toggle('de-hidden');
  2112.                         });
  2113.                         updateCSS();
  2114.                 })
  2115.         ]);
  2116. }
  2117.  
  2118. function getCfgPosts() {
  2119.         return $New('div', {'class': 'de-cfg-unvis', 'id': 'de-cfg-posts'}, [
  2120.                 lBox('ajaxUpdThr', false, TNum ? function () {
  2121.                         if (Cfg.ajaxUpdThr) {
  2122.                                 updater.enable();
  2123.                         } else {
  2124.                                 updater.disable();
  2125.                         }
  2126.                 } : null),
  2127.                 $New('label', null, [
  2128.                         inpTxt('updThrDelay', 2, null),
  2129.                         $txt(Lng.cfg.updThrDelay[lang])
  2130.                 ]),
  2131.                 $New('div', {'class': 'de-cfg-depend'}, [
  2132.                         lBox('noErrInTitle', true, null),
  2133.                         lBox('favIcoBlink', true, null),
  2134.                         lBox('markNewPosts', true, function () {
  2135.                                 firstThr.clearPostsMarks();
  2136.                         }),
  2137.                         $if('Notification' in window, lBox('desktNotif', true, function () {
  2138.                                 if (Cfg.desktNotif) {
  2139.                                         Notification.requestPermission();
  2140.                                 }
  2141.                         }))
  2142.                 ]),
  2143.                 optSel('expandPosts', true, null),
  2144.                 optSel('postBtnsCSS', true, null),
  2145.                 lBox('noSpoilers', true, updateCSS),
  2146.                 lBox('noPostNames', true, updateCSS),
  2147.                 lBox('noPostScrl', true, updateCSS),
  2148.                 $New('div', null, [
  2149.                         lBox('correctTime', false, dateTime.toggleSettings),
  2150.                         $add('<a href="https://github.com/SthephanShinkufag/Dollchan-Extension-Tools/wiki/Settings-time-' +
  2151.                                 (lang ? 'en' : 'ru') + '" class="de-abtn" target="_blank">[?]</a>')
  2152.                 ]),
  2153.                 $New('div', {'class': 'de-cfg-depend'}, [
  2154.                         $New('div', null, [
  2155.                                 inpTxt('timeOffset', 2, null),
  2156.                                 $txt(Lng.cfg.timeOffset[lang])
  2157.                         ]),
  2158.                         $New('div', null, [
  2159.                                 inpTxt('timePattern', 25, null),
  2160.                                 $txt(Lng.cfg.timePattern[lang])
  2161.                         ]),
  2162.                         $New('div', null, [
  2163.                                 inpTxt('timeRPattern', 25, null),
  2164.                                 $txt(Lng.cfg.timeRPattern[lang])
  2165.                         ])
  2166.                 ])
  2167.         ]);
  2168. }
  2169.  
  2170. function getCfgImages() {
  2171.         return $New('div', {'class': 'de-cfg-unvis', 'id': 'de-cfg-images'}, [
  2172.                 optSel('expandImgs', true, null),
  2173.                 $New('div', {'style': 'padding-left: 25px;'}, [
  2174.                         lBox('imgNavBtns', true, updateCSS),
  2175.                         lBox('resizeImgs', true, null),
  2176.                         $if(Post.sizing.dPxRatio > 1, lBox('resizeDPI', true, null)),
  2177.                         $New('div', null, [
  2178.                                 inpTxt('minImgSize', 4, function () {
  2179.                                         saveCfg('minImgSize', Math.max(+this.value, 1));
  2180.                                 }),
  2181.                                 $txt(Lng.cfg.minImgSize[lang])
  2182.                         ]),
  2183.                         inpTxt('zoomFactor', 4, function () {
  2184.                                 saveCfg('zoomFactor', Math.min(Math.max(+this.value, 1), 100));
  2185.                         }),
  2186.                         $txt(Lng.cfg.zoomFactor[lang]),
  2187.                         lBox('webmControl', true, null),
  2188.                         $New('div', null, [
  2189.                                 inpTxt('webmVolume', 4, function () {
  2190.                                         saveCfg('webmVolume', Math.min(+this.value, 100));
  2191.                                 }),
  2192.                                 $txt(Lng.cfg.webmVolume[lang])
  2193.                         ])
  2194.                 ]),
  2195.                 $if(!nav.Presto, lBox('preLoadImgs', true, null)),
  2196.                 $if(!nav.Presto, $New('div', {'class': 'de-cfg-depend'}, [
  2197.                         lBox('findImgFile', true, null)
  2198.                 ])),
  2199.                 lBox('openImgs', true, null),
  2200.                 $New('div', {'class': 'de-cfg-depend'}, [ lBox('openGIFs', false, null)]),
  2201.                 lBox('imgSrcBtns', true, null)
  2202.         ]);
  2203. }
  2204.  
  2205. function getCfgLinks() {
  2206.         return $New('div', {'class': 'de-cfg-unvis', 'id': 'de-cfg-links'}, [
  2207.                 optSel('linksNavig', true, null),
  2208.                 $New('div', {'class': 'de-cfg-depend'}, [
  2209.                         $New('div', null, [
  2210.                                 inpTxt('linksOver', 4, function () {
  2211.                                         saveCfg('linksOver', +this.value | 0);
  2212.                                 }),
  2213.                                 $txt(Lng.cfg.linksOver[lang])
  2214.                         ]),
  2215.                         $New('div', null, [
  2216.                                 inpTxt('linksOut', 4, function () {
  2217.                                         saveCfg('linksOut', +this.value | 0);
  2218.                                 }),
  2219.                                 $txt(Lng.cfg.linksOut[lang])
  2220.                         ]),
  2221.                         lBox('markViewed', true, null),
  2222.                         lBox('strikeHidd', true, null),
  2223.                         lBox('noNavigHidd', true, null)
  2224.                 ]),
  2225.                 lBox('crossLinks', true, null),
  2226.                 lBox('insertNum', true, null),
  2227.                 lBox('addMP3', true, null),
  2228.                 lBox('addImgs', true, null),
  2229.                 optSel('addYouTube', true, null),
  2230.                 $New('div', {'class': 'de-cfg-depend'}, [
  2231.                         $New('div', null, [
  2232.                                 optSel('YTubeType', false, null),
  2233.                                 inpTxt('YTubeWidth', 4, null),
  2234.                                 $txt('×'),
  2235.                                 inpTxt('YTubeHeigh', 4, null),
  2236.                                 $txt(' '),
  2237.                                 lBox('YTubeHD', false, null)
  2238.                         ]),
  2239.                         $if(!nav.Opera11 || nav.isGM, lBox('YTubeTitles', false, null)),
  2240.                         lBox('addVimeo', true, null)
  2241.                 ])
  2242.         ]);
  2243. }
  2244.  
  2245. function getCfgForm() {
  2246.         return $New('div', {'class': 'de-cfg-unvis', 'id': 'de-cfg-form'}, [
  2247.                 optSel('ajaxReply', true, null),
  2248.                 $if(pr.form, $New('div', {'class': 'de-cfg-depend'}, [
  2249.                         $if(!nav.Opera11, $New('div', null, [
  2250.                                 lBox('postSameImg', true, null),
  2251.                                 lBox('removeEXIF', false, null),
  2252.                                 lBox('removeFName', false, null),
  2253.                                 lBox('sendErrNotif', true, null)
  2254.                         ])),
  2255.                         lBox('scrAfterRep', true, null)
  2256.                 ])),
  2257.                 $if(pr.form, optSel('addPostForm', true, function() {
  2258.                         saveCfg('addPostForm', this.selectedIndex);
  2259.                         pr.isTopForm = Cfg.addPostForm !== 0;
  2260.                         pr.setReply(false, !TNum || Cfg.addPostForm > 1);
  2261.                 })),
  2262.                 lBox('favOnReply', true, null),
  2263.                 $if(pr.subj, lBox('warnSubjTrip', false, null)),
  2264.                 $if(pr.file && !nav.Presto, lBox('fileThumb', true, function () {
  2265.                         for (var inp = pr.fileObj; true; inp = inp.next) {
  2266.                                 inp.updateUtils();
  2267.                                 if (!inp.next) {
  2268.                                         break;
  2269.                                 }
  2270.                         }
  2271.                         if (inp.empty) {
  2272.                                 inp.hideInputs();
  2273.                         }
  2274.                 })),
  2275.                 $if(!aib.mak && pr.mail, $New('div', null, [
  2276.                         lBox('addSageBtn', false, null),
  2277.                         lBox('saveSage', false, null)
  2278.                 ])),
  2279.                 $if(pr.capTr, optSel('captchaLang', true, null)),
  2280.                 $if(pr.txta, $New('div', null, [
  2281.                         optSel('addTextBtns', false, function () {
  2282.                                 saveCfg('addTextBtns', this.selectedIndex);
  2283.                                 pr.addTextPanel();
  2284.                         }),
  2285.                         lBox('txtBtnsLoc', false, pr.addTextPanel.bind(pr))
  2286.                 ])),
  2287.                 $if(pr.passw, $New('div', null, [
  2288.                         inpTxt('passwValue', 15, PostForm.setUserPassw),
  2289.                         $txt(Lng.cfg.userPassw[lang]),
  2290.                         $btn(Lng.change[lang], '', function () {
  2291.                                 $q('input[info="passwValue"]', doc).value = Math.round(Math.random() * 1e15).toString(32);
  2292.                                 PostForm.setUserPassw();
  2293.                         })
  2294.                 ])),
  2295.                 $if(pr.name, $New('div', null, [
  2296.                         inpTxt('nameValue', 15, PostForm.setUserName),
  2297.                         lBox('userName', false, PostForm.setUserName)
  2298.                 ])),
  2299.                 $New('div', null, [
  2300.                         $txt(Lng.dontShow[lang]),
  2301.                         lBox('noBoardRule', false, updateCSS),
  2302.                         $if(pr.gothr, lBox('noGoto', false, function () {
  2303.                                 $disp(pr.gothr);
  2304.                         })),
  2305.                         $if(pr.passw, lBox('noPassword', false, function () {
  2306.                                 $disp(pr.passw.parentNode.parentNode);
  2307.                         }))
  2308.                 ])
  2309.         ]);
  2310. }
  2311.  
  2312. function getCfgCommon() {
  2313.         return $New('div', {'class': 'de-cfg-unvis', 'id': 'de-cfg-common'}, [
  2314.                 optSel('scriptStyle', true, function () {
  2315.                         saveCfg('scriptStyle', this.selectedIndex);
  2316.                         var lang = getThemeLang();
  2317.                         $id('de1-main').lang = lang;
  2318.                         $id('de-qarea').lang = lang;
  2319.                 }),
  2320.                 $New('div', null, [
  2321.                         lBox('userCSS', false, updateCSS),
  2322.                         addEditButton('css', function (Fn) {
  2323.                                 Fn(Cfg.userCSSTxt, false, function () {
  2324.                                         saveCfg('userCSSTxt', this.value);
  2325.                                         updateCSS();
  2326.                                         toggleContent('cfg', true);
  2327.                                 });
  2328.                         })
  2329.                 ]),
  2330.                 lBox('attachPanel', true, function () {
  2331.                         toggleContent('cfg', false);
  2332.                         updateCSS();
  2333.                 }),
  2334.                 lBox('panelCounter', true, updateCSS),
  2335.                 lBox('rePageTitle', true, null),
  2336.                 $if(nav.Anim, lBox('animation', true, null)),
  2337.                 lBox('closePopups', true, null),
  2338.                 $New('div', null, [
  2339.                         lBox('hotKeys', false, function () {
  2340.                                 if (Cfg.hotKeys) {
  2341.                                         if (hKeys) {
  2342.                                                 hKeys.enable();
  2343.                                         } else {
  2344.                                                 hKeys = new HotKeys();
  2345.                                         }
  2346.                                 } else if (hKeys) {
  2347.                                         hKeys.disable();
  2348.                                 }
  2349.                         }),
  2350.                         $btn(Lng.edit[lang], '', function (e) {
  2351.                                 $pd(e);
  2352.                                 if ($id('de-alert-edit-hotkeys')) {
  2353.                                         return;
  2354.                                 }
  2355.                                 HotKeys.readKeys(function (keys) {
  2356.                                         var aEl, evtListener, temp = KeyEditListener.getEditMarkup(keys);
  2357.                                         $alert(temp[1], 'edit-hotkeys', false);
  2358.                                         aEl = $id('de-alert-edit-hotkeys');
  2359.                                         evtListener = new KeyEditListener(aEl, keys, temp[0]);
  2360.                                         aEl.addEventListener('focus', evtListener, true);
  2361.                                         aEl.addEventListener('blur', evtListener, true);
  2362.                                         aEl.addEventListener('click', evtListener, true);
  2363.                                         aEl.addEventListener('keydown', evtListener, true);
  2364.                                         aEl.addEventListener('keyup', evtListener, true);
  2365.                                 });
  2366.                         })
  2367.                 ]),
  2368.                 $New('div', {'class': 'de-cfg-depend'}, [
  2369.                         inpTxt('loadPages', 2, null),
  2370.                         $txt(Lng.cfg.loadPages[lang])
  2371.                 ]),
  2372.                 $if(!nav.isChromeStorage && !nav.Presto || nav.isGM, $New('div', null, [
  2373.                         lBox('updScript', true, null),
  2374.                         $New('div', {'class': 'de-cfg-depend'}, [
  2375.                                 optSel('scrUpdIntrv', false, null),
  2376.                                 $btn(Lng.checkNow[lang], '', function () {
  2377.                                         $alert(Lng.loading[lang], 'updavail', true);
  2378.                                         checkForUpdates(true, function (html) {
  2379.                                                 $alert(html, 'updavail', false);
  2380.                                         });
  2381.                                 })
  2382.                         ])
  2383.                 ])),
  2384.                 lBox('turnOff', true, function () {
  2385.                         for (var dm in comCfg) {
  2386.                                 if (dm !== aib.dm && dm !== 'global' && dm !== 'lastUpd') {
  2387.                                         comCfg[dm].disabled = Cfg.turnOff;
  2388.                                 }
  2389.                         }
  2390.                         setStored('DESU_Config', JSON.stringify(comCfg) || '');
  2391.                 })
  2392.         ]);
  2393. }
  2394.  
  2395. function getCfgInfo() {
  2396.         function getHiddenThrCount() {
  2397.                 var b, tNum, count = 0;
  2398.                 for (b in hThr) {
  2399.                         for (tNum in hThr[b]) {
  2400.                                 count++;
  2401.                         }
  2402.                 }
  2403.                 return count;
  2404.         }
  2405.         return $New('div', {'class': 'de-cfg-unvis', 'id': 'de-cfg-info'}, [
  2406.                 $add('<div style="padding-bottom: 10px;">' +
  2407.                         '<a href="https://github.com/SthephanShinkufag/Dollchan-Extension-Tools/wiki/versions" ' +
  2408.                         'target="_blank">v' + version + '</a>&nbsp;|&nbsp;' +
  2409.                         '<a href="http://www.freedollchan.org/scripts/" target="_blank">Freedollchan</a>&nbsp;|&nbsp;' +
  2410.                         '<a href="https://github.com/SthephanShinkufag/Dollchan-Extension-Tools/wiki/' +
  2411.                         (lang ? 'home-en/' : '') + '" target="_blank">Github</a></div>'),
  2412.                 $add('<div><div style="display: inline-block; vertical-align: top; width: 186px; height: 230px;">' +
  2413.                         Lng.thrViewed[lang] + Cfg.stats.view + '<br>' +
  2414.                         Lng.thrCreated[lang] + Cfg.stats.op + '<br>' +
  2415.                         Lng.thrHidden[lang] + getHiddenThrCount() + '<br>' +
  2416.                         Lng.postsSent[lang] + Cfg.stats.reply + '</div>' +
  2417.                         '<div style="display: inline-block; padding-left: 7px; height: 230px; ' +
  2418.                         'border-left: 1px solid grey;">' + new Logger().get().join('<br>') + '</div></div>'),
  2419.                 $btn(Lng.debug[lang], Lng.infoDebug[lang], function () {
  2420.                         $alert(Lng.infoDebug[lang] +
  2421.                                 ':<textarea readonly id="de-debug-info" class="de-editor"></textarea>', 'help-debug', false);
  2422.                         $id('de-debug-info').value = JSON.stringify({
  2423.                                 'version': version,
  2424.                                 'location': String(window.location),
  2425.                                 'nav': nav,
  2426.                                 'cfg': Cfg,
  2427.                                 'sSpells': spells.list.split('\n'),
  2428.                                 'oSpells': sesStorage['de-spells-' + brd + TNum],
  2429.                                 'perf': new Logger().get()
  2430.                         }, function (key, value) {
  2431.                                 if (key in defaultCfg) {
  2432.                                         if (value === defaultCfg[key] || key === 'nameValue' || key === 'passwValue') {
  2433.                                                 return void 0;
  2434.                                         }
  2435.                                 }
  2436.                                 return key === 'stats' ? void 0 : value;
  2437.                         }, '\t');
  2438.                 })
  2439.         ]);
  2440. }
  2441.  
  2442. function addEditButton(name, getDataFn) {
  2443.         return $btn(Lng.edit[lang], Lng.editInTxt[lang], function (getData) {
  2444.                 getData(function (val, isJSON, saveFn) {
  2445.                         var el, ta = $new('textarea', {
  2446.                                 'class': 'de-editor',
  2447.                                 'value': isJSON ? JSON.stringify(val, null, '\t') : val
  2448.                         }, null);
  2449.                         $alert('', 'edit-' + name, false);
  2450.                         el = $c('de-alert-msg', $id('de-alert-edit-' + name));
  2451.                         el.appendChild($txt(Lng.editor[name][lang]));
  2452.                         el.appendChild(ta);
  2453.                         el.appendChild($btn(Lng.save[lang], Lng.saveChanges[lang], isJSON ? function (fun) {
  2454.                                 var data;
  2455.                                 try {
  2456.                                         data = JSON.parse(this.value.trim().replace(/[\n\r\t]/g, '') || '{}');
  2457.                                 } finally {
  2458.                                         if (data) {
  2459.                                                 fun(data);
  2460.                                                 closeAlert($id('de-alert-edit-' + name));
  2461.                                                 closeAlert($id('de-alert-err-invaliddata'));
  2462.                                         } else {
  2463.                                                 $alert(Lng.invalidData[lang], 'err-invaliddata', false);
  2464.                                         }
  2465.                                 }
  2466.                         }.bind(ta, saveFn) : saveFn.bind(ta)));
  2467.                 });
  2468.         }.bind(null, getDataFn));
  2469. }
  2470.  
  2471. function addSettings(Set, id) {
  2472.         Set.appendChild($New('div', {'class': aib.cReply}, [
  2473.                 $new('div', {'class': 'de-cfg-head', 'text': 'Dollchan Extension Tools'}, null),
  2474.                 $New('div', {'id': 'de-cfg-bar'}, [
  2475.                         cfgTab('filters'),
  2476.                         cfgTab('posts'),
  2477.                         cfgTab('images'),
  2478.                         cfgTab('links'),
  2479.                         $if(pr.form || pr.oeForm, cfgTab('form')),
  2480.                         cfgTab('common'),
  2481.                         cfgTab('info')
  2482.                 ]),
  2483.                 $New('div', {'id': 'de-cfg-btns'}, [
  2484.                         optSel('language', false, function () {
  2485.                                 saveCfg('language', lang = this.selectedIndex);
  2486.                                 $del($id('de1-main'));
  2487.                                 $del($id('de-css'));
  2488.                                 $del($id('de-css-dynamic'));
  2489.                                 scriptCSS();
  2490.                                 addPanel();
  2491.                                 toggleContent('cfg', false);
  2492.                         }),
  2493.                         $New('div', {'style': 'float: right;'}, [
  2494.                                 addEditButton('cfg', function (Fn) {
  2495.                                         Fn(Cfg, true, function (data) {
  2496.                                                 saveComCfg(aib.dm, data);
  2497.                                                 window.location.reload();
  2498.                                         });
  2499.                                 }),
  2500.                                 $if(nav.isGlobal, $btn(Lng.load[lang], Lng.loadGlobal[lang], function () {
  2501.                                         if (('global' in comCfg) && !$isEmpty(comCfg.global)) {
  2502.                                                 saveComCfg(aib.dm, null);
  2503.                                                 window.location.reload();
  2504.                                         } else {
  2505.                                                 $alert(Lng.noGlobalCfg[lang], 'err-noglobalcfg', false);
  2506.                                         }
  2507.                                 })),
  2508.                                 $if(nav.isGlobal, $btn(Lng.save[lang], Lng.saveGlobal[lang], function () {
  2509.                                         var i, obj = {},
  2510.                                                 com = comCfg[aib.dm];
  2511.                                         for (i in com) {
  2512.                                                 if (com[i] !== defaultCfg[i] && i !== 'stats') {
  2513.                                                         obj[i] = com[i];
  2514.                                                 }
  2515.                                         }
  2516.                                         saveComCfg('global', obj);
  2517.                                         toggleContent('cfg', true);
  2518.                                 })),
  2519.                                 $btn(Lng.reset[lang], Lng.resetCfg[lang], function () {
  2520.                                         if (confirm(Lng.conReset[lang])) {
  2521.                                                 delStored('DESU_Config');
  2522.                                                 delStored('DESU_Favorites');
  2523.                                                 delStored('DESU_Posts_' + aib.dm);
  2524.                                                 delStored('DESU_Threads_' + aib.dm);
  2525.                                                 delStored('DESU_keys');
  2526.                                                 window.location.reload();
  2527.                                         }
  2528.                                 })
  2529.                         ]),
  2530.                         $new('div', {'style': 'clear: both;'}, null)
  2531.                 ])
  2532.         ]));
  2533.         $q('.de-cfg-tab[info="' + (id || 'filters') + '"]', Set).click();
  2534. }
  2535.  
  2536.  
  2537. // MENU & POPUP
  2538. // ===========================================================================================================
  2539.  
  2540. function closeAlert(el) {
  2541.         if (el) {
  2542.                 el.closeTimeout = null;
  2543.                 if (Cfg.animation) {
  2544.                         nav.animEvent(el, function (node) {
  2545.                                 var p = node && node.parentNode;
  2546.                                 if (p) {
  2547.                                         p.removeChild(node);
  2548.                                 }
  2549.                         });
  2550.                         el.classList.add('de-close');
  2551.                 } else {
  2552.                         $del(el);
  2553.                 }
  2554.         }
  2555. }
  2556.  
  2557. function $alert(txt, id, wait) {
  2558.         var node, el = $id('de-alert-' + id),
  2559.                 cBtn = 'de-alert-btn' + (wait ? ' de-wait' : ''),
  2560.                 tBtn = wait ? '' : '\u2716 ';
  2561.         if (el) {
  2562.                 $t('div', el).innerHTML = txt.trim();
  2563.                 node = $t('span', el);
  2564.                 node.className = cBtn;
  2565.                 node.textContent = tBtn;
  2566.                 clearTimeout(el.closeTimeout);
  2567.                 if (!wait && Cfg.animation) {
  2568.                         nav.animEvent(el, function (node) {
  2569.                                 node.classList.remove('de-blink');
  2570.                         });
  2571.                         el.classList.add('de-blink');
  2572.                 }
  2573.         } else {
  2574.                 el = $id('de-alert').appendChild($New('div', {'class': aib.cReply, 'id': 'de-alert-' + id}, [
  2575.                         $new('span', {'class': cBtn, 'text': tBtn}, {'click': function () {
  2576.                                 closeAlert(this.parentNode);
  2577.                         }}),
  2578.                         $add('<div class="de-alert-msg">' + txt.trim() + '</div>')
  2579.                 ]));
  2580.                 if (Cfg.animation) {
  2581.                         nav.animEvent(el, function (node) {
  2582.                                 node.classList.remove('de-open');
  2583.                         });
  2584.                         el.classList.add('de-open');
  2585.                 }
  2586.         }
  2587.         if (Cfg.closePopups && !wait && !id.contains('help') && !id.contains('edit')) {
  2588.                 el.closeTimeout = setTimeout(closeAlert, 4e3, el);
  2589.         }
  2590. }
  2591.  
  2592. function showMenu(el, html, inPanel, onclick) {
  2593.         var y, pos, menu, cr = el.getBoundingClientRect();
  2594.         if (Cfg.attachPanel && inPanel) {
  2595.                 pos = 'fixed';
  2596.                 y = 'bottom: 25';
  2597.         } else {
  2598.                 pos = 'absolute';
  2599.                 y = 'top: ' + (window.pageYOffset + cr.bottom);
  2600.         }
  2601.         doc.body.insertAdjacentHTML('beforeend', '<div class="' + aib.cReply + ' de-menu" style="position: ' +
  2602.                 pos + '; right: ' + (doc.documentElement.clientWidth - cr.right - window.pageXOffset) +
  2603.                 'px; ' + y + 'px;">' + html + '</div>');
  2604.         menu = doc.body.lastChild;
  2605.         menu.addEventListener('mouseover', function (e) {
  2606.                 clearTimeout(e.currentTarget.odelay);
  2607.         }, true);
  2608.         menu.addEventListener('mouseout', removeMenu, true);
  2609.         menu.addEventListener('click', function (e) {
  2610.                 var el = e.target;
  2611.                 if (el.className === 'de-menu-item') {
  2612.                         setTimeout(this, 10, el);
  2613.                         do {
  2614.                                 el = el.parentElement;
  2615.                         } while (!el.classList.contains('de-menu'));
  2616.                         $del(el);
  2617.                 }
  2618.         }.bind(onclick), false);
  2619. }
  2620.  
  2621. function addMenu(e) {
  2622.         e.target.odelay = setTimeout(function (el) {
  2623.                 switch (el.id) {
  2624.                 case 'de-btn-addspell': addSpellMenu(el); return;
  2625.                 case 'de-btn-refresh': addAjaxPagesMenu(el); return;
  2626.                 case 'de-btn-savethr': addSaveThreadMenu(el); return;
  2627.                 case 'de-btn-audio-off': addAudioNotifMenu(el); return;
  2628.                 }
  2629.         }, Cfg.linksOver, e.target);
  2630. }
  2631.  
  2632. function removeMenu(e) {
  2633.         var el = $c('de-menu', doc),
  2634.                 rt = e.relatedTarget;
  2635.         clearTimeout(e.target.odelay);
  2636.         if (el && (!rt || (rt !== el && !el.contains(rt)))) {
  2637.                 el.odelay = setTimeout($del, 75, el);
  2638.         }
  2639. }
  2640.  
  2641. function addSpellMenu(el) {
  2642.         showMenu(el, '<div style="display: inline-block; border-right: 1px solid grey;">' +
  2643.                 '<span class="de-menu-item">' + ('#words,#exp,#exph,#imgn,#ihash,#subj,#name,#trip,#img,<br>')
  2644.                         .split(',').join('</span><span class="de-menu-item">') +
  2645.                 '</span></div><div style="display: inline-block;"><span class="de-menu-item">' +
  2646.                 ('#sage,#op,#tlen,#all,#video,#vauthor,#num,#wipe,#rep,#outrep')
  2647.                         .split(',').join('</span><span class="de-menu-item">') + '</span></div>', false,
  2648.         function (el) {
  2649.                 var exp = el.textContent,
  2650.                         idx = Spells.names.indexOf(exp.substr(1));
  2651.                 $txtInsert($id('de-spell-edit'), exp + (
  2652.                         TNum && exp !== '#op' && exp !== '#rep' && exp !== '#outrep' ? '[' + brd + ',' + TNum + ']' : ''
  2653.                 ) + (Spells.needArg[idx] ? '(' : ''));
  2654.         });
  2655. }
  2656.  
  2657. function addAjaxPagesMenu(el) {
  2658.         showMenu(el, '<span class="de-menu-item">' +
  2659.                 Lng.selAjaxPages[lang].join('</span><span class="de-menu-item">') + '</span>', true,
  2660.         function (el) {
  2661.                 loadPages(aProto.indexOf.call(el.parentNode.children, el) + 1);
  2662.         });
  2663. }
  2664.  
  2665. function addSaveThreadMenu(el) {
  2666.         showMenu(el, '<span class="de-menu-item">' +
  2667.                 Lng.selSaveThr[lang].join('</span><span class="de-menu-item">') + '</span>', true,
  2668.         function (el) {
  2669.                 if (!$id('de-alert-savethr')) {
  2670.                         var imgOnly = !!aProto.indexOf.call(el.parentNode.children, el);
  2671.                         if (Images_.preloading) {
  2672.                                 $alert(Lng.loading[lang], 'savethr', true);
  2673.                                 Images_.afterpreload = loadDocFiles.bind(null, imgOnly);
  2674.                                 Images_.progressId = 'savethr';
  2675.                         } else {
  2676.                                 loadDocFiles(imgOnly);
  2677.                         }
  2678.                 }
  2679.         });
  2680. }
  2681.  
  2682. function addAudioNotifMenu(el) {
  2683.         showMenu(el, '<span class="de-menu-item">' +
  2684.                 Lng.selAudioNotif[lang].join('</span><span class="de-menu-item">') + '</span>', true,
  2685.         function (el) {
  2686.                 var i = aProto.indexOf.call(el.parentNode.children, el);
  2687.                 updater.enable();
  2688.                 updater.toggleAudio(i === 0 ? 3e4 : i === 1 ? 6e4 : i === 2 ? 12e4 : 3e5);
  2689.                 $id('de-btn-audio-off').id = 'de-btn-audio-on';
  2690.         });
  2691. }
  2692.  
  2693.  
  2694. // HOTKEYS
  2695. // ===========================================================================================================
  2696.  
  2697. function HotKeys() {
  2698.         HotKeys.readKeys(this._init.bind(this));
  2699. }
  2700. HotKeys.version = 6;
  2701. HotKeys.readKeys = function (Fn) {
  2702.         getStored('DESU_keys', function (str) {
  2703.                 var tKeys, keys;
  2704.                 if (!str) {
  2705.                         this(HotKeys.getDefaultKeys());
  2706.                         return;
  2707.                 }
  2708.                 try {
  2709.                         keys = JSON.parse(str);
  2710.                 } finally {
  2711.                         if (!keys) {
  2712.                                 this(HotKeys.getDefaultKeys());
  2713.                                 return;
  2714.                         }
  2715.                         if (keys[0] !== HotKeys.version) {
  2716.                                 tKeys = HotKeys.getDefaultKeys();
  2717.                                 switch (keys[0]) {
  2718.                                 case 1:
  2719.                                         keys[2][11] = tKeys[2][11];
  2720.                                         keys[4] = tKeys[4];
  2721.                                 case 2:
  2722.                                         keys[2][12] = tKeys[2][12];
  2723.                                         keys[2][13] = tKeys[2][13];
  2724.                                         keys[2][14] = tKeys[2][14];
  2725.                                         keys[2][15] = tKeys[2][15];
  2726.                                         keys[2][16] = tKeys[2][16];
  2727.                                 case 3:
  2728.                                         keys[2][17] = keys[3][3];
  2729.                                         keys[3][3] = keys[3].splice(4, 1)[0];
  2730.                                 case 4:
  2731.                                 case 5:
  2732.                                         if (keys[2][18]) {
  2733.                                                 delete keys[2][18];
  2734.                                         }
  2735.                                 }
  2736.                                 keys[0] = HotKeys.version;
  2737.                                 setStored('DESU_keys', JSON.stringify(keys));
  2738.                         }
  2739.                         if (keys[1] ^ !!nav.Firefox) {
  2740.                                 var mapFunc = nav.Firefox ? function mapFuncFF(key) {
  2741.                                         switch (key) {
  2742.                                         case 189: return 173;
  2743.                                         case 187: return 61;
  2744.                                         case 186: return 59;
  2745.                                         default: return key;
  2746.                                         }
  2747.                                 } : function mapFuncNonFF(key) {
  2748.                                         switch (key) {
  2749.                                         case 173: return 189;
  2750.                                         case 61: return 187;
  2751.                                         case 59: return 186;
  2752.                                         default: return key;
  2753.                                         }
  2754.                                 };
  2755.                                 keys[1] = !!nav.Firefox;
  2756.                                 keys[2] = keys[2].map(mapFunc);
  2757.                                 keys[3] = keys[3].map(mapFunc);
  2758.                                 setStored('DESU_keys', JSON.stringify(keys));
  2759.                         }
  2760.                         this(keys);
  2761.                 }
  2762.         }.bind(Fn));
  2763. };
  2764. HotKeys.getDefaultKeys = function () {
  2765.         var isFirefox = !!nav.Firefox;
  2766.         var globKeys = [
  2767.                 /* One post/thread above      */ 0x004B /* = K          */,
  2768.                 /* One post/thread below      */ 0x004A /* = J          */,
  2769.                 /* Reply or create thread     */ 0x0052 /* = R          */,
  2770.                 /* Hide selected thread/post  */ 0x0048 /* = H          */,
  2771.                 /* Open previous page/picture */ 0x1025 /* = Ctrl+Left  */,
  2772.                 /* Send post (txt)            */ 0xC00D /* = Alt+Enter  */,
  2773.                 /* Open/close favorites posts */ 0x4046 /* = Alt+F      */,
  2774.                 /* Open/close hidden posts    */ 0x4048 /* = Alt+H      */,
  2775.                 /* Open/close panel           */ 0x0050 /* = P          */,
  2776.                 /* Mask/unmask images         */ 0x0042 /* = B          */,
  2777.                 /* Open/close settings        */ 0x4053 /* = Alt+S      */,
  2778.                 /* Expand current image       */ 0x0049 /* = I          */,
  2779.                 /* Bold text                  */ 0xC042 /* = Alt+B      */,
  2780.                 /* Italic text                */ 0xC049 /* = Alt+I      */,
  2781.                 /* Strike text                */ 0xC054 /* = Alt+T      */,
  2782.                 /* Spoiler text               */ 0xC050 /* = Alt+P      */,
  2783.                 /* Code text                  */ 0xC043 /* = Alt+C      */,
  2784.                 /* Open next page/picture     */ 0x1027 /* = Ctrl+Right */
  2785.         ];
  2786.         var nonThrKeys = [
  2787.                 /* One post above */ 0x004D /* = M */,
  2788.                 /* One post below */ 0x004E /* = N */,
  2789.                 /* Open thread    */ 0x0056 /* = V */,
  2790.                 /* Expand thread  */ 0x0045 /* = E */
  2791.         ];
  2792.         var thrKeys = [
  2793.                 /* Update thread  */ 0x0055 /* = U */
  2794.         ];
  2795.         return [HotKeys.version, isFirefox, globKeys, nonThrKeys, thrKeys];
  2796. };
  2797. HotKeys.prototype = {
  2798.         cPost: null,
  2799.         enabled: false,
  2800.         gKeys: null,
  2801.         lastPage: 0,
  2802.         lastPageOffset: 0,
  2803.         ntKeys: null,
  2804.         paused: false,
  2805.         tKeys: null,
  2806.         clear: function (lastPage) {
  2807.                 this.cPost = null;
  2808.                 this.lastPage = lastPage;
  2809.                 this.lastPageOffset = 0;
  2810.         },
  2811.         disable: function () {
  2812.                 if (this.enabled) {
  2813.                         if (this.cPost) {
  2814.                                 this.cPost.unselect();
  2815.                         }
  2816.                         doc.removeEventListener('keydown', this, true);
  2817.                         this.enabled = false;
  2818.                 }
  2819.         },
  2820.         enable: function () {
  2821.                 if (!this.enabled) {
  2822.                         this.clear(pageNum);
  2823.                         doc.addEventListener('keydown', this, true);
  2824.                         this.enabled = true;
  2825.                 }
  2826.         },
  2827.         handleEvent: function (e) {
  2828.                 if (this.paused) {
  2829.                         return;
  2830.                 }
  2831.                 var temp, post, scrollToThread, globIdx, idx, curTh = e.target.tagName,
  2832.                         kc = e.keyCode | (e.ctrlKey ? 0x1000 : 0) | (e.shiftKey ? 0x2000 : 0) |
  2833.                                 (e.altKey ? 0x4000 : 0) | (curTh === 'TEXTAREA' ||
  2834.                                 (curTh === 'INPUT' && e.target.type === 'text') ? 0x8000 : 0);
  2835.                 if (kc === 0x74 || kc === 0x8074) { // F5
  2836.                         if (TNum || $id('de-alert-load-pages')) {
  2837.                                 return;
  2838.                         }
  2839.                         if (Attachment.viewer) {
  2840.                                 Attachment.viewer.close(null);
  2841.                                 Attachment.viewer = null;
  2842.                         }
  2843.                         loadPages(+Cfg.loadPages);
  2844.                 } else if (kc === 0x1B) { // ESC
  2845.                         if (Attachment.viewer) {
  2846.                                 Attachment.viewer.close(null);
  2847.                                 Attachment.viewer = null;
  2848.                                 return;
  2849.                         }
  2850.                         if (this.cPost) {
  2851.                                 this.cPost.unselect();
  2852.                                 this.cPost = null;
  2853.                         }
  2854.                         if (TNum) {
  2855.                                 firstThr.clearPostsMarks();
  2856.                         }
  2857.                         this.lastPageOffset = 0;
  2858.                 } else if (kc === 0x801B) { // ESC (txt)
  2859.                         e.target.blur();
  2860.                 } else {
  2861.                         globIdx = this.gKeys.indexOf(kc);
  2862.                         switch (globIdx) {
  2863.                         case 2: // Quick reply
  2864.                                 if (pr.form) {
  2865.                                         post = this.cPost || this._getFirstVisPost(false, true) || firstThr.op;
  2866.                                         this.cPost = post;
  2867.                                         pr.showQuickReply(post, post.num, true, false);
  2868.                                         post.select();
  2869.                                 }
  2870.                                 break;
  2871.                         case 3: // Hide selected thread/post
  2872.                                 post = this._getFirstVisPost(false, true) || this._getNextVisPost(null, true, false);
  2873.                                 if (post) {
  2874.                                         post.toggleUserVisib();
  2875.                                         this._scroll(post, false, post.isOp);
  2876.                                 }
  2877.                                 break;
  2878.                         case 4: // Open previous page/picture
  2879.                                 if (Attachment.viewer) {
  2880.                                         Attachment.viewer.navigate(false);
  2881.                                 } else if (TNum || pageNum !== aib.firstPage) {
  2882.                                         window.location.pathname = aib.getPageUrl(brd, TNum ? 0 : pageNum - 1);
  2883.                                 }
  2884.                                 break;
  2885.                         case 5: // Send post (txt)
  2886.                                 if (e.target !== pr.txta && e.target !== pr.cap) {
  2887.                                         return;
  2888.                                 }
  2889.                                 pr.subm.click();
  2890.                                 break;
  2891.                         case 6: // Open/close favorites posts
  2892.                                 toggleContent('fav', false);
  2893.                                 break;
  2894.                         case 7: // Open/close hidden posts
  2895.                                 toggleContent('hid', false);
  2896.                                 break;
  2897.                         case 8: // Open/close panel
  2898.                                 $disp($id('de-panel').lastChild);
  2899.                                 break;
  2900.                         case 9: // Mask/unmask images
  2901.                                 toggleCfg('maskImgs');
  2902.                                 updateCSS();
  2903.                                 break;
  2904.                         case 10: // Open/close settings
  2905.                                 toggleContent('cfg', false);
  2906.                                 break;
  2907.                         case 11: // Expand current image
  2908.                                 post = this._getFirstVisPost(false, true) || this._getNextVisPost(null, true, false);
  2909.                                 if (post) {
  2910.                                         post.toggleImages(!post.imagesExpanded);
  2911.                                 }
  2912.                                 break;
  2913.                         case 12: // Bold text (txt)
  2914.                                 if (e.target !== pr.txta) {
  2915.                                         return;
  2916.                                 }
  2917.                                 $id('de-btn-bold').click();
  2918.                                 break;
  2919.                         case 13: // Italic text (txt)
  2920.                                 if (e.target !== pr.txta) {
  2921.                                         return;
  2922.                                 }
  2923.                                 $id('de-btn-italic').click();
  2924.                                 break;
  2925.                         case 14: // Strike text (txt)
  2926.                                 if (e.target !== pr.txta) {
  2927.                                         return;
  2928.                                 }
  2929.                                 $id('de-btn-strike').click();
  2930.                                 break;
  2931.                         case 15: // Spoiler text (txt)
  2932.                                 if (e.target !== pr.txta) {
  2933.                                         return;
  2934.                                 }
  2935.                                 $id('de-btn-spoil').click();
  2936.                                 break;
  2937.                         case 16: // Code text (txt)
  2938.                                 if (e.target !== pr.txta) {
  2939.                                         return;
  2940.                                 }
  2941.                                 $id('de-btn-code').click();
  2942.                                 break;
  2943.                         case 17: // Open next page/picture
  2944.                                 if (Attachment.viewer) {
  2945.                                         Attachment.viewer.navigate(true);
  2946.                                 } else if (!TNum && this.lastPage !== aib.lastPage) {
  2947.                                         window.location.pathname = aib.getPageUrl(brd, this.lastPage + 1);
  2948.                                 }
  2949.                                 break;
  2950.                         case -1:
  2951.                                 if (TNum) {
  2952.                                         idx = this.tKeys.indexOf(kc);
  2953.                                         if (idx === 0) { // Update thread
  2954.                                                 Thread.loadNewPosts(null);
  2955.                                                 break;
  2956.                                         }
  2957.                                         return;
  2958.                                 }
  2959.                                 idx = this.ntKeys.indexOf(kc);
  2960.                                 if (idx === -1) {
  2961.                                         return;
  2962.                                 } else if (idx === 2) { // Open thread
  2963.                                         post = this._getFirstVisPost(false, true) || this._getNextVisPost(null, true, false);
  2964.                                         if (post) {
  2965.                                                 if (nav.Firefox) {
  2966.                                                         GM_openInTab(aib.getThrdUrl(brd, post.tNum), false, true);
  2967.                                                 } else {
  2968.                                                         window.open(aib.getThrdUrl(brd, post.tNum), '_blank');
  2969.                                                 }
  2970.                                         }
  2971.                                         break;
  2972.                                 } else if (idx === 3) { // Expand/collapse thread
  2973.                                         post = this._getFirstVisPost(false, true) || this._getNextVisPost(null, true, false);
  2974.                                         if (post) {
  2975.                                                 if (post.thr.loadedOnce && post.thr.op.next.count === 1) {
  2976.                                                         temp = post.thr.nextNotHidden;
  2977.                                                         post.thr.load(visPosts, !!temp, null);
  2978.                                                         post = (temp || post.thr).op;
  2979.                                                 } else {
  2980.                                                         post.thr.load(1, false, null);
  2981.                                                         post = post.thr.op;
  2982.                                                 }
  2983.                                                 scrollTo(0, pageYOffset + post.topCoord);
  2984.                                                 if (this.cPost && this.cPost !== post) {
  2985.                                                         this.cPost.unselect();
  2986.                                                         this.cPost = post;
  2987.                                                 }
  2988.                                         }
  2989.                                         break;
  2990.                                 }
  2991.                         default:
  2992.                                 scrollToThread = !TNum && (globIdx === 0 || globIdx === 1);
  2993.                                 this._scroll(this._getFirstVisPost(scrollToThread, false), globIdx === 0 || idx === 0,
  2994.                                         scrollToThread);
  2995.                         }
  2996.                 }
  2997.                 e.stopPropagation();
  2998.                 $pd(e);
  2999.         },
  3000.         pause: function () {
  3001.                 this.paused = true;
  3002.         },
  3003.         resume: function (keys) {
  3004.                 this.gKeys = keys[2];
  3005.                 this.ntKeys = keys[3];
  3006.                 this.tKeys = keys[4];
  3007.                 this.paused = false;
  3008.         },
  3009.  
  3010.         _getFirstVisPost: function (getThread, getFull) {
  3011.                 var post, tPost;
  3012.                 if (this.lastPageOffset !== pageYOffset) {
  3013.                         post = getThread ? firstThr : firstThr.op;
  3014.                         while (post.topCoord < 1) {
  3015.                                 tPost = post.next;
  3016.                                 if (!tPost) {
  3017.                                         break;
  3018.                                 }
  3019.                                 post = tPost;
  3020.                         }
  3021.                         if (this.cPost) {
  3022.                                 this.cPost.unselect();
  3023.                         }
  3024.                         this.cPost = getThread ? getFull ? post.op : post.op.prev : getFull ? post : post.prev;
  3025.                         this.lastPageOffset = pageYOffset;
  3026.                 }
  3027.                 return this.cPost;
  3028.         },
  3029.         _getNextVisPost: function (cPost, isOp, toUp) {
  3030.                 var thr;
  3031.                 if (isOp) {
  3032.                         thr = cPost ? toUp ? cPost.thr.prevNotHidden : cPost.thr.nextNotHidden :
  3033.                                 firstThr.hidden ? firstThr.nextNotHidden : firstThr;
  3034.                         return thr ? thr.op : null;
  3035.                 }
  3036.                 return cPost ? cPost.getAdjacentVisPost(toUp) : firstThr.hidden ||
  3037.                         firstThr.op.hidden ? firstThr.op.getAdjacentVisPost(toUp) : firstThr.op;
  3038.         },
  3039.         _init: function (keys) {
  3040.                 this.enabled = true;
  3041.                 this.lastPage = pageNum;
  3042.                 this.gKeys = keys[2];
  3043.                 this.ntKeys = keys[3];
  3044.                 this.tKeys = keys[4];
  3045.                 doc.addEventListener('keydown', this, true);
  3046.         },
  3047.         _scroll: function (post, toUp, toThread) {
  3048.                 var next = this._getNextVisPost(post, toThread, toUp);
  3049.                 if (!next) {
  3050.                         if (!TNum && (toUp ? pageNum > aib.firstPage : this.lastPage < aib.lastPage)) {
  3051.                                 window.location.pathname = aib.getPageUrl(brd, toUp ? pageNum - 1 : this.lastPage + 1);
  3052.                         }
  3053.                         return;
  3054.                 }
  3055.                 if (post) {
  3056.                         post.unselect();
  3057.                 }
  3058.                 if (toThread) {
  3059.                         next.el.scrollIntoView();
  3060.                 } else {
  3061.                         scrollTo(0, pageYOffset + next.el.getBoundingClientRect().top -
  3062.                                 Post.sizing.wHeight / 2 + next.el.clientHeight / 2);
  3063.                 }
  3064.                 this.lastPageOffset = pageYOffset;
  3065.                 next.select();
  3066.                 this.cPost = next;
  3067.         }
  3068. }
  3069.  
  3070. function KeyEditListener(alertEl, keys, allKeys) {
  3071.         var j, k, i, len, aInputs = aProto.slice.call($C('de-input-key', alertEl));
  3072.         for (i = 0, len = allKeys.length; i < len; ++i) {
  3073.                 k = allKeys[i];
  3074.                 if (k !== 0) {
  3075.                         for (j = i + 1; j < len; ++j) {
  3076.                                 if (k === allKeys[j]) {
  3077.                                         aInputs[i].classList.add('de-error-key');
  3078.                                         aInputs[j].classList.add('de-error-key');
  3079.                                         break;
  3080.                                 }
  3081.                         }
  3082.                 }
  3083.         }
  3084.         this.aEl = alertEl;
  3085.         this.keys = keys;
  3086.         this.initKeys = JSON.parse(JSON.stringify(keys));
  3087.         this.allKeys = allKeys;
  3088.         this.allInputs = aInputs;
  3089.         this.errCount = $C('de-error-key', alertEl).length;
  3090.         if (this.errCount !== 0) {
  3091.                 this.saveButton.disabled = true;
  3092.         }
  3093. }
  3094. // Browsers have different codes for these keys (see HotKeys.readKeys):
  3095. //     Firefox - '-' - 173, '=' - 61, ';' - 59
  3096. //     Chrome/Opera: '-' - 189, '=' - 187, ';' - 186
  3097. KeyEditListener.keyCodes = ['',,,,,,,,'Backspace','Tab',,,,'Enter',,,'Shift','Ctrl','Alt',
  3098.         /* Pause/Break */,/* Caps Lock */,,,,,,,/* Escape */,,,,,'Space',/* Page Up */,
  3099.         /* Page Down */,/* End */,/* Home */,'←','↑','→','↓',,,,,/* Insert */,/* Delete */,,'0','1','2',
  3100.         '3','4','5','6','7','8','9',,';',,'=',,,,'A','B','C','D','E','F','G','H','I','J','K','L','M',
  3101.         'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',/* Left WIN Key */,/* Right WIN Key */,
  3102.         /* Select key */,,,'Numpad 0','Numpad 1','Numpad 2','Numpad 3','Numpad 4','Numpad 5','Numpad 6',
  3103.         'Numpad 7','Numpad 8','Numpad 9','Numpad *','Numpad +',,'Numpad -','Numpad .','Numpad /',
  3104.         /* F1 */,/* F2 */,/* F3 */,/* F4 */,/* F5 */,/* F6 */,/* F7 */,/* F8 */,/* F9 */,/* F10 */,
  3105.         /* F11 */,/* F12 */,,,,,,,,,,,,,,,,,,,,,/* Num Lock */,/* Scroll Lock */,,,,,,,,,,,,,,,,,,,,,,,,
  3106.         ,,,,'-',,,,,,,,,,,,,';','=',',','-','.','/','`',,,,,,,,,,,,,,,,,,,,,,,,,,,'[','\\',']','\''
  3107. ];
  3108. KeyEditListener.getStrKey = function (key) {
  3109.         var str = '';
  3110.         if (key & 0x1000) {
  3111.                 str += 'Ctrl+';
  3112.         }
  3113.         if (key & 0x2000) {
  3114.                 str += 'Shift+';
  3115.         }
  3116.         if (key & 0x4000) {
  3117.                 str += 'Alt+';
  3118.         }
  3119.         str += KeyEditListener.keyCodes[key & 0xFFF];
  3120.         return str;
  3121. };
  3122. KeyEditListener.getEditMarkup = function (keys) {
  3123.         var allKeys = [];
  3124.         var html = Lng.hotKeyEdit[lang].join('')
  3125.                 .replace(/%l/g, '<label class="de-block">')
  3126.                 .replace(/%\/l/g, '</label>')
  3127.                 .replace(/%i([2-4])([0-9]+)(t)?/g, function (aKeys, all, id1, id2, isText) {
  3128.                         var key = this[+id1][+id2];
  3129.                         aKeys.push(key);
  3130.                         return '<input class="de-input-key" type="text" de-id1="' + id1 + '" de-id2="' + id2 +
  3131.                                 '" size="26" value="' + KeyEditListener.getStrKey(key) +
  3132.                                 (isText ? '" de-text' : '"' ) + ' readonly></input>';
  3133.                 }.bind(keys, allKeys)) +
  3134.         '<input type="button" id="de-keys-save" value="' + Lng.save[lang] + '"></input>' +
  3135.         '<input type="button" id="de-keys-reset" value="' + Lng.reset[lang] + '"></input>';
  3136.         return [allKeys, html];
  3137. };
  3138. KeyEditListener.setTitle = function (el, idx) {
  3139.         var title = el.getAttribute('de-title');
  3140.         if (hKeys && idx !== -1) {
  3141.                 title += ' [' + KeyEditListener.getStrKey(hKeys.gKeys[idx]) + ']';
  3142.         }
  3143.         el.title = title;
  3144. };
  3145. KeyEditListener.prototype = {
  3146.         cEl: null,
  3147.         cKey: -1,
  3148.         errorInput: false,
  3149.         get saveButton() {
  3150.                 var val = $id('de-keys-save');
  3151.                 Object.defineProperty(this, 'saveButton', { value: val, configurable: true });
  3152.                 return val;
  3153.         },
  3154.         handleEvent: function (e) {
  3155.                 var key, keyStr, keys, str, id, temp, el = e.target;
  3156.                 switch (e.type) {
  3157.                 case 'blur':
  3158.                         if (hKeys && this.errCount === 0) {
  3159.                                 hKeys.resume(this.keys);
  3160.                         }
  3161.                         this.cEl = null;
  3162.                         return;
  3163.                 case 'focus':
  3164.                         if (hKeys) {
  3165.                                 hKeys.pause();
  3166.                         }
  3167.                         this.cEl = el;
  3168.                         return;
  3169.                 case 'click':
  3170.                         if (el.id === 'de-keys-reset') {
  3171.                                 this.keys = HotKeys.getDefaultKeys();
  3172.                                 this.initKeys = HotKeys.getDefaultKeys();
  3173.                                 if (hKeys) {
  3174.                                         hKeys.resume(this.keys);
  3175.                                 }
  3176.                                 temp = KeyEditListener.getEditMarkup(this.keys);
  3177.                                 this.allKeys = temp[0];
  3178.                                 $c('de-alert-msg', this.aEl).innerHTML = temp[1];
  3179.                                 this.allInputs = aProto.slice.call($C('de-input-key', this.aEl));
  3180.                                 this.errCount = 0;
  3181.                                 delete this.saveButton;
  3182.                                 break;
  3183.                         } else if (el.id === 'de-keys-save') {
  3184.                                 keys = this.keys;
  3185.                                 setStored('DESU_keys', JSON.stringify(keys));
  3186.                         } else if (el.className === 'de-alert-btn') {
  3187.                                 keys = this.initKeys;
  3188.                         } else {
  3189.                                 return;
  3190.                         }
  3191.                         if (hKeys) {
  3192.                                 hKeys.resume(keys);
  3193.                         }
  3194.                         closeAlert($id('de-alert-edit-hotkeys'));
  3195.                         break;
  3196.                 case 'keydown':
  3197.                         if (!this.cEl) {
  3198.                                 return;
  3199.                         }
  3200.                         key = e.keyCode;
  3201.                         if (key === 0x1B || key === 0x2E) { // ESC, DEL
  3202.                                 this.cEl.value = '';
  3203.                                 this.cKey = 0;
  3204.                                 this.errorInput = false;
  3205.                                 break;
  3206.                         }
  3207.                         keyStr = KeyEditListener.keyCodes[key];
  3208.                         if (keyStr == null) {
  3209.                                 this.cKey = -1;
  3210.                                 return;
  3211.                         }
  3212.                         str = '';
  3213.                         if (e.ctrlKey) {
  3214.                                 str += 'Ctrl+';
  3215.                         }
  3216.                         if (e.shiftKey) {
  3217.                                 str += 'Shift+';
  3218.                         }
  3219.                         if (e.altKey) {
  3220.                                 str += 'Alt+';
  3221.                         }
  3222.                         if (key === 16 || key === 17 || key === 18) {
  3223.                                 this.errorInput = true;
  3224.                         } else {
  3225.                                 this.cKey = key | (e.ctrlKey ? 0x1000 : 0) | (e.shiftKey ? 0x2000 : 0) |
  3226.                                         (e.altKey ? 0x4000 : 0) | (this.cEl.hasAttribute('de-text') ? 0x8000 : 0);
  3227.                                 this.errorInput = false;
  3228.                                 str += keyStr;
  3229.                         }
  3230.                         this.cEl.value = str;
  3231.                         break;
  3232.                 case 'keyup':
  3233.                         var idx, rIdx, oKey, rEl, isError, el = this.cEl,
  3234.                                 key = this.cKey;
  3235.                         if (!el || key === -1) {
  3236.                                 return;
  3237.                         }
  3238.                         isError = el.classList.contains('de-error-key');
  3239.                         if (!this.errorInput && key !== -1) {
  3240.                                 idx = this.allInputs.indexOf(el);
  3241.                                 oKey = this.allKeys[idx];
  3242.                                 if (oKey === key) {
  3243.                                         this.errorInput = false;
  3244.                                         break;
  3245.                                 }
  3246.                                 rIdx = key === 0 ? -1 : this.allKeys.indexOf(key);
  3247.                                 this.allKeys[idx] = key;
  3248.                                 if (isError) {
  3249.                                         idx = this.allKeys.indexOf(oKey);
  3250.                                         if (idx !== -1 && this.allKeys.indexOf(oKey, idx + 1) === -1) {
  3251.                                                 rEl = this.allInputs[idx];
  3252.                                                 if (rEl.classList.contains('de-error-key')) {
  3253.                                                         this.errCount--;
  3254.                                                         rEl.classList.remove('de-error-key');
  3255.                                                 }
  3256.                                         }
  3257.                                         if (rIdx === -1) {
  3258.                                                 this.errCount--;
  3259.                                                 el.classList.remove('de-error-key');
  3260.                                         }
  3261.                                 }
  3262.                                 if (rIdx === -1) {
  3263.                                         this.keys[+el.getAttribute('de-id1')][+el.getAttribute('de-id2')] = key;
  3264.                                         if (this.errCount === 0) {
  3265.                                                 this.saveButton.disabled = false;
  3266.                                         }
  3267.                                         this.errorInput = false;
  3268.                                         break;
  3269.                                 }
  3270.                                 rEl = this.allInputs[rIdx];
  3271.                                 if (!rEl.classList.contains('de-error-key')) {
  3272.                                         this.errCount++;
  3273.                                         rEl.classList.add('de-error-key');
  3274.                                 }
  3275.                         }
  3276.                         if (!isError) {
  3277.                                 this.errCount++;
  3278.                                 el.classList.add('de-error-key');
  3279.                         }
  3280.                         if (this.errCount !== 0) {
  3281.                                 this.saveButton.disabled = true;
  3282.                         }
  3283.                 }
  3284.                 $pd(e);
  3285.         }
  3286. };
  3287.  
  3288.  
  3289. // CONTENT FEATURES
  3290. // ===========================================================================================================
  3291.  
  3292. function initMessageFunctions() {
  3293.         window.addEventListener('message', function (e) {
  3294.                 var temp, data = e.data.substring(1);
  3295.                 switch (e.data[0]) {
  3296.                 case 'A':
  3297.                         if (data.substr(10, 5) === 'pform') {
  3298.                                 checkUpload($DOM(data.substr(15)));
  3299.                                 $q('iframe[name="de-iframe-pform"]', doc).src = 'about:blank';
  3300.                         } else {
  3301.                                 checkDelete($DOM(data.substr(15)));
  3302.                                 $q('iframe[name="de-iframe-dform"]', doc).src = 'about:blank';
  3303.                         }
  3304.                         return;
  3305.                 case 'B':
  3306.                         closeAlert($id('de-alert-load-favthr'));
  3307.                         $id('de-iframe-fav').style.height = data + 'px';
  3308.                         return;
  3309.                 }
  3310.         }, false);
  3311. }
  3312.  
  3313. function detectImgFile(ab) {
  3314.         var i, j, dat = new Uint8Array(ab),
  3315.                 len = dat.length;
  3316.         /* JPG [ff d8 ff e0] = [яШяа] */
  3317.         if (dat[0] === 0xFF && dat[1] === 0xD8) {
  3318.                 for (i = 0, j = 0; i < len - 1; i++) {
  3319.                         if (dat[i] === 0xFF) {
  3320.                                 /* Built-in JPG */
  3321.                                 if (dat[i + 1] === 0xD8) {
  3322.                                         j++;
  3323.                                 /* JPG end [ff d9] */
  3324.                                 } else if (dat[i + 1] === 0xD9 && --j === 0) {
  3325.                                         i += 2;
  3326.                                         break;
  3327.                                 }
  3328.                         }
  3329.                 }
  3330.         /* PNG [89 50 4e 47] = [‰PNG] */
  3331.         } else if (dat[0] === 0x89 && dat[1] === 0x50) {
  3332.                 for (i = 0; i < len - 7; i++) {
  3333.                         /* PNG end [49 45 4e 44 ae 42 60 82] */
  3334.                         if (dat[i] === 0x49 && dat[i + 1] === 0x45 && dat[i + 2] === 0x4E && dat[i + 3] === 0x44) {
  3335.                                 i += 8;
  3336.                                 break;
  3337.                         }
  3338.                 }
  3339.         } else {
  3340.                 return {};
  3341.         }
  3342.         /* Ignore small files */
  3343.         if (i !== len && len - i > 60) {
  3344.                 for (len = i + 90; i < len; i++) {
  3345.                         /* 7Z [37 7a bc af] = [7zјЇ] */
  3346.                         if (dat[i] === 0x37 && dat[i + 1] === 0x7A && dat[i + 2] === 0xBC) {
  3347.                                 return {'type': 0, 'idx': i, 'data': ab};
  3348.                         /* ZIP [50 4b 03 04] = [PK..] */
  3349.                         } else if (dat[i] === 0x50 && dat[i + 1] === 0x4B && dat[i + 2] === 0x03) {
  3350.                                 return {'type': 1, 'idx': i, 'data': ab};
  3351.                         /* RAR [52 61 72 21] = [Rar!] */
  3352.                         } else if (dat[i] === 0x52 && dat[i + 1] === 0x61 && dat[i + 2] === 0x72) {
  3353.                                 return {'type': 2, 'idx': i, 'data': ab};
  3354.                         /* OGG [4f 67 67 53] = [OggS] */
  3355.                         } else if (dat[i] === 0x4F && dat[i + 1] === 0x67 && dat[i + 2] === 0x67) {
  3356.                                 return {'type': 3, 'idx': i, 'data': ab};
  3357.                         /* MP3 [0x49 0x44 0x33] = [ID3] */
  3358.                         } else if (dat[i] === 0x49 && dat[i + 1] === 0x44 && dat[i + 2] === 0x33) {
  3359.                                 return {'type': 4, 'idx': i, 'data': ab};
  3360.                         }
  3361.                 }
  3362.         }
  3363.         return {};
  3364. }
  3365.  
  3366. function workerQueue(mReqs, wrkFn, errFn) {
  3367.         if (!nav.hasWorker) {
  3368.                 this.run = this._runSync.bind(wrkFn);
  3369.                 return;
  3370.         }
  3371.         this.queue = new $queue(mReqs, this._createWrk.bind(this), null);
  3372.         this.run = this._runWrk;
  3373.         this.wrks = new $workers('self.onmessage = function (e) {\
  3374.                 var info = (' + String(wrkFn) + ')(e.data[1]);\
  3375.                 if (info.data) {\
  3376.                         self.postMessage([e.data[0], info], [info.data]);\
  3377.                 } else {\
  3378.                         self.postMessage([e.data[0], info]);\
  3379.                 }\
  3380.         }', mReqs);
  3381.         this.errFn = errFn;
  3382. }
  3383. workerQueue.prototype = {
  3384.         _runSync: function (data, transferObjs, Fn) {
  3385.                 Fn(this(data));
  3386.         },
  3387.         onMess: function (Fn, e) {
  3388.                 this.queue.end(e.data[0]);
  3389.                 Fn(e.data[1]);
  3390.         },
  3391.         onErr: function (qIdx, e) {
  3392.                 this.queue.end(qIdx);
  3393.                 this.errFn(e);
  3394.         },
  3395.         _runWrk: function (data, transObjs, Fn) {
  3396.                 this.queue.run([data, transObjs, this.onMess.bind(this, Fn)]);
  3397.         },
  3398.         _createWrk: function (qIdx, num, data) {
  3399.                 var w = this.wrks[qIdx];
  3400.                 w.onmessage = data[2];
  3401.                 w.onerror = this.onErr.bind(this, qIdx);
  3402.                 w.postMessage([qIdx, data[0]], data[1]);
  3403.         },
  3404.         clear: function () {
  3405.                 this.wrks && this.wrks.clear();
  3406.                 this.wrks = null;
  3407.         }
  3408. };
  3409.  
  3410. function addImgFileIcon(fName, info) {
  3411.         var app, ext, type = info.type;
  3412.         if (typeof type !== 'undefined') {
  3413.                 if (type === 2) {
  3414.                         app = 'application/x-rar-compressed';
  3415.                         ext = 'rar';
  3416.                 } else if (type === 1) {
  3417.                         app = 'application/zip';
  3418.                         ext = 'zip';
  3419.                 } else if (type === 0) {
  3420.                         app = 'application/x-7z-compressed';
  3421.                         ext = '7z';
  3422.                 } else if (type === 3) {
  3423.                         app = 'audio/ogg';
  3424.                         ext = 'ogg';
  3425.                 } else {
  3426.                         app = 'audio/mpeg';
  3427.                         ext = 'mp3';
  3428.                 }
  3429.                 this.insertAdjacentHTML('afterend', '<a href="' + window.URL.createObjectURL(
  3430.                                 new Blob([new Uint8Array(info.data).subarray(info.idx)], {'type': app})
  3431.                         ) + '" class="de-img-' + (type > 2 ? 'audio' : 'arch') + '" title="' + Lng.downloadFile[lang] +
  3432.                         '" download="' + fName.substring(0, fName.lastIndexOf('.')) + '.' + ext + '">.' + ext + '</a>'
  3433.                 );
  3434.         }
  3435. }
  3436.  
  3437. function downloadImgData(url, Fn) {
  3438.         downloadObjInfo(Fn, {
  3439.                 'method': 'GET',
  3440.                 'url': url,
  3441.                 'onreadystatechange': function onDownloaded(url, e) {
  3442.                         if (e.readyState !== 4) {
  3443.                                 return;
  3444.                         }
  3445.                         var isAb = e.responseType === 'arraybuffer';
  3446.                         if (e.status === 0 && isAb) {
  3447.                                 Fn(new Uint8Array(e.response));
  3448.                         } else if (e.status !== 200) {
  3449.                                 if (e.status === 404 || !url) {
  3450.                                         Fn(null);
  3451.                                 } else {
  3452.                                         downloadObjInfo(Fn, {
  3453.                                                 'method': 'GET',
  3454.                                                 'url': url,
  3455.                                                 'onreadystatechange': onDownloaded.bind(null, null)
  3456.                                         });
  3457.                                 }
  3458.                         } else if (isAb) {
  3459.                                 Fn(new Uint8Array(e.response));
  3460.                         } else {
  3461.                                 for (var len, i = 0, txt = e.responseText, rv = new Uint8Array(len = txt.length); i < len; ++i) {
  3462.                                         rv[i] = txt.charCodeAt(i) & 0xFF;
  3463.                                 }
  3464.                                 Fn(rv);
  3465.                         }
  3466.                 }.bind(null, url)
  3467.         });
  3468. }
  3469.  
  3470. function downloadObjInfo(Fn, obj) {
  3471.         if (aib.fch && nav.Firefox && !obj.url.startsWith('blob')) {
  3472.                 obj.overrideMimeType = 'text/plain; charset=x-user-defined';
  3473.                 GM_xmlhttpRequest(obj);
  3474.         } else {
  3475.                 obj.responseType = 'arraybuffer';
  3476.                 try {
  3477.                         $xhr(obj);
  3478.                 } catch(e) {
  3479.                         Fn(null);
  3480.                 }
  3481.         }
  3482. }
  3483.  
  3484. function preloadImages(post) {
  3485.         if (!Cfg.preLoadImgs && !Cfg.openImgs && !isPreImg) {
  3486.                 return;
  3487.         }
  3488.         var lnk, url, iType, nExp, el, i, len, els, queue, mReqs = post ? 1 : 4, cImg = 1,
  3489.                 rjf = (isPreImg || Cfg.findImgFile) && new workerQueue(mReqs, detectImgFile, function (e) {
  3490.                         console.error("FILE DETECTOR ERROR, line: " + e.lineno + " - " + e.message);
  3491.                 });
  3492.         if (isPreImg || Cfg.preLoadImgs) {
  3493.                 queue = new $queue(mReqs, function (qIdx, num, dat) {
  3494.                         downloadImgData(dat[0], function (idx, data) {
  3495.                                 if (data) {
  3496.                                         var a = this[1],
  3497.                                                 fName = this[0].substring(this[0].lastIndexOf("/") + 1),
  3498.                                                 aEl = $q(aib.qImgLink, aib.getImgWrap(a));
  3499.                                         aEl.setAttribute('download', fName);
  3500.                                         a.href = window.URL.createObjectURL(new Blob([data], {'type': this[2]}));
  3501.                                         a.setAttribute('de-name', fName);
  3502.                                         if (this[2] === 'video/webm') {
  3503.                                                 this[4].setAttribute('de-video', '');
  3504.                                         }
  3505.                                         if (this[3]) {
  3506.                                                 this[4].src = a.href;
  3507.                                         }
  3508.                                         if (rjf) {
  3509.                                                 rjf.run(data.buffer, [data.buffer], addImgFileIcon.bind(aEl, fName));
  3510.                                         }
  3511.                                 }
  3512.                                 queue.end(idx);
  3513.                                 if (Images_.progressId) {
  3514.                                         $alert(Lng.loadImage[lang] + cImg + '/' + len, Images_.progressId, true);
  3515.                                 }
  3516.                                 cImg++;
  3517.                         }.bind(dat, qIdx));
  3518.                 }, function () {
  3519.                         Images_.preloading = false;
  3520.                         if (Images_.afterpreload) {
  3521.                                 Images_.afterpreload();
  3522.                                 Images_.afterpreload = Images_.progressId = null;
  3523.                         }
  3524.                         rjf && rjf.clear();
  3525.                         rjf = queue = cImg = len = null;
  3526.                 });
  3527.                 Images_.preloading = true;
  3528.         }
  3529.         for (i = 0, els = $Q(aib.qThumbImages, post || dForm), len = els.length; i < len; i++) {
  3530.                 if (lnk = $parent(el = els[i], 'A')) {
  3531.                         url = lnk.href;
  3532.                         nExp = !!Cfg.openImgs;
  3533.                         if (/\.gif$/i.test(url)) {
  3534.                                 iType = 'image/gif';
  3535.                         } else {
  3536.                                 if (/\.jpe?g$/i.test(url)) {
  3537.                                         iType = 'image/jpeg';
  3538.                                 } else if (/\.png$/i.test(url)) {
  3539.                                         iType = 'image/png';
  3540.                                 } else if (/\.webm$/i.test(url)) {
  3541.                                         iType = 'video/webm';
  3542.                                         nExp = false;
  3543.                                 } else {
  3544.                                         continue;
  3545.                                 }
  3546.                                 nExp &= !Cfg.openGIFs;
  3547.                         }
  3548.                         if (queue) {
  3549.                                 queue.run([url, lnk, iType, nExp, el]);
  3550.                         } else if (nExp) {
  3551.                                 el.src = url; // !
  3552.                         }
  3553.                 }
  3554.         }
  3555.         queue && queue.complete();
  3556. }
  3557.  
  3558. function getDataFromImg(img) {
  3559.         var cnv = Images_.canvas || (Images_.canvas = doc.createElement('canvas'));
  3560.         cnv.width = img.width;
  3561.         cnv.height = img.height;
  3562.         cnv.getContext('2d').drawImage(img, 0, 0);
  3563.         return new Uint8Array(atob(cnv.toDataURL("image/png").split(',')[1]).split('').map(function (a) {
  3564.                 return a.charCodeAt();
  3565.         }));
  3566. }
  3567.  
  3568. function loadDocFiles(imgOnly) {
  3569.         var els, files, progress, counter, count = 0,
  3570.                 current = 1,
  3571.                 warnings = '',
  3572.                 tar = new $tar(),
  3573.                 dc = imgOnly ? doc : doc.documentElement.cloneNode(true);
  3574.         Images_.queue = new $queue(4, function (qIdx, num, dat) {
  3575.                 downloadImgData(dat[0], function (idx, data) {
  3576.                         var name = this[1].replace(/[\\\/:*?"<>|]/g, '_'), el = this[2];
  3577.                         progress.value = current;
  3578.                         counter.innerHTML = current;
  3579.                         current++;
  3580.                         if (this[3]) {
  3581.                                 if (!data) {
  3582.                                         warnings += '<br>' + Lng.cantLoad[lang] + '<a href="' + this[0] + '">' +
  3583.                                                 this[0] + '</a><br>' + Lng.willSavePview[lang];
  3584.                                         $alert(Lng.loadErrors[lang] + warnings, 'floadwarn', false);
  3585.                                         name = 'thumb-' + name.replace(/\.[a-z]+$/, '.png');
  3586.                                         data = getDataFromImg(this[2]);
  3587.                                 }
  3588.                                 if (!imgOnly) {
  3589.                                         el.classList.add('de-thumb');
  3590.                                         el.src = this[3].href =
  3591.                                                 $q(aib.qImgLink, aib.getImgWrap(this[3])).href =
  3592.                                                 name = 'images/' + name;
  3593.                                 }
  3594.                                 tar.addFile(name, data);
  3595.                         } else if (data && data.length > 0) {
  3596.                                 tar.addFile(el.href = el.src = 'data/' + name, data);
  3597.                         } else {
  3598.                                 $del(el);
  3599.                         }
  3600.                         Images_.queue.end(idx);
  3601.                 }.bind(dat, qIdx));
  3602.         }, function () {
  3603.                 var u, a, dt, name = aib.dm + '-' + brd.replace(/[\\\/:*?"<>|]/g, '') + '-' + TNum;
  3604.                 if (!imgOnly) {
  3605.                         dt = doc.doctype;
  3606.                         $t('head', dc).insertAdjacentHTML('beforeend',
  3607.                                 '<script type="text/javascript" src="data/dollscript.js"></script>');
  3608.                         tar.addString('data/dollscript.js', '(' + String(de_main_func) + ')(null, true);');
  3609.                         tar.addString(
  3610.                                 name + '.html', '<!DOCTYPE ' + dt.name +
  3611.                                 (dt.publicId ? ' PUBLIC "' + dt.publicId + '"' : dt.systemId ? ' SYSTEM' : '') +
  3612.                                 (dt.systemId ? ' "' + dt.systemId + '"' : '') + '>' + dc.outerHTML
  3613.                         );
  3614.                 }
  3615.                 u = window.URL.createObjectURL(tar.get());
  3616.                 a = $new('a', {'href': u, 'download': name + (imgOnly ? '-images.tar' : '.tar')}, null);
  3617.                 doc.body.appendChild(a);
  3618.                 a.click();
  3619.                 setTimeout(function (el, url) {
  3620.                         window.URL.revokeObjectURL(url);
  3621.                         $del(el);
  3622.                 }, 0, a, u);
  3623.                 $del($id('de-alert-filesload'));
  3624.                 Images_.queue = tar = warnings = count = current = imgOnly = progress = counter = null;
  3625.         });
  3626.         els = aProto.slice.call($Q(aib.qThumbImages, $q('[de-form]', dc)));
  3627.         count += els.length;
  3628.         els.forEach(function (el) {
  3629.                 var lnk, url;
  3630.                 if (lnk = $parent(el, 'A')) {
  3631.                         url = lnk.href;
  3632.                         if (aib.tiny) {
  3633.                                 url = url.replace(/^.*?\?v=|&.*?$/g, '');
  3634.                         }
  3635.                         Images_.queue.run([url, lnk.getAttribute('de-name') ||
  3636.                                 url.substring(url.lastIndexOf("/") + 1), el, lnk]);
  3637.                 }
  3638.         });
  3639.         if (!imgOnly) {
  3640.                 files = [];
  3641.                 $each($Q('#de1-main, .de-parea, .de-post-btns, #de-qarea, .de-refmap, #de-updater-div, .de-video-obj,' +
  3642.                         ' link[rel="alternate stylesheet"], script, ' + aib.qPostForm, dc), $del);
  3643.                 $each($T('a', dc), function (el) {
  3644.                         var num, tc = el.textContent;
  3645.                         if (tc[0] === '>' && tc[1] === '>' && (num = +tc.substr(2)) && (num in pByNum)) {
  3646.                                 el.href = aib.anchor + num;
  3647.                         } else {
  3648.                                 el.href = getAbsLink(el.href);
  3649.                         }
  3650.                         if (!el.classList.contains('de-preflink')) {
  3651.                                 el.className = 'de-preflink ' + el.className;
  3652.                         }
  3653.                 });
  3654.                 $each($Q('.' + aib.cRPost, dc), function (post, i) {
  3655.                         post.setAttribute('de-num', i === 0 ? TNum : aib.getPNum(post));
  3656.                 });
  3657.                 $each($Q('link, *[src]', dc), function (el) {
  3658.                         if (els.indexOf(el) !== -1) {
  3659.                                 return;
  3660.                         }
  3661.                         var temp, i, ext, name, url = el.tagName === 'LINK' ? el.href : el.src;
  3662.                         if (!this.test(url)) {
  3663.                                 $del(el);
  3664.                                 return;
  3665.                         }
  3666.                         name = url.substring(url.lastIndexOf("/") + 1).replace(/[\\\/:*?"<>|]/g, '_')
  3667.                                 .toLowerCase();
  3668.                         if (files.indexOf(name) !== -1) {
  3669.                                 temp = url.lastIndexOf('.');
  3670.                                 ext = url.substring(temp);
  3671.                                 url = url.substring(0, temp);
  3672.                                 name = name.substring(0, name.lastIndexOf('.'));
  3673.                                 for (i = 0; ; ++i) {
  3674.                                         temp = name + '(' + i + ')' + ext;
  3675.                                         if (files.indexOf(temp) === -1) {
  3676.                                                 break;
  3677.                                         }
  3678.                                 }
  3679.                                 name = temp;
  3680.                         }
  3681.                         files.push(name);
  3682.                         Images_.queue.run([url, name, el, null]);
  3683.                         count++;
  3684.                 }.bind(new RegExp('^\\/\\/?|^https?:\\/\\/([^\\/]*\.)?' + regQuote(aib.dm) + '\\/', 'i')));
  3685.         }
  3686.         $alert((imgOnly ? Lng.loadImage[lang] : Lng.loadFile[lang]) +
  3687.                 '<br><progress id="de-loadprogress" value="0" max="' + count + '"></progress> <span>1</span>/' +
  3688.                 count, 'filesload', true);
  3689.         progress = $id('de-loadprogress');
  3690.         counter = progress.nextElementSibling;
  3691.         Images_.queue.complete();
  3692.         els = null;
  3693. }
  3694.  
  3695.  
  3696. // TIME CORRECTION
  3697. // ===========================================================================================================
  3698.  
  3699. function dateTime(pattern, rPattern, diff, dtLang, onRPat) {
  3700.         if (dateTime.checkPattern(pattern)) {
  3701.                 this.disabled = true;
  3702.                 return;
  3703.         }
  3704.         this.regex = pattern
  3705.                 .replace(/(?:[sihdny]\?){2,}/g, function () {
  3706.                         return '(?:' + arguments[0].replace(/\?/g, '') + ')?';
  3707.                 })
  3708.                 .replace(/\-/g, '[^<]')
  3709.                 .replace(/\+/g, '[^0-9]')
  3710.                 .replace(/([sihdny]+)/g, '($1)')
  3711.                 .replace(/[sihdny]/g, '\\d')
  3712.                 .replace(/m|w/g, '([a-zA-Zа-яА-Я]+)');
  3713.         this.pattern = pattern.replace(/[\?\-\+]+/g, '').replace(/([a-z])\1+/g, '$1');
  3714.         this.diff = parseInt(diff, 10);
  3715.         this.sDiff = (this.diff < 0 ? '' : '+') + this.diff;
  3716.         this.arrW = Lng.week[dtLang];
  3717.         this.arrM = Lng.month[dtLang];
  3718.         this.arrFM = Lng.fullMonth[dtLang];
  3719.         this.rPattern = rPattern;
  3720.         this.onRPat = onRPat;
  3721. }
  3722. dateTime.toggleSettings = function (el) {
  3723.         if (el.checked && (!/^[+-]\d{1,2}$/.test(Cfg.timeOffset) || dateTime.checkPattern(Cfg.timePattern))) {
  3724.                 $alert(Lng.cTimeError[lang], 'err-correcttime', false);
  3725.                 saveCfg('correctTime', 0);
  3726.                 el.checked = false;
  3727.         }
  3728. };
  3729. dateTime.checkPattern = function (val) {
  3730.         return !val.contains('i') || !val.contains('h') || !val.contains('d') || !val.contains('y') ||
  3731.                 !(val.contains('n') || val.contains('m')) ||
  3732.                 /[^\?\-\+sihdmwny]|mm|ww|\?\?|([ihdny]\?)\1+/.test(val);
  3733. };
  3734. dateTime.prototype = {
  3735.         getRPattern: function (txt) {
  3736.                 var k, p, a, str, i = 1,
  3737.                         j = 0,
  3738.                         m = txt.match(new RegExp(this.regex));
  3739.                 if (!m) {
  3740.                         this.disabled = true;
  3741.                         return false;
  3742.                 }
  3743.                 this.rPattern = '';
  3744.                 str = m[0];
  3745.                 while (a = m[i++]) {
  3746.                         p = this.pattern[i - 2];
  3747.                         if ((p === 'm' || p === 'y') && a.length > 3) {
  3748.                                 p = p.toUpperCase();
  3749.                         }
  3750.                         k = str.indexOf(a, j);
  3751.                         this.rPattern += str.substring(j, k) + '_' + p;
  3752.                         j = k + a.length;
  3753.                 }
  3754.                 this.onRPat && this.onRPat(this.rPattern);
  3755.                 return true;
  3756.         },
  3757.         pad2: function (num) {
  3758.                 return num < 10 ? '0' + num : num;
  3759.         },
  3760.         fix: function (txt) {
  3761.                 if (this.disabled || (!this.rPattern && !this.getRPattern(txt))) {
  3762.                         return txt;
  3763.                 }
  3764.                 return txt.replace(new RegExp(this.regex, 'g'), function () {
  3765.                         var i, a, t, second, minute, hour, day, month, year, dtime;
  3766.                         for (i = 1; i < 8; i++) {
  3767.                                 a = arguments[i];
  3768.                                 t = this.pattern[i - 1];
  3769.                                 t === 's' ? second = a :
  3770.                                 t === 'i' ? minute = a :
  3771.                                 t === 'h' ? hour = a :
  3772.                                 t === 'd' ? day = a :
  3773.                                 t === 'n' ? month = a - 1 :
  3774.                                 t === 'y' ? year = a :
  3775.                                 t === 'm' && (
  3776.                                         month =
  3777.                                                 /^янв|^jan/i.test(a) ? 0 :
  3778.                                                 /^фев|^feb/i.test(a) ? 1 :
  3779.                                                 /^мар|^mar/i.test(a) ? 2 :
  3780.                                                 /^апр|^apr/i.test(a) ? 3 :
  3781.                                                 /^май|^may/i.test(a) ? 4 :
  3782.                                                 /^июн|^jun/i.test(a) ? 5 :
  3783.                                                 /^июл|^jul/i.test(a) ? 6 :
  3784.                                                 /^авг|^aug/i.test(a) ? 7 :
  3785.                                                 /^сен|^sep/i.test(a) ? 8 :
  3786.                                                 /^окт|^oct/i.test(a) ? 9 :
  3787.                                                 /^ноя|^nov/i.test(a) ? 10 :
  3788.                                                 /^дек|^dec/i.test(a) && 11
  3789.                                 );
  3790.                         }
  3791.                         dtime = new Date(year.length === 2 ? '20' + year : year, month, day, hour, minute, second || 0);
  3792.                         dtime.setHours(dtime.getHours() + this.diff);
  3793.                         return this.rPattern
  3794.                                 .replace('_o', this.sDiff)
  3795.                                 .replace('_s', this.pad2(dtime.getSeconds()))
  3796.                                 .replace('_i', this.pad2(dtime.getMinutes()))
  3797.                                 .replace('_h', this.pad2(dtime.getHours()))
  3798.                                 .replace('_d', this.pad2(dtime.getDate()))
  3799.                                 .replace('_w', this.arrW[dtime.getDay()])
  3800.                                 .replace('_n', this.pad2(dtime.getMonth() + 1))
  3801.                                 .replace('_m', this.arrM[dtime.getMonth()])
  3802.                                 .replace('_M', this.arrFM[dtime.getMonth()])
  3803.                                 .replace('_y', ('' + dtime.getFullYear()).substring(2))
  3804.                                 .replace('_Y', dtime.getFullYear());
  3805.                 }.bind(this));
  3806.         }
  3807. };
  3808.  
  3809.  
  3810. // PLAYER
  3811. // ===========================================================================================================
  3812.  
  3813. YouTube = new function () {
  3814.         var instance, vData, embedType, videoType, width, height, isHD, loadTitles;
  3815.  
  3816.         function addThumb(el, m, isYtube) {
  3817.                 var wh = ' width="' + width + '" height="' + height + '"></a>';
  3818.                 if (isYtube) {
  3819.                         el.innerHTML = '<a href="' + aib.prot + '//www.youtube.com/watch?v=' + m[1] + '" target="_blank">' +
  3820.                                 '<img class="de-video-thumb de-ytube" src="https://i.ytimg.com/vi/' + m[1] + '/0.jpg"' + wh;
  3821.                 } else {
  3822.                         el.innerHTML = '<a href="' + aib.prot + '//vimeo.com/' + m[1] + '" target="_blank">' +
  3823.                                 '<img class="de-video-thumb de-vimeo" src=""' + wh;
  3824.                         GM_xmlhttpRequest({
  3825.                                 'method': 'GET',
  3826.                                 'url': aib.prot + '//vimeo.com/api/v2/video/' + m[1] + '.json',
  3827.                                 'onload': function (xhr){
  3828.                                         this.setAttribute('src', JSON.parse(xhr.responseText)[0].thumbnail_large);
  3829.                                 }.bind(el.firstChild.firstChild)
  3830.                         });
  3831.                 }
  3832.         }
  3833.  
  3834.         function addPlayer(el, m, isYtube) {
  3835.                 var time, list, id = m[1],
  3836.                         wh = ' width="' + width + '" height="' + height + '">';
  3837.                 if (isYtube) {
  3838.                         time = (m[2] ? m[2] * 3600 : 0) + (m[3] ? m[3] * 60 : 0) + (m[4] ? +m[4] : 0);
  3839.                         list = m[0].match(/list=[^&#]+/);
  3840.                         el.innerHTML = '<iframe frameborder="0" allowfullscreen="1" src="' + aib.prot + '//www.youtube.com/embed/' +
  3841.                                 id + '?' + (el.parentNode.id === 'de-content-vid' ? 'enablejsapi=1&' : '') +
  3842.                                 (isHD ? 'hd=1&' : '') + (list ? list[0] + '&' : '') + 'start=' + time + (videoType === 1 ?
  3843.                                         '&html5=1&rel=0" type="text/html"' : '" type="application/x-shockwave-flash"') + wh;
  3844.                 } else {
  3845.                         time = m[2] ? m[2] : '';
  3846.                         el.innerHTML = videoType === 1 ?
  3847.                                 '<iframe src="' + aib.prot + '//player.vimeo.com/video/' + id + time +
  3848.                                         '" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen' + wh :
  3849.                                 '<embed type="application/x-shockwave-flash" src="' + aib.prot + '//vimeo.com/moogaloop.swf' +
  3850.                                         '?clip_id=' + id + time + '&server=vimeo.com&color=00adef&fullscreen=1" ' +
  3851.                                         'allowscriptaccess="always" allowfullscreen="true"' + wh;
  3852.                 }
  3853.         }
  3854.  
  3855.         function addLink(post, m, loader, link, isYtube) {
  3856.                 var msg, src, time, dataObj;
  3857.                 if (!post) {
  3858.                         return;
  3859.                 }
  3860.                 post.hasYTube = true;
  3861.                 if (post.ytInfo === null) {
  3862.                         if (embedType === 2) {
  3863.                                 addPlayer(post.ytObj, post.ytInfo = m, isYtube);
  3864.                         } else if (embedType > 2) {
  3865.                                 addThumb(post.ytObj, post.ytInfo = m, isYtube);
  3866.                         }
  3867.                 } else if (!link && $q('.de-video-link[href*="' + m[1] + '"]', post.msg)) {
  3868.                         return;
  3869.                 }
  3870.                 if (loader && (dataObj = vData[m[1]])) {
  3871.                         post.ytData.push(dataObj);
  3872.                 }
  3873.                 if (m[4] || m[3] || m[2]) {
  3874.                         if (m[4] >= 60) {
  3875.                                 m[3] = (m[3] || 0) + Math.floor(m[4] / 60);
  3876.                                 m[4] %= 60;
  3877.                         }
  3878.                         if (m[3] >= 60) {
  3879.                                 m[2] = (m[2] || 0) + Math.floor(m[3] / 60);
  3880.                                 m[3] %= 60;
  3881.                         }
  3882.                         time = (m[2] ? m[2] + 'h' : '') + (m[3] ? m[3] + 'm' : '') + (m[4] ? m[4] + 's' : '');
  3883.                 }
  3884.                 if (link) {
  3885.                         link.href = link.href.replace(/^http:/, 'https:');
  3886.                         if (time) {
  3887.                                 link.setAttribute('de-time', time);
  3888.                         }
  3889.                         if (dataObj) {
  3890.                                 link.textContent = dataObj[0];
  3891.                                 link.className = 'de-video-link de-ytube de-video-title';
  3892.                                 link.setAttribute('de-author', dataObj[1]);
  3893.                                 link.title = Lng.author[lang] + dataObj[1] + ', ' +
  3894.                                         Lng.views[lang] + dataObj[2] + ', ' + Lng.published[lang] + dataObj[3];
  3895.                         } else {
  3896.                                 link.className = 'de-video-link ' + (isYtube ? 'de-ytube' : 'de-vimeo');
  3897.                                 if (!isYtube && Cfg.YTubeTitles) {
  3898.                                         getVimeoTitle(link, m);
  3899.                                 }
  3900.                         }
  3901.                 } else {
  3902.                         src = isYtube ? aib.prot + '//www.youtube.com/watch?v=' + m[1] + (time ? '#t=' + time : '')
  3903.                                 : aib.prot + '//vimeo.com/' + m[1];
  3904.                         post.msg.insertAdjacentHTML('beforeend',
  3905.                                 '<p class="de-video-ext"><a class="de-video-link ' + (isYtube ? 'de-ytube' : 'de-vimeo') +
  3906.                                         (dataObj ? ' de-video-title" title="' + Lng.author[lang] + dataObj[1] + ', ' +
  3907.                                                 Lng.views[lang] + dataObj[2] + ', ' + Lng.published[lang] + dataObj[3] +
  3908.                                                 '" de-author="' + dataObj[1] : '') + (time ? '" de-time="' + time : '') +
  3909.                                         '" href="' + src + '">' + (dataObj ? dataObj[0] : src) + '</a></p>');
  3910.                         link = post.msg.lastChild.firstChild;
  3911.                 }
  3912.                 if (!post.ytInfo || post.ytInfo === m) {
  3913.                         post.ytLink = link;
  3914.                 }
  3915.                 link.ytInfo = m;
  3916.                 if (loader && !dataObj) {
  3917.                         post.ytLinksLoading++;
  3918.                         loader.run([post, link, m[1]]);
  3919.                 }
  3920.         }
  3921.  
  3922.         function clickLink(post, el, mode) {
  3923.                 var m = el.ytInfo;
  3924.                 if (post.ytInfo === m) {
  3925.                         if (mode === 3) {
  3926.                                 if ($c('de-video-thumb', post.ytObj)) {
  3927.                                         el.classList.add('de-current');
  3928.                                         addPlayer(post.ytObj, post.ytInfo = m, el.classList.contains('de-ytube'));
  3929.                                 } else {
  3930.                                         el.classList.remove('de-current');
  3931.                                         addThumb(post.ytObj, post.ytInfo = m, el.classList.contains('de-ytube'));
  3932.                                 }
  3933.                         } else {
  3934.                                 el.classList.remove('de-current');
  3935.                                 post.ytObj.innerHTML = '';
  3936.                                 post.ytInfo = null;
  3937.                         }
  3938.                 } else if (mode > 2) {
  3939.                         post.ytLink.classList.remove('de-current');
  3940.                         post.ytLink = el;
  3941.                         addThumb(post.ytObj, post.ytInfo = m, el.classList.contains('de-ytube'));
  3942.                 } else {
  3943.                         post.ytLink.classList.remove('de-current');
  3944.                         post.ytLink = el;
  3945.                         el.classList.add('de-current');
  3946.                         addPlayer(post.ytObj, post.ytInfo = m, el.classList.contains('de-ytube'));
  3947.                 }
  3948.         }
  3949.  
  3950.         function getVimeoTitle(link, m) {
  3951.                 GM_xmlhttpRequest({
  3952.                         'method': 'GET',
  3953.                         'url': aib.prot + '//vimeo.com/api/v2/video/' + m[1] + '.json',
  3954.                         'onload': function (xhr) {
  3955.                                 var json = JSON.parse(xhr.responseText)[0],
  3956.                                         date = new RegExp (/(.*)\s(.*)?/).exec(json["upload_date"]);
  3957.                                 link.textContent = json["title"];
  3958.                                 link.title = Lng.author[lang] + json["user_name"] + ', ' +
  3959.                                         Lng.views[lang] + json["stats_number_of_plays"] + ', ' + Lng.published[lang] + date[1];
  3960.                         }
  3961.                 });
  3962.         }
  3963.  
  3964.         function getYtubeTitleLoader() {
  3965.                 var queueEnd, queue = new $queue(4, function (qIdx, num, data) {
  3966.                         if (num % 30 === 0) {
  3967.                                 queue.pause();
  3968.                                 setTimeout(queue.continue.bind(queue), 3e3);
  3969.                         }
  3970.                         GM_xmlhttpRequest({
  3971.                                 'method': 'GET',
  3972.                                 'url': aib.prot + '//gdata.youtube.com/feeds/api/videos/' + data[2] +
  3973.                                         '?alt=json&fields=title/text(),author/name,yt:statistics/@viewCount,published',
  3974.                                 'onreadystatechange': function (idx, xhr) {
  3975.                                         if (xhr.readyState !== 4) {
  3976.                                                 return;
  3977.                                         }
  3978.                                         var entry, title, author, views, publ, data, post = this[0], link = this[1];
  3979.                                         try {
  3980.                                                 if (xhr.status === 200) {
  3981.                                                         entry = JSON.parse(xhr.responseText).entry;
  3982.                                                         title = entry.title.$t;
  3983.                                                         author = entry.author[0].name.$t;
  3984.                                                         views = entry.yt$statistics.viewCount;
  3985.                                                         publ = entry.published.$t.substr(0, 10);
  3986.                                                 }
  3987.                                         } finally {
  3988.                                                 if (title) {
  3989.                                                         link.textContent = title;
  3990.                                                         link.setAttribute('de-author', author);
  3991.                                                         link.classList.add('de-video-title');
  3992.                                                         link.title = Lng.author[lang] + author + ', ' + Lng.views[lang] + views + ', ' +
  3993.                                                                 Lng.published[lang] + publ;
  3994.                                                         vData[this[2]] = data = [title, author, views, publ];
  3995.                                                         post.ytData.push(data);
  3996.                                                         post.ytLinksLoading--;
  3997.                                                         if (post.ytHideFun !== null) {
  3998.                                                                 post.ytHideFun(data);
  3999.                                                         }
  4000.                                                 }
  4001.                                                 setTimeout(queueEnd, 250, idx);
  4002.                                         }
  4003.                                 }.bind(data, qIdx)
  4004.                         });
  4005.                 }, function () {
  4006.                         sesStorage['de-ytube-data'] = JSON.stringify(vData);
  4007.                         queue = queueEnd = null;
  4008.                 });
  4009.                 queueEnd = queue.end.bind(queue);
  4010.                 return queue;
  4011.         }
  4012.  
  4013.         function YouTubeSingleton() {
  4014.                 if (instance) {
  4015.                         return instance;
  4016.                 }
  4017.                 instance = this;
  4018.                 embedType = Cfg.addYouTube;
  4019.                 if (embedType === 0) {
  4020.                         this.parseLinks = emptyFn;
  4021.                         this.updatePost = emptyFn;
  4022.                 }
  4023.                 loadTitles = Cfg.YTubeTitles;
  4024.                 if (loadTitles) {
  4025.                         vData = JSON.parse(sesStorage['de-ytube-data'] || '{}');
  4026.                 }
  4027.                 videoType = Cfg.YTubeType;
  4028.                 width = Cfg.YTubeWidth;
  4029.                 height = Cfg.YTubeHeigh;
  4030.                 isHD = Cfg.YTubeHD;
  4031.         }
  4032.         YouTubeSingleton.prototype = {
  4033.                 embedType: embedType,
  4034.                 ytReg: /^https?:\/\/(?:www\.|m\.)?youtu(?:be\.com\/(?:watch\?.*?v=|v\/|embed\/)|\.be\/)([a-zA-Z0-9-_]+).*?(?:t(?:ime)?=(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?)?$/,
  4035.                 vimReg: /^https?:\/\/(?:www\.)?vimeo\.com\/(?:[^\?]+\?clip_id=|.*?\/)?(\d+).*?(#t=\d+)?$/,
  4036.                 vData: vData,
  4037.  
  4038.                 addPlayer: addPlayer,
  4039.                 addLink: addLink,
  4040.                 clickLink: clickLink,
  4041.                 parseLinks: function (post) {
  4042.                         var i, len, els, el, src, m, embedTube = [],
  4043.                                 loader = loadTitles && getYtubeTitleLoader();
  4044.                         for (i = 0, els = $Q('embed, object, iframe', post ? post.el : dForm), len = els.length; i < len; ++i) {
  4045.                                 el = els[i];
  4046.                                 src = el.src || el.data;
  4047.                                 if (m = src.match(this.ytReg)) {
  4048.                                         embedTube.push(post || aib.getPostEl(el).post, m, true);
  4049.                                         $del(el);
  4050.                                 }
  4051.                                 if (Cfg.addVimeo && (m = src.match(this.vimReg))) {
  4052.                                         embedTube.push(post || aib.getPostEl(el).post, m, false);
  4053.                                         $del(el);
  4054.                                 }
  4055.                         }
  4056.                         for (i = 0, els = $Q('a[href*="youtu"]', post ? post.el : dForm), len = els.length; i < len; ++i) {
  4057.                                 el = els[i];
  4058.                                 if (m = el.href.match(this.ytReg)) {
  4059.                                         addLink(post || aib.getPostEl(el).post, m, loader, el, true);
  4060.                                 }
  4061.                         }
  4062.                         if (Cfg.addVimeo) {
  4063.                                 for (i = 0, els = $Q('a[href*="vimeo.com"]', post ? post.el : dForm), len = els.length; i < len; ++i) {
  4064.                                         el = els[i];
  4065.                                         if (m = el.href.match(this.vimReg)) {
  4066.                                                 addLink(post || aib.getPostEl(el).post, m, null, el, false);
  4067.                                         }
  4068.                                 }
  4069.                         }
  4070.                         for (i = 0, len = embedTube.length; i < len; i += 3) {
  4071.                                 addLink(embedTube[i], embedTube[i + 1], loader, null, embedTube[i + 2]);
  4072.                         }
  4073.                         loader && loader.complete();
  4074.                 },
  4075.                 updatePost: function (post, oldLinks, newLinks, cloned) {
  4076.                         var i, j, el, link, m, loader = !cloned && loadTitles && getYtubeTitleLoader(),
  4077.                                 len = newLinks.length;
  4078.                         for (i = 0, j = 0; i < len; i++) {
  4079.                                 el = newLinks[i];
  4080.                                 link = oldLinks[j];
  4081.                                 if (link && link.classList.contains('de-current')) {
  4082.                                         post.ytLink = el;
  4083.                                 }
  4084.                                 if (cloned) {
  4085.                                         el.ytInfo = link.ytInfo;
  4086.                                         j++;
  4087.                                 } else if (m = el.href.match(this.ytReg)) {
  4088.                                         addLink(post, m, loader, el, true);
  4089.                                         j++;
  4090.                                 }
  4091.                         }
  4092.                         post.ytLink = post.ytLink || newLinks[0];
  4093.                         loader && loader.complete();
  4094.                 }
  4095.         };
  4096.  
  4097.         return YouTubeSingleton;
  4098. };
  4099.  
  4100. function embedMP3Links(post) {
  4101.         var el, link, src, i, els, len;
  4102.         if (!Cfg.addMP3) {
  4103.                 return;
  4104.         }
  4105.         for (i = 0, els = $Q('a[href*=".mp3"]', post ? post.el : dForm), len = els.length; i < len; ++i) {
  4106.                 link = els[i];
  4107.                 if (link.target !== '_blank' && link.rel !== 'nofollow') {
  4108.                         continue;
  4109.                 }
  4110.                 src = link.href;
  4111.                 el = (post || aib.getPostEl(link).post).mp3Obj;
  4112.                 if (nav.canPlayMP3) {
  4113.                         if (!$q('audio[src="' + src + '"]', el)) {
  4114.                                 el.insertAdjacentHTML('beforeend',
  4115.                                         '<p><audio src="' + src + '" preload="none" controls></audio></p>');
  4116.                                 link = el.lastChild.firstChild;
  4117.                                 link.addEventListener('play', updater.addPlayingTag, false);
  4118.                                 link.addEventListener('pause', updater.removePlayingTag, false);
  4119.                         }
  4120.                 } else if (!$q('object[FlashVars*="' + src + '"]', el)) {
  4121.                         el.insertAdjacentHTML('beforeend', '<object data="http://junglebook2007.narod.ru/audio/player.swf" type="application/x-shockwave-flash" wmode="transparent" width="220" height="16" FlashVars="playerID=1&amp;bg=0x808080&amp;leftbg=0xB3B3B3&amp;lefticon=0x000000&amp;rightbg=0x808080&amp;rightbghover=0x999999&amp;rightcon=0x000000&amp;righticonhover=0xffffff&amp;text=0xffffff&amp;slider=0x222222&amp;track=0xf5f5dc&amp;border=0x666666&amp;loader=0x7fc7ff&amp;loop=yes&amp;autostart=no&amp;soundFile=' + src + '"><br>');
  4122.                 }
  4123.         }
  4124. }
  4125.  
  4126.  
  4127. // AJAX
  4128. // ===========================================================================================================
  4129.  
  4130. function ajaxLoad(url, loadForm, Fn, errFn) {
  4131.         return GM_xmlhttpRequest({
  4132.                 'method': 'GET',
  4133.                 'url': nav.fixLink(url),
  4134.                 'onreadystatechange': function (xhr) {
  4135.                         if (xhr.readyState !== 4) {
  4136.                                 return;
  4137.                         }
  4138.                         if (xhr.status !== 200) {
  4139.                                 if (errFn) {
  4140.                                         errFn(xhr.status, xhr.statusText, this);
  4141.                                 }
  4142.                         } else if (Fn) {
  4143.                                 do {
  4144.                                         var el, text = xhr.responseText;
  4145.                                         if ((aib.futa ? /<!--gz-->$/ : /<\/html?>[\s\n\r]*$/).test(text)) {
  4146.                                                 el = $DOM(text);
  4147.                                                 if (!loadForm || (el = $q(aib.qDForm, el))) {
  4148.                                                         Fn(el, this);
  4149.                                                         break;
  4150.                                                 }
  4151.                                         }
  4152.                                         if (errFn) {
  4153.                                                 errFn(0, Lng.errCorruptData[lang], this);
  4154.                                         }
  4155.                                 } while (false);
  4156.                         }
  4157.                         loadForm = Fn = errFn = null;
  4158.                 }
  4159.         });;
  4160. }
  4161.  
  4162. function getJsonPosts(url, Fn) {
  4163.         GM_xmlhttpRequest({
  4164.                 'method': 'GET',
  4165.                 'url': nav.fixLink(url),
  4166.                 'onreadystatechange': function (xhr) {
  4167.                         if (xhr.readyState !== 4) {
  4168.                                 return;
  4169.                         }
  4170.                         if (xhr.status === 304) {
  4171.                                 closeAlert($id('de-alert-newposts'));
  4172.                         } else {
  4173.                                 try {
  4174.                                         var json = JSON.parse(xhr.responseText);
  4175.                                 } catch(e) {
  4176.                                         Fn(1, e.toString(), null, this);
  4177.                                 } finally {
  4178.                                         if (json) {
  4179.                                                 Fn(xhr.status, xhr.statusText, json, this);
  4180.                                         }
  4181.                                         Fn = null;
  4182.                                 }
  4183.                         }
  4184.                 }
  4185.         });
  4186. }
  4187.  
  4188. function loadFavorThread() {
  4189.         var post, el = this.parentNode,
  4190.                 ifrm = $t('iframe', el),
  4191.                 cont = $c('de-content', doc);
  4192.         if (ifrm) {
  4193.                 $del(ifrm);
  4194.                 cont.style.overflowY = 'auto';
  4195.                 return;
  4196.         }
  4197.         if ((post = pByNum[el.getAttribute('de-num')]) && !post.hidden) {
  4198.                 scrollTo(0, pageYOffset + post.el.getBoundingClientRect().top);
  4199.                 return;
  4200.         }
  4201.         $del($id('de-iframe-fav'));
  4202.         $c('de-content', doc).style.overflowY = 'scroll';
  4203.         $alert(Lng.loading[lang], 'load-favthr', true);
  4204.         el.insertAdjacentHTML('beforeend', '<iframe name="de-iframe-fav" id="de-iframe-fav" src="' +
  4205.                 $t('a', el).href + '" scrolling="no" style="display: block; border: none; width: ' +
  4206.                 (doc.documentElement.clientWidth - 55) + 'px; height: 1px;">');
  4207. }
  4208.  
  4209. function loadPages(count) {
  4210.         var fun, i = pageNum,
  4211.                 len = Math.min(aib.lastPage + 1, i + count),
  4212.                 pages = [],
  4213.                 loaded = 1;
  4214.         count = len - i;
  4215.  
  4216.         function onLoadOrError(idx, eCodeOrForm, eMsgOrXhr, maybeXhr) {
  4217.                 if (typeof eCodeOrForm === 'number') {
  4218.                         pages[idx] = $add('<div><center style="font-size: 2em">' +
  4219.                                 getErrorMessage(eCodeOrForm, eMsgOrXhr) + '</center><hr></div>');
  4220.                 } else {
  4221.                         pages[idx] = replacePost(eCodeOrForm);
  4222.                 }
  4223.                 if (loaded === count) {
  4224.                         var el, df, j, parseThrs = Thread.parsed,
  4225.                                 threads = parseThrs ? [] : null;
  4226.                         for (j in pages) {
  4227.                                 if (!pages.hasOwnProperty(j)) {
  4228.                                         continue;
  4229.                                 }
  4230.                                 if (j != pageNum) {
  4231.                                         dForm.insertAdjacentHTML('beforeend', '<center style="font-size: 2em">' +
  4232.                                                 Lng.page[lang] + ' ' + j + '</center><hr>');
  4233.                                 }
  4234.                                 df = pages[j];
  4235.                                 if (parseThrs) {
  4236.                                         threads = parseThreadNodes(df, threads);
  4237.                                 }
  4238.                                 while (el = df.firstChild) {
  4239.                                         dForm.appendChild(el);
  4240.                                 }
  4241.                         }
  4242.                         if (!parseThrs) {
  4243.                                 threads = $Q(aib.qThread, dForm);
  4244.                         }
  4245.                         do {
  4246.                                 if (threads.length !== 0) {
  4247.                                         try {
  4248.                                                 parseDelform(dForm, threads);
  4249.                                         } catch(e) {
  4250.                                                 $alert(getPrettyErrorMessage(e), 'load-pages', true);
  4251.                                                 break;
  4252.                                         }
  4253.                                         initDelformAjax();
  4254.                                         addDelformStuff(false);
  4255.                                         readUserPosts();
  4256.                                         readFavoritesPosts();
  4257.                                         $each($Q('input[type="password"]', dForm), function (pEl) {
  4258.                                                 pr.dpass = pEl;
  4259.                                                 pEl.value = Cfg.passwValue;
  4260.                                         });
  4261.                                         if (hKeys) {
  4262.                                                 hKeys.clear(pageNum + count - 1);
  4263.                                         }
  4264.                                 }
  4265.                                 closeAlert($id('de-alert-load-pages'));
  4266.                         } while (false);
  4267.                         dForm.style.display = '';
  4268.                         loaded = pages = count = null;
  4269.                 } else {
  4270.                         loaded++;
  4271.                 }
  4272.         }
  4273.  
  4274.         $alert(Lng.loading[lang], 'load-pages', true);
  4275.         $each($Q('a[href^="blob:"]', dForm), function (a) {
  4276.                 window.URL.revokeObjectURL(a.href);
  4277.         });
  4278.         Pview.clearCache();
  4279.         isExpImg = false;
  4280.         pByNum = Object.create(null);
  4281.         Thread.tNums = [];
  4282.         Post.hiddenNums = [];
  4283.         if (Attachment.viewer) {
  4284.                 Attachment.viewer.close(null);
  4285.                 Attachment.viewer = null;
  4286.         }
  4287.         dForm.style.display = 'none';
  4288.         dForm.innerHTML = '';
  4289.         if (pr.isQuick) {
  4290.                 if (pr.file) {
  4291.                         pr.delFilesUtils();
  4292.                 }
  4293.                 pr.txta.value = '';
  4294.         }
  4295.         while (i < len) {
  4296.                 fun = onLoadOrError.bind(null, i);
  4297.                 ajaxLoad(aib.getPageUrl(brd, i++), true, fun, fun);
  4298.         }
  4299. }
  4300.  
  4301. function infoLoadErrors(eCode, eMsg, newPosts) {
  4302.         if (eCode === 200 || eCode === 304) {
  4303.                 closeAlert($id('de-alert-newposts'));
  4304.         } else if (eCode === 0) {
  4305.                 $alert(eMsg || Lng.noConnect[lang], 'newposts', false);
  4306.         } else {
  4307.                 $alert(Lng.thrNotFound[lang] + TNum + '): \n' + getErrorMessage(eCode, eMsg), 'newposts', false);
  4308.                 if (newPosts !== -1) {
  4309.                         doc.title = '{' + eCode + '} ' + doc.title;
  4310.                 }
  4311.         }
  4312. }
  4313.  
  4314.  
  4315. // SPELLS
  4316. // ===========================================================================================================
  4317.  
  4318. function Spells(read) {
  4319.         if (read) {
  4320.                 this._read(true);
  4321.         } else {
  4322.                 this.disable(false);
  4323.         }
  4324. }
  4325. Spells.names = [
  4326.         'words', 'exp', 'exph', 'imgn', 'ihash', 'subj', 'name', 'trip', 'img', 'sage', 'op', 'tlen',
  4327.         'all', 'video', 'wipe', 'num', 'vauthor'
  4328. ];
  4329. Spells.needArg = [
  4330.         /* words */ true, /* exp */ true, /* exph */ true, /* imgn */ true, /* ihash */ true,
  4331.         /* subj */ false, /* name */ true, /* trip */ false, /* img */ false, /* sage */ false,
  4332.         /* op */ false, /* tlen */ false, /* all */ false, /* video */ false, /* wipe */ false,
  4333.         /* num */ true, /* vauthor */ true
  4334. ];
  4335. Spells.decompileSpell = function (type, neg, val, scope) {
  4336.         var temp, temp_, spell = (neg ? '!#' : '#') + Spells.names[type] + (scope ? '[' +
  4337.                 scope[0] + (scope[1] ? ',' + (scope[1] === -1 ? '' : scope[1]) : '') + ']' : '');
  4338.         if (!val) {
  4339.                 return spell;
  4340.         }
  4341.         // #img
  4342.         if (type === 8) {
  4343.                 return spell + '(' + (val[0] === 2 ? '>' : val[0] === 1 ? '<' : '=') +
  4344.                         (val[1] ? val[1][0] + (val[1][1] === val[1][0] ? '' : '-' + val[1][1]) : '') +
  4345.                         (val[2] ? '@' + val[2][0] + (val[2][0] === val[2][1] ? '' : '-' + val[2][1]) + 'x' +
  4346.                         val[2][2] + (val[2][2] === val[2][3] ? '' : '-' + val[2][3]) : '') + ')';
  4347.         }
  4348.         // #wipe
  4349.         else if (type === 14) {
  4350.                 if (val === 0x3F) {
  4351.                         return spell;
  4352.                 }
  4353.                 temp = [];
  4354.                 (val & 1) && temp.push('samelines');
  4355.                 (val & 2) && temp.push('samewords');
  4356.                 (val & 4) && temp.push('longwords');
  4357.                 (val & 8) && temp.push('symbols');
  4358.                 (val & 16) && temp.push('capslock');
  4359.                 (val & 32) && temp.push('numbers');
  4360.                 (val & 64) && temp.push('whitespace');
  4361.                 return spell + '(' + temp.join(',') + ')';
  4362.         }
  4363.         // #num, #tlen
  4364.         else if (type === 15 || type === 11) {
  4365.                 if ((temp = val[1].length - 1) !== -1) {
  4366.                         for (temp_ = []; temp >= 0; temp--) {
  4367.                                 temp_.push(val[1][temp][0] + '-' + val[1][temp][1]);
  4368.                         }
  4369.                         temp_.reverse();
  4370.                 }
  4371.                 spell += '(';
  4372.                 if (val[0].length !== 0) {
  4373.                         spell += val[0].join(',') + (temp_ ? ',' : '');
  4374.                 }
  4375.                 if (temp_) {
  4376.                         spell += temp_.join(',');
  4377.                 }
  4378.                 return spell + ')';
  4379.         }
  4380.         // #words, #name, #trip, #vauthor
  4381.         else if (type === 0 || type === 6 || type === 7 || type === 16) {
  4382.                 return spell + '(' + val.replace(/\)/g, '\\)') + ')';
  4383.         } else {
  4384.                 return spell + '(' + String(val) + ')';
  4385.         }
  4386. };
  4387. Spells.prototype = {
  4388.         _optimizeSpells: function (spells) {
  4389.                 var i, j, len, flags, type, spell, scope, neg, parensSpells, lastSpell = -1,
  4390.                         newSpells = [];
  4391.                 for (i = 0, len = spells.length; i < len; ++i) {
  4392.                         spell = spells[i];
  4393.                         flags = spell[0];
  4394.                         type = flags & 0xFF;
  4395.                         neg = (flags & 0x100) !== 0;
  4396.                         if (type === 0xFF) {
  4397.                                 parensSpells = this._optimizeSpells(spell[1]);
  4398.                                 if (parensSpells) {
  4399.                                         if (parensSpells.length !== 1) {
  4400.                                                 newSpells.push([flags, parensSpells]);
  4401.                                                 lastSpell++;
  4402.                                                 continue;
  4403.                                         } else if ((parensSpells[0][0] & 0xFF) !== 12) {
  4404.                                                 newSpells.push([(parensSpells[0][0] | (flags & 0x200)) ^ (flags & 0x100),
  4405.                                                         parensSpells[0][1]]);
  4406.                                                 lastSpell++;
  4407.                                                 continue;
  4408.                                         }
  4409.                                         flags = parensSpells[0][0];
  4410.                                         neg = !(neg ^ ((flags & 0x100) !== 0));
  4411.                                 }
  4412.                         } else {
  4413.                                 scope = spell[2];
  4414.                                 if (!scope || (scope[0] === brd &&
  4415.                                         (scope[1] === -1 ? !TNum : (!scope[1] || scope[1] === TNum))))
  4416.                                 {
  4417.                                         if (type === 12) {
  4418.                                                 neg = !neg;
  4419.                                         } else {
  4420.                                                 newSpells.push([flags, spell[1]]);
  4421.                                                 lastSpell++;
  4422.                                                 continue;
  4423.                                         }
  4424.                                 }
  4425.                         }
  4426.                         for (j = lastSpell; j >= 0 && (((newSpells[j][0] & 0x200) !== 0) ^ neg); --j) {}
  4427.                         if (j !== lastSpell) {
  4428.                                 newSpells = newSpells.slice(0, j + 1);
  4429.                                 lastSpell = j;
  4430.                         }
  4431.                         if (neg && j !== -1) {
  4432.                                 newSpells[j][0] &= 0x1FF;
  4433.                         }
  4434.                         if (((flags & 0x200) !== 0) ^ neg) {
  4435.                                 break;
  4436.                         }
  4437.                 }
  4438.                 return lastSpell === -1 ? neg ? [[12, '']] : null : newSpells;
  4439.         },
  4440.         _initSpells: function (data) {
  4441.                 if (data) {
  4442.                         data.forEach(function initExps(item) {
  4443.                                 var val = item[1];
  4444.                                 if (val) {
  4445.                                         switch (item[0] & 0xFF) {
  4446.                                         case 1:
  4447.                                         case 2:
  4448.                                         case 3:
  4449.                                         case 5:
  4450.                                         case 13: item[1] = toRegExp(val, true); break;
  4451.                                         case 0xFF: val.forEach(initExps);
  4452.                                         }
  4453.                                 }
  4454.                         });
  4455.                 }
  4456.                 return data;
  4457.         },
  4458.         _decompileScope: function (scope, indent) {
  4459.                 var spell, type, temp, str, dScope = [], hScope = false, i = 0, j = 0, len = scope.length;
  4460.                 for (; i < len; i++, j++) {
  4461.                         spell = scope[i];
  4462.                         type = spell[0] & 0xFF;
  4463.                         if (type === 0xFF) {
  4464.                                 hScope = true;
  4465.                                 temp = this._decompileScope(spell[1], indent + '    ');
  4466.                                 if (temp[1]) {
  4467.                                         str = ((spell[0] & 0x100) ? '!(\n' : '(\n') + indent + '    ' +
  4468.                                                 temp[0].join('\n' + indent + '    ') + '\n' + indent + ')';
  4469.                                         if (j === 0) {
  4470.                                                 dScope[0] = str;
  4471.                                         } else {
  4472.                                                 dScope[--j] += ' ' + str;
  4473.                                         }
  4474.                                 } else {
  4475.                                         dScope[j] = ((spell[0] & 0x100) ? '!(' : '(') + temp[0].join(' ') + ')';
  4476.                                 }
  4477.                         } else {
  4478.                                 dScope[j] = Spells.decompileSpell(type, spell[0] & 0x100, spell[1], spell[2]);
  4479.                         }
  4480.                         if (i !== len - 1) {
  4481.                                 dScope[j] += (spell[0] & 0x200) ? ' &' : ' |';
  4482.                         }
  4483.                 }
  4484.                 return [dScope, dScope.length > 2 || hScope];
  4485.         },
  4486.         _decompileSpells: function () {
  4487.                 var str, reps, oreps, data = this._data;
  4488.                 if (!data) {
  4489.                         this._read(false);
  4490.                         if (!(data = this._data)) {
  4491.                                 return this._list = '';
  4492.                         }
  4493.                 }
  4494.                 str = data[1] ? this._decompileScope(data[1], '')[0].join('\n') : '';
  4495.                 reps = data[2];
  4496.                 oreps = data[3];
  4497.                 if (reps || oreps) {
  4498.                         if (str) {
  4499.                                 str += '\n\n';
  4500.                         }
  4501.                         reps && reps.forEach(function (rep) {
  4502.                                 str += this._decompileRep(rep, false) + '\n';
  4503.                         }.bind(this));
  4504.                         oreps && oreps.forEach(function (orep) {
  4505.                                 str += this._decompileRep(orep, true) + '\n';
  4506.                         }.bind(this));
  4507.                         str = str.substr(0, str.length - 1);
  4508.                 }
  4509.                 this._data = null;
  4510.                 return this._list = str;
  4511.         },
  4512.         _decompileRep: function (rep, isOrep) {
  4513.                 return (isOrep ? '#outrep' : '#rep') +
  4514.                         (rep[0] ? '[' + rep[0] + (rep[1] ? ',' + (rep[1] === -1 ? '' : rep[1]) : '') + ']' : '') +
  4515.                         '(' + rep[2] + ',' + rep[3].replace(/\)/g, '\\)') + ')';
  4516.         },
  4517.         _optimizeReps: function (data) {
  4518.                 if (data) {
  4519.                         var nData = [];
  4520.                         data.forEach(function (temp) {
  4521.                                 if (!temp[0] || (temp[0] === brd && (temp[1] === -1 ? !TNum : !temp[1] || temp[1] === TNum))) {
  4522.                                         nData.push([temp[2], temp[3]]);
  4523.                                 }
  4524.                         });
  4525.                         return nData.length === 0 ? false : nData;
  4526.                 }
  4527.                 return false;
  4528.         },
  4529.         _initReps: function (data) {
  4530.                 if (data) {
  4531.                         for (var i = data.length - 1; i >= 0; i--) {
  4532.                                 data[i][0] = toRegExp(data[i][0], false);
  4533.                         }
  4534.                 }
  4535.                 return data;
  4536.         },
  4537.         _init: function (spells, reps, outreps) {
  4538.                 this._spells = this._initSpells(spells);
  4539.                 this._sLength = spells && spells.length;
  4540.                 this._reps = this._initReps(reps);
  4541.                 this._outreps = this._initReps(outreps);
  4542.                 this.enable = !!this._spells;
  4543.                 this.haveReps = !!reps;
  4544.                 this.haveOutreps = !!outreps;
  4545.         },
  4546.         _read: function (init) {
  4547.                 var spells, data;
  4548.                 try {
  4549.                         spells = JSON.parse(Cfg.spells);
  4550.                         data = JSON.parse(sesStorage['de-spells-' + brd + TNum]);
  4551.                 } catch(e) {}
  4552.                 if (data && spells && data[0] === spells[0]) {
  4553.                         this._data = spells;
  4554.                         if (init) {
  4555.                                 this.hash = data[0];
  4556.                                 this._init(data[1], data[2], data[3]);
  4557.                         }
  4558.                         return;
  4559.                 }
  4560.                 if (!spells) {
  4561.                         spells = this.parseText('#wipe(samelines,samewords,longwords,numbers,whitespace)');
  4562.                 }
  4563.                 if (init) {
  4564.                         this.update(spells, false, false);
  4565.                 } else {
  4566.                         this._data = spells;
  4567.                 }
  4568.         },
  4569.         _asyncSpellComplete: function (interp) {
  4570.                 this.hasNumSpell |= interp.hasNumSpell;
  4571.                 this._asyncJobs--;
  4572.                 this.end(null);
  4573.         },
  4574.         _asyncJobs: 0,
  4575.         _completeFns: [],
  4576.         _hasComplFns: false,
  4577.         _data: null,
  4578.         _list: '',
  4579.  
  4580.         hash: 0,
  4581.         hasNumSpell: false,
  4582.         enable: false,
  4583.         get list() {
  4584.                 return this._list || this._decompileSpells();
  4585.         },
  4586.         addCompleteFunc: function (Fn) {
  4587.                 this._completeFns.push(Fn);
  4588.                 this._hasComplFns = true;
  4589.         },
  4590.         parseText: function (str) {
  4591.                 var codeGen = new SpellsCodegen(str),
  4592.                         data = codeGen.generate();
  4593.                 if (codeGen.hasError) {
  4594.                         $alert(Lng.error[lang] + ': ' + codeGen.error, 'help-err-spell', false);
  4595.                 } else if (data) {
  4596.                         if (data[0] && Cfg.sortSpells) {
  4597.                                 this.sort(data[0]);
  4598.                         }
  4599.                         return [Date.now(), data[0], data[1], data[2]];
  4600.                 }
  4601.                 return null;
  4602.         },
  4603.         sort: function (sp) {
  4604.                 // Wraps AND-spells with brackets for proper sorting
  4605.                 for (var i = 0, len = sp.length-1; i < len; i++) {
  4606.                         if (sp[i][0] > 0x200) {
  4607.                                 var temp = [0xFF, []];
  4608.                                 do {
  4609.                                         temp[1].push(sp.splice(i, 1)[0]);
  4610.                                         len--;
  4611.                                 } while (sp[i][0] > 0x200);
  4612.                                 temp[1].push(sp.splice(i, 1)[0]);
  4613.                                 sp.splice(i, 0, temp);
  4614.                         }
  4615.                 }
  4616.                 sp = sp.sort();
  4617.                 for (var i = 0, len = sp.length-1; i < len; i++) {
  4618.                         // Removes duplicates and weaker spells
  4619.                         if (sp[i][0] === sp[i+1][0] && sp[i][1] <= sp[i+1][1] && sp[i][1] >= sp[i+1][1] &&
  4620.                           (sp[i][2] === null || // Stronger spell with 3 parameters
  4621.                            sp[i][2] === undefined || // Equal spells with 2 parameters
  4622.                           (sp[i][2] <= sp[i+1][2] && sp[i][2] >= sp[i+1][2])))
  4623.                         { // Equal spells with 3 parameters
  4624.                                 sp.splice(i+1, 1);
  4625.                                 i--;
  4626.                                 len--;
  4627.                         // Moves brackets to the end of the list
  4628.                         } else if (sp[i][0] === 0xFF) {
  4629.                                 sp.push(sp.splice(i, 1)[0]);
  4630.                                 i--;
  4631.                                 len--;
  4632.                         }
  4633.                 }
  4634.         },
  4635.         update: function (data, sync, isHide) {
  4636.                 var spells = data[1] ? this._optimizeSpells(data[1]) : false,
  4637.                         reps = this._optimizeReps(data[2]),
  4638.                         outreps = this._optimizeReps(data[3]);
  4639.                 saveCfg('spells', JSON.stringify(data));
  4640.                 sesStorage['de-spells-' + brd + TNum] = JSON.stringify([data[0], spells, reps, outreps]);
  4641.                 this._data = data;
  4642.                 this._list = '';
  4643.                 this.hash = data[0];
  4644.                 if (sync) {
  4645.                         locStorage['__de-spells'] = JSON.stringify({
  4646.                                 'hide': (!!this.list && !!isHide),
  4647.                                 'data': data
  4648.                         });
  4649.                         locStorage.removeItem('__de-spells');
  4650.                 }
  4651.                 this._init(spells, reps, outreps);
  4652.         },
  4653.         setSpells: function (spells, sync) {
  4654.                 this.update(spells, sync, Cfg.hideBySpell);
  4655.                 if (Cfg.hideBySpell) {
  4656.                         for (var post = firstThr.op; post; post = post.next) {
  4657.                                 this.check(post);
  4658.                         }
  4659.                         this.end(savePosts);
  4660.                 } else {
  4661.                         this.enable = false;
  4662.                 }
  4663.         },
  4664.         disable: function (sync) {
  4665.                 this.enable = false;
  4666.                 this._list = '';
  4667.                 this._data = null;
  4668.                 this.haveReps = this.haveOutreps = false;
  4669.                 saveCfg('hideBySpell', false);
  4670.         },
  4671.         end: function (Fn) {
  4672.                 if (this._asyncJobs === 0) {
  4673.                         Fn && Fn();
  4674.                         if (this._hasComplFns) {
  4675.                                 for (var i = 0, len = this._completeFns.length; i < len; ++i) {
  4676.                                         this._completeFns[i]();
  4677.                                 }
  4678.                                 this._completeFns = [];
  4679.                                 this._hasComplFns = false;
  4680.                         }
  4681.                 } else if (Fn) {
  4682.                         this.addCompleteFunc(Fn);
  4683.                 }
  4684.         },
  4685.         check: function (post) {
  4686.                 if (!this.enable) {
  4687.                         return 0;
  4688.                 }
  4689.                 var interp = new SpellsInterpreter(post, this._spells, this._sLength);
  4690.                 if (interp.run()) {
  4691.                         this.hasNumSpell |= interp.hasNumSpell;
  4692.                         return interp.postHidden ? 1 : 0;
  4693.                 }
  4694.                 interp.setEndFn(this._asyncSpellComplete.bind(this));
  4695.                 this._asyncJobs++;
  4696.                 return 0;
  4697.         },
  4698.         replace: function (txt) {
  4699.                 for (var i = 0, len = this._reps.length; i < len; i++) {
  4700.                         txt = txt.replace(this._reps[i][0], this._reps[i][1]);
  4701.                 }
  4702.                 return txt;
  4703.         },
  4704.         outReplace: function (txt) {
  4705.                 for (var i = 0, len = this._outreps.length; i < len; i++) {
  4706.                         txt = txt.replace(this._outreps[i][0], this._outreps[i][1]);
  4707.                 }
  4708.                 return txt;
  4709.         },
  4710.         addSpell: function (type, arg, scope, isNeg, spells) {
  4711.                 if (!spells) {
  4712.                         if (!this._data) {
  4713.                                 this._read(false);
  4714.                         }
  4715.                         spells = this._data || [Date.now(), [], false, false];
  4716.                 }
  4717.                 var idx, sScope = String(scope),
  4718.                         sArg = String(arg);
  4719.                 if (spells[1]) {
  4720.                         spells[1].some(scope && isNeg ? function (spell, i) {
  4721.                                 var data;
  4722.                                 if (spell[0] === 0xFF && ((data = spell[1]) instanceof Array) && data.length === 2 &&
  4723.                                         data[0][0] === 0x20C && data[1][0] === type && data[1][2] == null &&
  4724.                                         String(data[1][1]) === sArg && String(data[0][2]) === sScope)
  4725.                                 {
  4726.                                         idx = i;
  4727.                                         return true;
  4728.                                 }
  4729.                                 return (spell[0] & 0x200) !== 0;
  4730.                         } : function (spell, i) {
  4731.                                 if (spell[0] === type && String(spell[1]) === sArg && String(spell[2]) === sScope) {
  4732.                                         idx = i;
  4733.                                         return true;
  4734.                                 }
  4735.                                 return (spell[0] & 0x200) !== 0;
  4736.                         });
  4737.                 } else {
  4738.                         spells[1] = [];
  4739.                 }
  4740.                 if (typeof idx !== 'undefined') {
  4741.                         spells[1].splice(idx, 1);
  4742.                 } else if (scope && isNeg) {
  4743.                         spells[1].splice(0, 0, [0xFF, [[0x20C, '', scope], [type, arg, void 0]], void 0]);
  4744.                 } else {
  4745.                         spells[1].splice(0, 0, [type, arg, scope]);
  4746.                 }
  4747.                 this.update(spells, true, true);
  4748.                 idx = null;
  4749.         }
  4750. };
  4751.  
  4752. function SpellsCodegen(sList) {
  4753.         this._line = 1;
  4754.         this._col = 1;
  4755.         this._sList = sList;
  4756.         this.hasError = false;
  4757. }
  4758. SpellsCodegen.prototype = {
  4759.         TYPE_UNKNOWN: 0,
  4760.         TYPE_ANDOR: 1,
  4761.         TYPE_NOT: 2,
  4762.         TYPE_SPELL: 3,
  4763.         TYPE_PARENTHESES: 4,
  4764.         TYPE_REPLACER: 5,
  4765.  
  4766.         generate: function () {
  4767.                 return this._sList ? this._generate(this._sList, false) : null;
  4768.         },
  4769.         get error() {
  4770.                 if (!this.hasError) {
  4771.                         return '';
  4772.                 }
  4773.                 return (this._errMsgArg ? this._errMsg.replace('%s', this._errMsgArg) : this._errMsg) +
  4774.                         Lng.seRow[lang] + this._line + Lng.seCol[lang] + this._col + ')';
  4775.         },
  4776.  
  4777.         _errMsg: '',
  4778.         _errMsgArg: null,
  4779.         _generate: function (sList, inParens) {
  4780.                 var res, name, i = 0,
  4781.                         len = sList.length,
  4782.                         spells = [],
  4783.                         reps = [],
  4784.                         outreps = [],
  4785.                         lastType = this.TYPE_UNKNOWN,
  4786.                         hasReps = false;
  4787.                 for (; i < len; i++, this._col++) {
  4788.                         switch (sList[i]) {
  4789.                         case '\n':
  4790.                                 this._line++;
  4791.                                 this._col = 0;
  4792.                         case '\r':
  4793.                         case ' ': continue;
  4794.                         case '#':
  4795.                                 name = '';
  4796.                                 i++;
  4797.                                 this._col++;
  4798.                                 while ((sList[i] >= 'a' && sList[i] <= 'z') || (sList[i] >= 'A' && sList[i] <= 'Z')) {
  4799.                                         name += sList[i].toLowerCase();
  4800.                                         i++;
  4801.                                         this._col++;
  4802.                                 }
  4803.                                 if (name === 'rep' || name === 'outrep') {
  4804.                                         if (!hasReps) {
  4805.                                                 if (inParens) {
  4806.                                                         this._col -= 1 + name.length;
  4807.                                                         this._setError(Lng.seRepsInParens[lang], '#' + name);
  4808.                                                         return null;
  4809.                                                 }
  4810.                                                 if (lastType === this.TYPE_ANDOR || lastType === this.TYPE_NOT) {
  4811.                                                         i -= 1 + name.length;
  4812.                                                         this._col -= 1 + name.length;
  4813.                                                         lookBack:
  4814.                                                         while (i >= 0) {
  4815.                                                                 switch (sList[i]) {
  4816.                                                                 case '\n':
  4817.                                                                 case '\r':
  4818.                                                                 case ' ':
  4819.                                                                         i--;
  4820.                                                                         this._col--;
  4821.                                                                         break;
  4822.                                                                 default:
  4823.                                                                         break lookBack;
  4824.                                                                 }
  4825.                                                         }
  4826.                                                         this._setError(Lng.seOpInReps[lang], sList[i]);
  4827.                                                         return null;
  4828.                                                 }
  4829.                                                 hasReps = true;
  4830.                                         }
  4831.                                         res = this._doRep(name, sList.substr(i));
  4832.                                         if (!res) {
  4833.                                                 return null;
  4834.                                         }
  4835.                                         if (name === 'rep') {
  4836.                                                 reps.push(res[1]);
  4837.                                         } else {
  4838.                                                 outreps.push(res[1]);
  4839.                                         }
  4840.                                         i += res[0] - 1;
  4841.                                         this._col += res[0] - 1;
  4842.                                         lastType = this.TYPE_REPLACER;
  4843.                                 } else {
  4844.                                         if (lastType === this.TYPE_SPELL || lastType === this.TYPE_PARENTHESES) {
  4845.                                                 this._setError(Lng.seMissOp[lang], null);
  4846.                                                 return null;
  4847.                                         }
  4848.                                         res = this._doSpell(name, sList.substr(i), lastType === this.TYPE_NOT)
  4849.                                         if (!res) {
  4850.                                                 return null;
  4851.                                         }
  4852.                                         i += res[0] - 1;
  4853.                                         this._col += res[0] - 1;
  4854.                                         spells.push(res[1]);
  4855.                                         lastType = this.TYPE_SPELL;
  4856.                                 }
  4857.                                 break;
  4858.                         case '(':
  4859.                                 if (hasReps) {
  4860.                                         this._setError(Lng.seUnexpChar[lang], '(');
  4861.                                         return null;
  4862.                                 }
  4863.                                 if (lastType === this.TYPE_SPELL || lastType === this.TYPE_PARENTHESES) {
  4864.                                         this._setError(Lng.seMissOp[lang], null);
  4865.                                         return null;
  4866.                                 }
  4867.                                 res = this._generate(sList.substr(i + 1), true);
  4868.                                 if (!res) {
  4869.                                         return null;
  4870.                                 }
  4871.                                 i += res[0] + 1;
  4872.                                 spells.push([lastType === this.TYPE_NOT ? 0x1FF : 0xFF, res[1]]);
  4873.                                 lastType = this.TYPE_PARENTHESES;
  4874.                                 break;
  4875.                         case '|':
  4876.                         case '&':
  4877.                                 if (hasReps) {
  4878.                                         this._setError(Lng.seUnexpChar[lang], sList[i]);
  4879.                                         return null;
  4880.                                 }
  4881.                                 if (lastType !== this.TYPE_SPELL && lastType !== this.TYPE_PARENTHESES) {
  4882.                                         this._setError(Lng.seMissSpell[lang], null);
  4883.                                         return null;
  4884.                                 }
  4885.                                 if (sList[i] === '&') {
  4886.                                         spells[spells.length - 1][0] |= 0x200;
  4887.                                 }
  4888.                                 lastType = this.TYPE_ANDOR;
  4889.                                 break;
  4890.                         case '!':
  4891.                                 if (hasReps) {
  4892.                                         this._setError(Lng.seUnexpChar[lang], '!');
  4893.                                         return null;
  4894.                                 }
  4895.                                 if (lastType !== this.TYPE_ANDOR && lastType !== this.TYPE_UNKNOWN) {
  4896.                                         this._setError(Lng.seMissOp[lang], null);
  4897.                                         return null;
  4898.                                 }
  4899.                                 lastType = this.TYPE_NOT;
  4900.                                 break;
  4901.                         case ')':
  4902.                                 if (hasReps) {
  4903.                                         this._setError(Lng.seUnexpChar[lang], ')');
  4904.                                         return null;
  4905.                                 }
  4906.                                 if (lastType === this.TYPE_ANDOR || lastType === this.TYPE_NOT) {
  4907.                                         this._setError(Lng.seMissSpell[lang], null);
  4908.                                         return null;
  4909.                                 }
  4910.                                 if (inParens) {
  4911.                                         return [i, spells];
  4912.                                 }
  4913.                         default:
  4914.                                 this._setError(Lng.seUnexpChar[lang], sList[i]);
  4915.                                 return null;
  4916.                         }
  4917.                 }
  4918.                 if (inParens) {
  4919.                         this._setError(Lng.seMissClBkt[lang], null);
  4920.                         return null;
  4921.                 }
  4922.                 if (lastType !== this.TYPE_SPELL && lastType !== this.TYPE_PARENTHESES &&
  4923.                    lastType !== this.TYPE_REPLACER)
  4924.                 {
  4925.                         this._setError(Lng.seMissSpell[lang], null);
  4926.                         return null;
  4927.                 }
  4928.                 if (reps.length === 0) {
  4929.                         reps = false;
  4930.                 }
  4931.                 if (outreps.length === 0) {
  4932.                         outreps = false;
  4933.                 }
  4934.                 return [spells, reps, outreps];
  4935.         },
  4936.         _getScope: function (str) {
  4937.                 var scope, m = str.match(/^\[([a-z0-9\/]+)(?:(,)|,(\s*[0-9]+))?\]/);
  4938.                 if (m) {
  4939.                         return [m[0].length, [m[1], m[3] ? m[3] : m[2] ? -1 : false]];
  4940.                 }
  4941.                 return null;
  4942.         },
  4943.         _getRegex: function (str, haveComma) {
  4944.                 var val, m = str.match(/^\((\/.*?[^\\]\/[igm]*)(?:\)|\s*(,))/);
  4945.                 if (m) {
  4946.                         if (haveComma !== !!m[2]) {
  4947.                                 return null;
  4948.                         }
  4949.                         val = m[1];
  4950.                         try {
  4951.                                 toRegExp(val, true);
  4952.                         } catch(e) {
  4953.                                 this._setError(Lng.seErrRegex[lang], val);
  4954.                                 return null;
  4955.                         }
  4956.                         return [m[0].length, val];
  4957.                 }
  4958.                 return null;
  4959.         },
  4960.         _getText: function (str, haveBracket) {
  4961.                 var m = str.match(/^(\()?(.*?[^\\])\)/);
  4962.                 if (m) {
  4963.                         if (haveBracket !== !!m[1]) {
  4964.                                 return null;
  4965.                         }
  4966.                         return [m[0].length, m[2].replace(/\\\)/g, ')')];
  4967.                 }
  4968.                 return null;
  4969.         },
  4970.         _doRep: function (name, str) {
  4971.                 var regex, val, scope = this._getScope(str);
  4972.                 if (scope) {
  4973.                         str = str.substring(scope[0]);
  4974.                 } else {
  4975.                         scope = [0, ['', '']];
  4976.                 }
  4977.                 regex = this._getRegex(str, true);
  4978.                 if (regex) {
  4979.                         str = str.substring(regex[0]);
  4980.                         if (str[0] === ')') {
  4981.                                 return [regex[0] + scope[0] + 1, [scope[1][0], scope[1][1], regex[1], '']];
  4982.                         }
  4983.                         val = this._getText(str, false);
  4984.                         if (val) {
  4985.                                 return [val[0] + regex[0] + scope[0], [scope[1][0], scope[1][1], regex[1], val[1]]];
  4986.                         }
  4987.                 }
  4988.                 this._setError(Lng.seSyntaxErr[lang], name);
  4989.                 return null;
  4990.         },
  4991.         _doSpell: function (name, str, isNeg) {
  4992.                 var scope, m, spellType, val, temp, i = 0,
  4993.                         scope = null,
  4994.                         spellIdx = Spells.names.indexOf(name);
  4995.                 if (spellIdx === -1) {
  4996.                         this._setError(Lng.seUnknown[lang], name);
  4997.                         return null;
  4998.                 }
  4999.                 temp = this._getScope(str);
  5000.                 if (temp) {
  5001.                         i += temp[0];
  5002.                         str = str.substring(temp[0]);
  5003.                         scope = temp[1];
  5004.                 }
  5005.                 spellType = isNeg ? spellIdx | 0x100 : spellIdx;
  5006.                 if (str[0] !== '(' || str[1] === ')') {
  5007.                         if (Spells.needArg[spellIdx]) {
  5008.                                 this._setError(Lng.seMissArg[lang], name);
  5009.                                 return null;
  5010.                         }
  5011.                         return [str[0] === '(' ? i + 2 : i, [spellType, spellIdx === 14 ? 0x3F : '', scope]];
  5012.                 }
  5013.                 switch (spellIdx) {
  5014.                 // #ihash
  5015.                 case 4:
  5016.                         m = str.match(/^\((\d+)\)/);
  5017.                         if (+m[1] === +m[1]) {
  5018.                                 return [i + m[0].length, [spellType, +m[1], scope]];
  5019.                         }
  5020.                         break;
  5021.                 // #img
  5022.                 case 8:
  5023.                         m = str.match(/^\(([><=])(?:(\d+(?:\.\d+)?)(?:-(\d+(?:\.\d+)?))?)?(?:@(\d+)(?:-(\d+))?x(\d+)(?:-(\d+))?)?\)/);
  5024.                         if (m && (m[2] || m[4])) {
  5025.                                 return [i + m[0].length, [spellType, [
  5026.                                         m[1] === '=' ? 0 : m[1] === '<' ? 1 : 2,
  5027.                                         m[2] && [+m[2], m[3] ? +m[3] : +m[2]],
  5028.                                         m[4] && [+m[4], m[5] ? +m[5] : +m[4], +m[6], m[7] ? +m[7] : +m[6]]
  5029.                                 ], scope]];
  5030.                         }
  5031.                         break;
  5032.                 // #wipe
  5033.                 case 14:
  5034.                         m = str.match(/^\(([a-z, ]+)\)/);
  5035.                         if (m) {
  5036.                                 val = m[1].split(/, */).reduce(function (val, str) {
  5037.                                         switch (str) {
  5038.                                         case 'samelines': return val |= 1;
  5039.                                         case 'samewords': return val |= 2;
  5040.                                         case 'longwords': return val |= 4;
  5041.                                         case 'symbols': return val |= 8;
  5042.                                         case 'capslock': return val |= 16;
  5043.                                         case 'numbers': return val |= 32;
  5044.                                         case 'whitespace': return val |= 64;
  5045.                                         default: return -1;
  5046.                                         }
  5047.                                 }, 0);
  5048.                                 if (val !== -1) {
  5049.                                         return [i + m[0].length, [spellType, val, scope]];
  5050.                                 }
  5051.                         }
  5052.                         break;
  5053.                 // #tlen, #num
  5054.                 case 11:
  5055.                 case 15:
  5056.                         m = str.match(/^\(([\d-, ]+)\)/);
  5057.                         if (m) {
  5058.                                 m[1].split(/, */).forEach(function (v) {
  5059.                                         if (v.contains('-')) {
  5060.                                                 var nums = v.split('-');
  5061.                                                 nums[0] = +nums[0];
  5062.                                                 nums[1] = +nums[1];
  5063.                                                 this[1].push(nums);
  5064.                                         } else {
  5065.                                                 this[0].push(+v);
  5066.                                         }
  5067.                                 }, val = [[], []]);
  5068.                                 return [i + m[0].length, [spellType, val, scope]];
  5069.                         }
  5070.                         break;
  5071.                 // #exp, #exph, #imgn, #subj, #video
  5072.                 case 1:
  5073.                 case 2:
  5074.                 case 3:
  5075.                 case 5:
  5076.                 case 13:
  5077.                         temp = this._getRegex(str, false);
  5078.                         if (temp) {
  5079.                                 return [i + temp[0], [spellType, temp[1], scope]];
  5080.                         }
  5081.                         break;
  5082.                 // #sage, #op, #all, #trip, #name, #words, #vauthor
  5083.                 default:
  5084.                         temp = this._getText(str, true);
  5085.                         if (temp) {
  5086.                                 return [i + temp[0], [spellType, spellIdx === 0 ? temp[1].toLowerCase() : temp[1], scope]];
  5087.                         }
  5088.                 }
  5089.                 this._setError(Lng.seSyntaxErr[lang], name);
  5090.                 return null;
  5091.         },
  5092.         _setError: function (msg, arg) {
  5093.                 this.hasError = true;
  5094.                 this._errMsg = msg;
  5095.                 this._errMsgArg = arg;
  5096.         }
  5097. };
  5098.  
  5099. function SpellsInterpreter(post, spells, length) {
  5100.         this._post = post;
  5101.         this._ctx = [length, spells, 0];
  5102.         this._deep = 0;
  5103. }
  5104. SpellsInterpreter.prototype = {
  5105.         hasNumSpell: false,
  5106.         postHidden: false,
  5107.         run: function () {
  5108.                 var rv, type, val, i = this._ctx.pop(),
  5109.                         scope = this._ctx.pop(),
  5110.                         len = this._ctx.pop();
  5111.                 while (true) {
  5112.                         if (i < len) {
  5113.                                 type = scope[i][0] & 0xFF;
  5114.                                 if (type === 0xFF) {
  5115.                                         this._deep++;
  5116.                                         this._ctx.push(len, scope, i);
  5117.                                         scope = scope[i][1];
  5118.                                         len = scope.length;
  5119.                                         i = 0;
  5120.                                         continue;
  5121.                                 }
  5122.                                 val = this._runSpell(type, scope[i][1]);
  5123.                                 if (this._asyncWait) {
  5124.                                         this._ctx.push(len, scope, i, scope[i][0]);
  5125.                                         return false;
  5126.                                 }
  5127.                                 rv = this._checkRes(scope[i][0], val);
  5128.                                 if (rv === null) {
  5129.                                         i++;
  5130.                                         continue;
  5131.                                 }
  5132.                                 this._lastSpellIdx = i;
  5133.                         } else {
  5134.                                 this._lastSpellIdx = i -= 1;
  5135.                                 rv = false;
  5136.                         }
  5137.                         if (this._deep !== 0) {
  5138.                                 this._deep--;
  5139.                                 i = this._ctx.pop();
  5140.                                 scope = this._ctx.pop();
  5141.                                 len = this._ctx.pop();
  5142.                                 rv = this._checkRes(scope[i][0], rv);
  5143.                                 if (rv === null) {
  5144.                                         i++;
  5145.                                         continue;
  5146.                                 }
  5147.                         }
  5148.                         if (rv) {
  5149.                                 this._post.spellHide(this._getMsg(scope[i]));
  5150.                                 this.postHidden = true;
  5151.                         } else if (!this._post.deleted) {
  5152.                                 sVis[this._post.count] = 1;
  5153.                         }
  5154.                         return true;
  5155.                 }
  5156.         },
  5157.         setEndFn: function (Fn) {
  5158.                 this._endFn = Fn;
  5159.         },
  5160.  
  5161.         _asyncWait: false,
  5162.         _endFn: null,
  5163.         _lastSpellIdx: 0,
  5164.         _wipeMsg: '',
  5165.         _asyncContinue: function (val) {
  5166.                 this._asyncWait = false;
  5167.                 var temp, rv = this._checkRes(this._ctx.pop(), val);
  5168.                 if (rv === null) {
  5169.                         if (!this.run()) {
  5170.                                 return;
  5171.                         }
  5172.                 } else if (rv) {
  5173.                         temp = this._ctx.pop();
  5174.                         this._post.spellHide(this._getMsg(this._ctx.pop()[temp - 1]));
  5175.                         this.postHidden = true;
  5176.                 } else if (!this._post.deleted) {
  5177.                         sVis[this._post.count] = 1;
  5178.                 }
  5179.                 if (this._endFn) {
  5180.                         this._endFn(this);
  5181.                 }
  5182.         },
  5183.         _checkRes: function (flags, val) {
  5184.                 if ((flags & 0x100) !== 0) {
  5185.                         val = !val;
  5186.                 }
  5187.                 if ((flags & 0x200) !== 0) {
  5188.                         if (!val) {
  5189.                                 return false;
  5190.                         }
  5191.                 } else if (val) {
  5192.                         return true;
  5193.                 }
  5194.                 return null;
  5195.         },
  5196.         _getMsg: function (spell) {
  5197.                 var neg = spell[0] & 0x100,
  5198.                         type = spell[0] & 0xFF,
  5199.                         val = spell[1];
  5200.                 if (type === 0xFF) {
  5201.                         return this._getMsg(val[this._lastSpellIdx]);
  5202.                 }
  5203.                 if (type === 14) {
  5204.                         return (neg ? '!#wipe' : '#wipe') + (Spells._lastWipeMsg ? ': ' + Spells._lastWipeMsg : '');
  5205.                 } else {
  5206.                         return Spells.decompileSpell(type, neg, val, spell[2]);
  5207.                 }
  5208.         },
  5209.         _runSpell: function (spellId, val) {
  5210.                 switch (spellId) {
  5211.                 case 0: return this._words(val);
  5212.                 case 1: return this._exp(val);
  5213.                 case 2: return this._exph(val);
  5214.                 case 3: return this._imgn(val);
  5215.                 case 4: return this._ihash(val);
  5216.                 case 5: return this._subj(val);
  5217.                 case 6: return this._name(val);
  5218.                 case 7: return this._trip(val);
  5219.                 case 8: return this._img(val);
  5220.                 case 9: return this._sage(val);
  5221.                 case 10: return this._op(val);
  5222.                 case 11: return this._tlen(val);
  5223.                 case 12: return this._all(val);
  5224.                 case 13: return this._video(val);
  5225.                 case 14: return this._wipe(val);
  5226.                 case 15:
  5227.                         this.hasNumSpell = true;
  5228.                         return this._num(val);
  5229.                 case 16: return this._vauthor(val);
  5230.                 }
  5231.         },
  5232.         _words: function (val) {
  5233.                 return this._post.text.toLowerCase().contains(val) || this._post.subj.toLowerCase().contains(val);
  5234.         },
  5235.         _exp: function (val) {
  5236.                 return val.test(this._post.text);
  5237.         },
  5238.         _exph: function (val) {
  5239.                 return val.test(this._post.html);
  5240.         },
  5241.         _imgn: function (val) {
  5242.                 for (var i = 0, imgs = this._post.images, len = imgs.length; i < len; ++i) {
  5243.                         if (val.test(imgs[i].info)) {
  5244.                                 return true;
  5245.                         }
  5246.                 }
  5247.                 return false;
  5248.         },
  5249.         _ihash: function (val) {
  5250.                 for (var i = 0, imgs = this._post.images, len = imgs.length; i < len; ++i) {
  5251.                         if (imgs[i].hash === val) {
  5252.                                 return true;
  5253.                         }
  5254.                 }
  5255.                 if (this._post.hashImgsBusy === 0) {
  5256.                         return false;
  5257.                 }
  5258.                 this._post.hashHideFun = this._ihash_helper.bind(this, val);
  5259.                 this._asyncWait = true;
  5260.                 return false;
  5261.         },
  5262.         _ihash_helper: function (val, hash) {
  5263.                 if (val === hash) {
  5264.                         this._post.hashHideFun = null;
  5265.                         this._asyncContinue(true);
  5266.                 } else if (this._post.hashImgsBusy === 0) {
  5267.                         this.hashHideFun = null;
  5268.                         this._asyncContinue(false);
  5269.                 }
  5270.         },
  5271.         _subj: function (val) {
  5272.                 var pSubj = this._post.subj;
  5273.                 return pSubj ? !val || val.test(pSubj) : false;
  5274.         },
  5275.         _name: function (val) {
  5276.                 var pName = this._post.posterName;
  5277.                 return pName ? !val || pName.contains(val) : false;
  5278.         },
  5279.         _trip: function (val) {
  5280.                 var pTrip = this._post.posterTrip;
  5281.                 return pTrip ? !val || pTrip.contains(val) : false;
  5282.         },
  5283.         _img: function (val) {
  5284.                 var temp, w, h, hide, img, i, imgs = this._post.images,
  5285.                         len = imgs.length;
  5286.                 if (!val) {
  5287.                         return len !== 0;
  5288.                 }
  5289.                 for (i = 0; i < len; ++i) {
  5290.                         img = imgs[i];
  5291.                         if (temp = val[1]) {
  5292.                                 w = img.weight;
  5293.                                 switch (val[0]) {
  5294.                                 case 0: hide = w >= temp[0] && w <= temp[1]; break;
  5295.                                 case 1: hide = w < temp[0]; break;
  5296.                                 case 2: hide = w > temp[0];
  5297.                                 }
  5298.                                 if (!hide) {
  5299.                                         continue;
  5300.                                 } else if (!val[2]) {
  5301.                                         return true;
  5302.                                 }
  5303.                         }
  5304.                         if (temp = val[2]) {
  5305.                                 w = img.width;
  5306.                                 h = img.height;
  5307.                                 switch (val[0]) {
  5308.                                 case 0:
  5309.                                         if (w >= temp[0] && w <= temp[1] && h >= temp[2] && h <= temp[3]) {
  5310.                                                 return true
  5311.                                         }
  5312.                                         break;
  5313.                                 case 1:
  5314.                                         if (w < temp[0] && h < temp[3]) {
  5315.                                                 return true
  5316.                                         }
  5317.                                         break;
  5318.                                 case 2:
  5319.                                         if (w > temp[0] && h > temp[3]) {
  5320.                                                 return true
  5321.                                         }
  5322.                                 }
  5323.                         }
  5324.                 }
  5325.                 return false;
  5326.         },
  5327.         _sage: function (val) {
  5328.                 return this._post.sage;
  5329.         },
  5330.         _op: function (val) {
  5331.                 return this._post.isOp;
  5332.         },
  5333.         _tlen: function (val) {
  5334.                 var text = this._post.text;
  5335.                 return !val ? !!text : this._tlenNum_helper(val, text.replace(/\n/g, '').length);
  5336.         },
  5337.         _all: function (val) {
  5338.                 return true;
  5339.         },
  5340.         _video: function (val) {
  5341.                 return this._videoVauthor(val, false);
  5342.         },
  5343.         _wipe: function (val) {
  5344.                 var arr, len, i, j, n, x, keys, pop, capsw, casew, _txt, txt = this._post.text;
  5345.                 // (1 << 0): samelines
  5346.                 if (val & 1) {
  5347.                         arr = txt.replace(/>/g, '').split(/\s*\n\s*/);
  5348.                         if ((len = arr.length) > 5) {
  5349.                                 arr.sort();
  5350.                                 for (i = 0, n = len / 4; i < len;) {
  5351.                                         x = arr[i];
  5352.                                         j = 0;
  5353.                                         while (arr[i++] === x) {
  5354.                                                 j++;
  5355.                                         }
  5356.                                         if (j > 4 && j > n && x) {
  5357.                                                 this._wipeMsg = 'same lines: "' + x.substr(0, 20) + '" x' + (j + 1);
  5358.                                                 return true;
  5359.                                         }
  5360.                                 }
  5361.                         }
  5362.                 }
  5363.                 // (1 << 1): samewords
  5364.                 if (val & 2) {
  5365.                         arr = txt.replace(/[\s\.\?\!,>]+/g, ' ').toUpperCase().split(' ');
  5366.                         if ((len = arr.length) > 3) {
  5367.                                 arr.sort();
  5368.                                 for (i = 0, n = len / 4, keys = 0, pop = 0; i < len; keys++) {
  5369.                                         x = arr[i];
  5370.                                         j = 0;
  5371.                                         while (arr[i++] === x) {
  5372.                                                 j++;
  5373.                                         }
  5374.                                         if (len > 25) {
  5375.                                                 if (j > pop && x.length > 2) {
  5376.                                                         pop = j;
  5377.                                                 }
  5378.                                                 if (pop >= n) {
  5379.                                                         this._wipeMsg = 'same words: "' + x.substr(0, 20) + '" x' + (pop + 1);
  5380.                                                         return true;
  5381.                                                 }
  5382.                                         }
  5383.                                 }
  5384.                                 x = keys / len;
  5385.                                 if (x < 0.25) {
  5386.                                         this._wipeMsg = 'uniq words: ' + (x * 100).toFixed(0) + '%';
  5387.                                         return true;
  5388.                                 }
  5389.                         }
  5390.                 }
  5391.                 // (1 << 2): longwords
  5392.                 if (val & 4) {
  5393.                         arr = txt.replace(/https*:\/\/.*?(\s|$)/g, '').replace(/[\s\.\?!,>:;-]+/g, ' ').split(' ');
  5394.                         if (arr[0].length > 50 || ((len = arr.length) > 1 && arr.join('').length / len > 10)) {
  5395.                                 this._wipeMsg = 'long words';
  5396.                                 return true;
  5397.                         }
  5398.                 }
  5399.                 // (1 << 3): symbols
  5400.                 if (val & 8) {
  5401.                         _txt = txt.replace(/\s+/g, '');
  5402.                         if ((len = _txt.length) > 30 &&
  5403.                                 (x = _txt.replace(/[0-9a-zа-я\.\?!,]/ig, '').length / len) > 0.4)
  5404.                         {
  5405.                                 this._wipeMsg = 'specsymbols: ' + (x * 100).toFixed(0) + '%';
  5406.                                 return true;
  5407.                         }
  5408.                 }
  5409.                 // (1 << 4): capslock
  5410.                 if (val & 16) {
  5411.                         arr = txt.replace(/[\s\.\?!;,-]+/g, ' ').trim().split(' ');
  5412.                         if ((len = arr.length) > 4) {
  5413.                                 for (i = 0, n = 0, capsw = 0, casew = 0; i < len; i++) {
  5414.                                         x = arr[i];
  5415.                                         if ((x.match(/[a-zа-я]/ig) || []).length < 5) {
  5416.                                                 continue;
  5417.                                         }
  5418.                                         if ((x.match(/[A-ZА-Я]/g) || []).length > 2) {
  5419.                                                 casew++;
  5420.                                         }
  5421.                                         if (x === x.toUpperCase()) {
  5422.                                                 capsw++;
  5423.                                         }
  5424.                                         n++;
  5425.                                 }
  5426.                                 if (capsw / n >= 0.3 && n > 4) {
  5427.                                         this._wipeMsg = 'CAPSLOCK: ' + capsw / arr.length * 100 + '%';
  5428.                                         return true;
  5429.                                 } else if (casew / n >= 0.3 && n > 8) {
  5430.                                         this._wipeMsg = 'cAsE words: ' + casew / arr.length * 100 + '%';
  5431.                                         return true;
  5432.                                 }
  5433.                         }
  5434.                 }
  5435.                 // (1 << 5): numbers
  5436.                 if (val & 32) {
  5437.                         _txt = txt.replace(/\s+/g, ' ').replace(/>>\d+|https*:\/\/.*?(?: |$)/g, '');
  5438.                         if ((len = _txt.length) > 30 && (x = (len - _txt.replace(/\d/g, '').length) / len) > 0.4) {
  5439.                                 this._wipeMsg = 'numbers: ' + Math.round(x * 100) + '%';
  5440.                                 return true;
  5441.                         }
  5442.                 }
  5443.                 // (1 << 5): whitespace
  5444.                 if (val & 64) {
  5445.                         if (/(?:\n\s*){5}/i.test(txt)) {
  5446.                                 this._wipeMsg = 'whitespace';
  5447.                                 return true;
  5448.                         }
  5449.                 }
  5450.                 return false;
  5451.         },
  5452.         _num: function (val) {
  5453.                 return this._tlenNum_helper(val, this._post.count + 1);
  5454.         },
  5455.         _tlenNum_helper: function (val, num) {
  5456.                 var i, arr;
  5457.                 for (arr = val[0], i = arr.length - 1; i >= 0; --i) {
  5458.                         if (arr[i] === num) {
  5459.                                 return true;
  5460.                         }
  5461.                 }
  5462.                 for (arr = val[1], i = arr.length - 1; i >= 0; --i) {
  5463.                         if (num >= arr[i][0] && num <= arr[i][1]) {
  5464.                                 return true;
  5465.                         }
  5466.                 }
  5467.                 return false;
  5468.         },
  5469.         _vauthor: function (val) {
  5470.                 return this._videoVauthor(val, true);
  5471.         },
  5472.         _videoVauthor: function (val, isAuthorSpell) {
  5473.                 if (!val) {
  5474.                         return !!this._post.hasYTube;
  5475.                 }
  5476.                 if (!this._post.hasYTube || !Cfg.YTubeTitles) {
  5477.                         return false;
  5478.                 }
  5479.                 var i, data, len;
  5480.                 for (i = 0, data = this._post.ytData, len = data.length; i < len; ++i) {
  5481.                         if (isAuthorSpell ? val === data[i][1] : val.test(data[i][0])) {
  5482.                                 return true;
  5483.                         }
  5484.                 }
  5485.                 if (this._post.ytLinksLoading === 0) {
  5486.                         return false;
  5487.                 }
  5488.                 this._post.ytHideFun = this._videoVauthor_helper.bind(this, isAuthorSpell, val);
  5489.                 this._asyncWait = true;
  5490.                 return false;
  5491.         },
  5492.         _videoVauthor_helper: function (isAuthorSpell, val, data) {
  5493.                 if (isAuthorSpell ? val === data[1] : val.test(data[0])) {
  5494.                         this._post.ytHideFun = null;
  5495.                         this._asyncContinue(true);
  5496.                 } else if (this._post.ytLinksLoading === 0) {
  5497.                         this._post.ytHideFun = null;
  5498.                         this._asyncContinue(false);
  5499.                 }
  5500.         }
  5501. }
  5502.  
  5503. function disableSpells() {
  5504.         closeAlert($id('de-alert-help-err-spell'));
  5505.         if (spells.enable) {
  5506.                 sVis = TNum ? '1'.repeat(firstThr.pcount).split('') : [];
  5507.                 for (var post = firstThr.op; post; post = post.next) {
  5508.                         if (post.spellHidden && !post.userToggled) {
  5509.                                 post.spellUnhide();
  5510.                         }
  5511.                 }
  5512.         }
  5513. }
  5514.  
  5515. function toggleSpells() {
  5516.         var temp, fld = $id('de-spell-edit'),
  5517.                 val = fld.value;
  5518.         if (val && (temp = spells.parseText(val))) {
  5519.                 disableSpells();
  5520.                 spells.setSpells(temp, true);
  5521.                 fld.value = spells.list;
  5522.         } else {
  5523.                 if (val) {
  5524.                         locStorage['__de-spells'] = '{"hide": false, "data": null}';
  5525.                 } else {
  5526.                         disableSpells();
  5527.                         spells.disable();
  5528.                         saveCfg('spells', '');
  5529.                         locStorage['__de-spells'] = '{"hide": false, "data": ""}';
  5530.                 }
  5531.                 locStorage.removeItem('__de-spells');
  5532.                 $q('input[info="hideBySpell"]', doc).checked = spells.enable = false;
  5533.         }
  5534. }
  5535.  
  5536. function addSpell(type, arg, isNeg) {
  5537.         var temp, fld = $id('de-spell-edit'),
  5538.                 val = fld && fld.value,
  5539.                 chk = $q('input[info="hideBySpell"]', doc);
  5540.         if (!val || (temp = spells.parseText(val))) {
  5541.                 disableSpells();
  5542.                 spells.addSpell(type, arg, TNum ? [brd, TNum] : null, isNeg, temp);
  5543.                 val = spells.list;
  5544.                 saveCfg('hideBySpell', !!val);
  5545.                 if (val) {
  5546.                         for (var post = firstThr.op; post; post = post.next) {
  5547.                                 spells.check(post);
  5548.                         }
  5549.                         spells.end(savePosts);
  5550.                 } else {
  5551.                         saveCfg('spells', '');
  5552.                         spells.enable = false;
  5553.                 }
  5554.                 if (fld) {
  5555.                         chk.checked = !!(fld.value = val);
  5556.                 }
  5557.                 return;
  5558.         }
  5559.         spells.enable = false;
  5560.         if (chk) {
  5561.                 chk.checked = false;
  5562.         }
  5563. }
  5564.  
  5565.  
  5566. // POSTFORM
  5567. // ===========================================================================================================
  5568.  
  5569. function PostForm(form, ignoreForm, init, dc) {
  5570.         this.oeForm = $q('form[name="oeform"], form[action*="paint"]', dc);
  5571.         if (!ignoreForm && !form) {
  5572.                 if (this.oeForm) {
  5573.                         ajaxLoad(aib.getThrdUrl(brd, aib.getTNum(dForm)), false, function (dc, xhr) {
  5574.                                 pr = new PostForm($q(aib.qPostForm, dc), true, init, dc);
  5575.                         }, function (eCode, eMsg, xhr) {
  5576.                                 pr = new PostForm(null, true, init, dc);
  5577.                         });
  5578.                 } else {
  5579.                         this.form = null;
  5580.                 }
  5581.                 return;
  5582.         }
  5583.         function $x(path, root) {
  5584.                 return dc.evaluate(path, root, null, 8, null).singleNodeValue;
  5585.         }
  5586.         var p = './/tr[not(contains(@style,"none"))]//input[not(@type="hidden") and ';
  5587.         this.tNum = TNum;
  5588.         this.form = form;
  5589.         this.cap = $q('input[type="text"][name*="aptcha"], div[id*="captcha"]', form);
  5590.         this.txta = $q('tr:not([style*="none"]) textarea:not([style*="display:none"])', form);
  5591.         this.subm = $q('tr input[type="submit"]', form);
  5592.         this.file = $q('tr input[type="file"]', form);
  5593.         if (this.file) {
  5594.                 this.fileTd = $parent(this.file, 'TD');
  5595.         }
  5596.         this.passw = $q('tr input[type="password"]', form);
  5597.         this.dpass = $q('input[type="password"], input[name="password"]', dForm);
  5598.         this.name = $x(p + '(@name="field1" or @name="name" or @name="internal_n" or @name="nya1" or @name="akane")]', form);
  5599.         this.mail = $x(p + (
  5600.                         aib._410 ? '@name="sage"]' :
  5601.                         '(@name="field2" or @name="em" or @name="sage" or @name="email" or @name="nabiki" or @name="dont_bump")]'
  5602.                 ), form);
  5603.         this.subj = $x(p + '(@name="field3" or @name="sub" or @name="subject" or @name="internal_s" or @name="nya3" or @name="kasumi")]', form);
  5604.         this.video = $q('tr input[name="video"], tr input[name="embed"]', form);
  5605.         this.gothr = aib.qPostRedir && (p = $q(aib.qPostRedir, form)) && $parent(p, 'TR');
  5606.         if (init) {
  5607.                 this._init();
  5608.         }
  5609. }
  5610. PostForm.setUserName = function () {
  5611.         var el = $q('input[info="nameValue"]', doc);
  5612.         if (el) {
  5613.                 saveCfg('nameValue', el.value);
  5614.         }
  5615.         pr.name.value = Cfg.userName ? Cfg.nameValue : '';
  5616. };
  5617. PostForm.setUserPassw = function () {
  5618.         var el = $q('input[info="passwValue"]', doc);
  5619.         if (el) {
  5620.                 saveCfg('passwValue', el.value);
  5621.         }
  5622.         (pr.dpass || {}).value = pr.passw.value = Cfg.passwValue;
  5623. };
  5624. PostForm.prototype = {
  5625.         fileObj: null,
  5626.         isHidden: false,
  5627.         isQuick: false,
  5628.         isTopForm: false,
  5629.         lastQuickPNum: -1,
  5630.         pForm: null,
  5631.         pArea: [],
  5632.         qArea: null,
  5633.         get fileImageTD() {
  5634.                 var val = $t(aib.tiny ? 'th' : 'td', $parent(this.txta, 'TR'));
  5635.                 val.innerHTML = '';
  5636.                 Object.defineProperty(this, 'fileImageTD', { value: val });
  5637.                 return val;
  5638.         },
  5639.         get rarInput() {
  5640.                 var val = doc.body.appendChild($new('input', {'type': 'file', 'style': 'display: none;'}, null))
  5641.                 Object.defineProperty(this, 'rarInput', { value: val });
  5642.                 return val;
  5643.         },
  5644.         addTextPanel: function () {
  5645.                 var i, len, tag, html, btns, tPanel = $id('de-txt-panel');
  5646.                 if (!Cfg.addTextBtns) {
  5647.                         $del(tPanel);
  5648.                         return;
  5649.                 }
  5650.                 if (!tPanel) {
  5651.                         tPanel = $new('span', {'id': 'de-txt-panel'}, {
  5652.                                 'click': this,
  5653.                                 'mouseover': this
  5654.                         });
  5655.                 }
  5656.                 tPanel.style.cssFloat = Cfg.txtBtnsLoc ? 'none' : 'right';
  5657.                 $after(Cfg.txtBtnsLoc ? $id('de-txt-resizer') || this.txta :
  5658.                         aib._420 ? $c('popup', this.form) : this.subm, tPanel);
  5659.                 for (html = '', i = 0, btns = aib.formButtons, len = btns.id.length; i < len; ++i) {
  5660.                         tag = btns.tag[i];
  5661.                         if (tag === '') {
  5662.                                 continue;
  5663.                         }
  5664.                         html += '<span id="de-btn-' + btns.id[i] + '" de-title="' + Lng.txtBtn[i][lang] +
  5665.                                 '" de-tag="' + tag + '"' + (btns.bb[i] ? 'de-bb' : '') + '>' + (
  5666.                                         Cfg.addTextBtns === 2 ?
  5667.                                                 (i === 0 ? '[ ' : '') + '<a class="de-abtn" href="#">' + btns.val[i] +
  5668.                                                 '</a>' + (i === len - 1 ? ' ]' : ' / ') :
  5669.                                         Cfg.addTextBtns === 3 ?
  5670.                                                 '<input type="button" value="' + btns.val[i] + '" style="font-weight: bold;">' : ''
  5671.                                 ) + '</span>';
  5672.                 }
  5673.                 tPanel.innerHTML = html;
  5674.         },
  5675.         delFilesUtils: function () {
  5676.                 for (var inp = this.fileObj; inp; inp = inp.next) {
  5677.                         inp.delUtils();
  5678.                 }
  5679.         },
  5680.         eventFiles: function (clear) {
  5681.                 var i, len, inp, els, el, last = null;
  5682.                 for (i = 0, els = $Q('input[type="file"]', this.fileTd), len = els.length; i < len; ++i) {
  5683.                         el = els[i];
  5684.                         inp = el.obj;
  5685.                         if (inp) {
  5686.                                 inp.prev = last;
  5687.                                 if (last) {
  5688.                                         last.next = inp;
  5689.                                 }
  5690.                                 last = inp;
  5691.                         } else {
  5692.                                 el.obj = last = new FileInput(this, el, last);
  5693.                                 last.init(false);
  5694.                                 if (clear && el.files && el.files.length !== 0) {
  5695.                                         last.clear();
  5696.                                 }
  5697.                         }
  5698.                 }
  5699.                 this.fileObj = els[0].obj;
  5700.         },
  5701.         handleEvent: function (e) {
  5702.                 var x, start, end, scrtop, id, len, val, el = e.target;
  5703.                 if (el.tagName !== 'SPAN') {
  5704.                         el = el.parentNode;
  5705.                 }
  5706.                 id = el.id;
  5707.                 if (id.startsWith('de-btn')) {
  5708.                         if (e.type === 'mouseover') {
  5709.                                 if (id === 'de-btn-quote') {
  5710.                                         quotetxt = $txtSelect();
  5711.                                 }
  5712.                                 x = -1;
  5713.                                 if (hKeys) {
  5714.                                         switch (id.substr(7)) {
  5715.                                         case 'bold': x = 12; break;
  5716.                                         case 'italic': x = 13; break;
  5717.                                         case 'strike': x = 14; break;
  5718.                                         case 'spoil': x = 15; break;
  5719.                                         case 'code': x = 16; break;
  5720.                                         }
  5721.                                 }
  5722.                                 KeyEditListener.setTitle(el, x);
  5723.                                 return;
  5724.                         }
  5725.                         x = pr.txta;
  5726.                         start = x.selectionStart;
  5727.                         end = x.selectionEnd;
  5728.                         if (id === 'de-btn-quote') {
  5729.                                 $txtInsert(x, '> ' + (start === end ? quotetxt : x.value.substring(start, end))
  5730.                                         .replace(/\n/gm, '\n> '));
  5731.                         } else {
  5732.                                 scrtop = x.scrollTop;
  5733.                                 val = this._wrapText(el.hasAttribute('de-bb'), el.getAttribute('de-tag'),
  5734.                                         x.value.substring(start, end));
  5735.                                 len = start + val[0];
  5736.                                 x.value = x.value.substr(0, start) + val[1] + x.value.substr(end);
  5737.                                 x.setSelectionRange(len, len);
  5738.                                 x.focus();
  5739.                                 x.scrollTop = scrtop;
  5740.                         }
  5741.                         $pd(e);
  5742.                         e.stopPropagation();
  5743.                 }
  5744.         },
  5745.         get isVisible() {
  5746.                 if (!this.isHidden && this.isTopForm && $q(':focus', this.pForm)) {
  5747.                         var cr = this.pForm.getBoundingClientRect();
  5748.                         return cr.bottom > 0 && cr.top < window.innerHeight;
  5749.                 }
  5750.                 return false;
  5751.         },
  5752.         get topCoord() {
  5753.                 return this.pForm.getBoundingClientRect().top;
  5754.         },
  5755.         showQuickReply: function (post, pNum, closeReply, isNumClick) {
  5756.                 var temp, tNum = post.tNum;
  5757.                 if (!this.isQuick) {
  5758.                         this.isQuick = true;
  5759.                         this.setReply(true, false);
  5760.                         $t('a', this._pBtn[+this.isTopForm]).className =
  5761.                                 'de-abtn de-parea-btn-' + (TNum ? 'reply' : 'thrd');
  5762.                         if (!TNum && !aib.kus && !aib.dobr && !aib.mak) {
  5763.                                 if (this.oeForm) {
  5764.                                         $del($q('input[name="oek_parent"]', this.oeForm));
  5765.                                         this.oeForm.insertAdjacentHTML('afterbegin', '<input type="hidden" value="' +
  5766.                                                 tNum + '" name="oek_parent">');
  5767.                                 }
  5768.                                 if (this.form) {
  5769.                                         $del($q('#thr_id, input[name="parent"]', this.form));
  5770.                                         this.form.insertAdjacentHTML('afterbegin',
  5771.                                                 '<input type="hidden" id="thr_id" value="' + tNum + '" name="' + (
  5772.                                                         aib.fch || aib.futa ? 'resto' :
  5773.                                                         aib.tiny ? 'thread' :
  5774.                                                         'parent'
  5775.                                                 ) + '">'
  5776.                                         );
  5777.                                 }
  5778.                         }
  5779.                 } else if (closeReply && !quotetxt && post.wrap.nextElementSibling === this.qArea) {
  5780.                         this.closeQReply();
  5781.                         return;
  5782.                 }
  5783.                 $after(post.wrap, this.qArea);
  5784.                 if (!TNum) {
  5785.                         this._toggleQuickReply(tNum);
  5786.                 }
  5787.                 if (!this.form) {
  5788.                         return;
  5789.                 }
  5790.                 if (this._lastCapUpdate && ((!TNum && this.tNum !== tNum) || (Date.now() - this._lastCapUpdate > 3e5))) {
  5791.                         this.tNum = tNum;
  5792.                         this.refreshCapImg(false);
  5793.                 }
  5794.                 this.tNum = tNum;
  5795.                 if (aib._420 && this.txta.value === 'Comment') {
  5796.                         this.txta.value = '';
  5797.                 }
  5798.                 temp = this.txta.value;
  5799.                 if (!TNum && post.isOp && !isNumClick) {
  5800.                         this.txta.focus();
  5801.                 } else {
  5802.                         $txtInsert(this.txta, (
  5803.                                 isNumClick ? '>>' + pNum :
  5804.                                         (temp !== '' && temp.slice(-1) !== '\n' ? '\n' : '') +
  5805.                                         (this.lastQuickPNum === pNum && temp.contains('>>' + pNum) ? '' : '>>' + pNum + '\n')) +
  5806.                                 (quotetxt ? quotetxt.replace(/^\n|\n$/g, '').replace(/(^|\n)(.)/gm, '$1> $2') + '\n': ''));
  5807.                 }
  5808.                 temp = pByNum[pNum].thr.op.title;
  5809.                 if (temp.length > 30) {
  5810.                         temp = temp.substr(0, 30) + '\u2026';
  5811.                 }
  5812.                 this.qArea.firstChild.firstChild.textContent = temp || '#' + pNum;
  5813.                 this.lastQuickPNum = pNum;
  5814.         },
  5815.         showMainReply: function (isTop, evt) {
  5816.                 this.closeQReply();
  5817.                 if (this.isTopForm === isTop) {
  5818.                         this.pForm.style.display = this.isHidden ? '' : 'none';
  5819.                         this.isHidden = !this.isHidden;
  5820.                         this.updatePAreaBtns();
  5821.                 } else {
  5822.                         this.isTopForm = isTop;
  5823.                         this.setReply(false, false);
  5824.                 }
  5825.                 if (evt) {
  5826.                         $pd(evt);
  5827.                 }
  5828.         },
  5829.         closeQReply: function () {
  5830.                 if (this.isQuick) {
  5831.                         this.isQuick = false;
  5832.                         this.lastQuickPNum = -1;
  5833.                         if (!TNum) {
  5834.                                 this._toggleQuickReply(0);
  5835.                                 $del($id('thr_id'));
  5836.                         }
  5837.                         this.setReply(false, !TNum || Cfg.addPostForm > 1);
  5838.                 }
  5839.         },
  5840.         refreshCapImg: function (focus) {
  5841.                 var src, img;
  5842.                 if (aib.abu && (img = $id('captcha_div')) && img.hasAttribute('onclick')) {
  5843.                         src = {'isCustom': true, 'focus': focus};
  5844.                                 img.dispatchEvent(new CustomEvent('click', {
  5845.                                         'bubbles': true,
  5846.                                         'cancelable': true,
  5847.                                         'detail': (nav.Firefox ? cloneInto(src, document.defaultView) : src)
  5848.                                 }));
  5849.                         return;
  5850.                 }
  5851.                 if (aib.mak) {
  5852.                         aib.updateCaptcha(focus);
  5853.                 } else {
  5854.                         if (!this.cap || (aib.krau && !$q('input[name="captcha_name"]', this.form).hasAttribute('value'))) {
  5855.                                 return;
  5856.                         }
  5857.                         img = this.recap ? $id('recaptcha_image') : $t('img', this.capTr);
  5858.                         if (aib.dobr || aib.krau || aib.dvachnet || this.recap) {
  5859.                                 img.click();
  5860.                         } else if (img) {
  5861.                                 src = img.getAttribute('src');
  5862.                                 if (aib.tire) {
  5863.                                         src = '/' + brd + '/captcha.fpl?' + Math.random();
  5864.                                 } else if (aib.kus || aib.tinyIb) {
  5865.                                         src = src.replace(/\?[^?]+$|$/, (aib._410 ? '?board=' + brd + '&' : '?') + Math.random());
  5866.                                 } else {
  5867.                                         src = src.replace(/pl$/, 'pl?key=mainpage&amp;dummy=')
  5868.                                                 .replace(/dummy=[\d\.]*/, 'dummy=' + Math.random());
  5869.                                         src = this.tNum ? src.replace(/mainpage|res\d+/, 'res' + this.tNum) :
  5870.                                                 src.replace(/res\d+/, 'mainpage');
  5871.                                 }
  5872.                                 img.src = '';
  5873.                                 img.src = src;
  5874.                         }
  5875.                 }
  5876.                 this.cap.value = '';
  5877.                 if (focus) {
  5878.                         this.cap.focus();
  5879.                 }
  5880.                 if (this._lastCapUpdate) {
  5881.                         this._lastCapUpdate = Date.now();
  5882.                 }
  5883.         },
  5884.         setReply: function (quick, hide) {
  5885.                 if (quick) {
  5886.                         this.qArea.appendChild(this.pForm);
  5887.                 } else {
  5888.                         $after(this.pArea[+this.isTopForm], this.qArea);
  5889.                         $after(this._pBtn[+this.isTopForm], this.pForm);
  5890.                 }
  5891.                 this.isHidden = hide;
  5892.                 this.qArea.style.display = quick ? '' : 'none';
  5893.                 this.pForm.style.display = hide ? 'none' : '';
  5894.                 this.updatePAreaBtns();
  5895.         },
  5896.         updatePAreaBtns: function () {
  5897.                 var txt = 'de-abtn de-parea-btn-',
  5898.                         rep = TNum ? 'reply' : 'thrd';
  5899.                 $t('a', this._pBtn[+this.isTopForm]).className = txt + (this.pForm.style.display === '' ? 'close' : rep);
  5900.                 $t('a', this._pBtn[+!this.isTopForm]).className = txt + rep;
  5901.         },
  5902.  
  5903.         _lastCapUpdate: 0,
  5904.         _pBtn: [],
  5905.         _init: function () {
  5906.                 var btn, el;
  5907.                 this.pForm = $New('div', {'id': 'de-pform'}, [this.form, this.oeForm]);
  5908.                 dForm.insertAdjacentHTML('beforebegin', '<div class="de-parea"><div>[<a href="#"></a>]</div><hr></div>');
  5909.                 this.pArea[0] = dForm.previousSibling;
  5910.                 this._pBtn[0] = this.pArea[0].firstChild;
  5911.                 this._pBtn[0].firstElementChild.onclick = this.showMainReply.bind(this, false);
  5912.                 el = aib.fch ? $c('board', dForm) : dForm;
  5913.                 el.insertAdjacentHTML('afterend', '<div class="de-parea"><div>[<a href="#"></a>]</div><hr></div>');
  5914.                 this.pArea[1] = el.nextSibling;
  5915.                 this._pBtn[1] = this.pArea[1].firstChild;
  5916.                 this._pBtn[1].firstElementChild.onclick = this.showMainReply.bind(this, true);
  5917.                 this.qArea = $add('<div style="display: none;" id="de-qarea" class="' + aib.cReply +
  5918.                         (Cfg.hangQReply ? ' de-qarea-hanging' : ' de-qarea-inline') + '"></div>');
  5919.                 this.isTopForm = Cfg.addPostForm !== 0;
  5920.                 this.setReply(false, !TNum || Cfg.addPostForm > 1);
  5921.                 el = this.qArea;
  5922.                 el.style.right = Cfg.qreplyRight + 'px';
  5923.                 el.style.bottom = Cfg.qreplyBottom + 'px';
  5924.                 el.lang = getThemeLang();
  5925.                 el.insertAdjacentHTML('beforeend',
  5926.                         '<div' + (Cfg.hangQReply ? ' class="de-cfg-head"' : '') + '><span id="de-qarea-target"' +
  5927.                         (Cfg.hangQReply ? '': ' style="display: none;"') + '></span><span id="de-qarea-utils">' +
  5928.                         '<span id="de-qarea-toggle" title="' + Lng.toggleQReply[lang] + '">\u2750</span>' +
  5929.                         '<span id="de-qarea-close" title="' + Lng.closeQReply[lang] + '">\u2716</span></span></div>');
  5930.                 el = el.firstChild;
  5931.                 el.addEventListener('mousedown', {
  5932.                         _el: this.qArea,
  5933.                         _elStyle: this.qArea.style,
  5934.                         _oldX: 0,
  5935.                         _oldY: 0,
  5936.                         handleEvent: function (e) {
  5937.                                 if (!Cfg.hangQReply) {
  5938.                                         return;
  5939.                                 }
  5940.                                 var right, bottom, curX = e.clientX,
  5941.                                         curY = e.clientY;
  5942.                                 switch (e.type) {
  5943.                                 case 'mousedown':
  5944.                                         this._oldX = curX;
  5945.                                         this._oldY = curY;
  5946.                                         doc.body.addEventListener('mousemove', this, false);
  5947.                                         doc.body.addEventListener('mouseup', this, false);
  5948.                                         $pd(e);
  5949.                                         return;
  5950.                                 case 'mousemove':
  5951.                                         right = parseInt(this._elStyle.right, 10) - curX + this._oldX;
  5952.                                         bottom = parseInt(this._elStyle.bottom, 10) - curY + this._oldY;
  5953.                                         this._elStyle.right = (right < 0 ? 0 :
  5954.                                                 Math.min(right, Post.sizing.wWidth - this._el.offsetWidth)) + 'px';
  5955.                                         this._elStyle.bottom = (bottom < 25 ? 25 :
  5956.                                                 Math.min(bottom, Post.sizing.wHeight - this._el.offsetHeight)) + 'px';
  5957.                                         this._oldX = curX;
  5958.                                         this._oldY = curY;
  5959.                                         return;
  5960.                                 default: // mouseup
  5961.                                         doc.body.removeEventListener('mousemove', this, false);
  5962.                                         doc.body.removeEventListener('mouseup', this, false);
  5963.                                         saveCfg('qreplyRight', parseInt(this._elStyle.right, 10));
  5964.                                         saveCfg('qreplyBottom', parseInt(this._elStyle.bottom, 10));
  5965.                                 }
  5966.                         }
  5967.                 }, false);
  5968.                 el = el.lastChild;
  5969.                 el.firstChild.onclick = function() {
  5970.                         var node = this.qArea;
  5971.                         toggleCfg('hangQReply')
  5972.                         if (Cfg.hangQReply) {
  5973.                                 node.className = aib.cReply + ' de-qarea-hanging';
  5974.                                 node = node.firstChild;
  5975.                                 node.className = 'de-cfg-head';
  5976.                                 node.firstChild.style.display = '';
  5977.                         } else {
  5978.                                 node.className = aib.cReply + ' de-qarea-inline';
  5979.                                 node = node.firstChild;
  5980.                                 node.removeAttribute('class');
  5981.                                 node.firstChild.style.display = 'none';
  5982.                                 this.txta.focus();
  5983.                         }
  5984.                 }.bind(this);
  5985.                 el.lastChild.onclick = this.closeQReply.bind(this);
  5986.                 if (aib.tire) {
  5987.                         $each($Q('input[type="hidden"]', dForm), $del);
  5988.                         dForm.appendChild($c('userdelete', doc.body));
  5989.                         this.dpass = $q('input[type="password"]', dForm);
  5990.                 }
  5991.                 if (!this.form || !this.txta) {
  5992.                         return;
  5993.                 }
  5994.                 aib.disableRedirection(this.form);
  5995.                 this.form.style.display = 'inline-block';
  5996.                 this.form.style.textAlign = 'left';
  5997.                 if (nav.Firefox) {
  5998.                         this.txta.addEventListener('mouseup', function () {
  5999.                                 saveCfg('textaWidth', parseInt(this.style.width, 10));
  6000.                                 saveCfg('textaHeight', parseInt(this.style.height, 10));
  6001.                         }, false);
  6002.                 } else {
  6003.                         this.txta.insertAdjacentHTML('afterend', '<div id="de-txt-resizer"></div>');
  6004.                         this.txta.nextSibling.addEventListener('mousedown', {
  6005.                                 el: this.txta,
  6006.                                 elStyle: this.txta.style,
  6007.                                 handleEvent: function (e) {
  6008.                                         switch (e.type) {
  6009.                                         case 'mousedown':
  6010.                                                 doc.body.addEventListener('mousemove', this, false);
  6011.                                                 doc.body.addEventListener('mouseup', this, false);
  6012.                                                 $pd(e);
  6013.                                                 return;
  6014.                                         case 'mousemove':
  6015.                                                 var cr = this.el.getBoundingClientRect();
  6016.                                                 this.elStyle.width = (e.pageX - cr.left - window.pageXOffset) + 'px';
  6017.                                                 this.elStyle.height = (e.pageY - cr.top - window.pageYOffset) + 'px';
  6018.                                                 return;
  6019.                                         default: // mouseup
  6020.                                                 doc.body.removeEventListener('mousemove', this, false);
  6021.                                                 doc.body.removeEventListener('mouseup', this, false);
  6022.                                                 saveCfg('textaWidth', parseInt(this.elStyle.width, 10));
  6023.                                                 saveCfg('textaHeight', parseInt(this.elStyle.height, 10));
  6024.                                         }
  6025.                                 }
  6026.                         }, false);
  6027.                 }
  6028.                 if (aib.kus) {
  6029.                         while (this.subm.nextSibling) {
  6030.                                 $del(this.subm.nextSibling);
  6031.                         }
  6032.                 }
  6033.                 if (Cfg.addSageBtn && this.mail) {
  6034.                         btn = $new('span', {'id': 'de-sagebtn'}, {'click': function (e) {
  6035.                                 e.stopPropagation();
  6036.                                 $pd(e);
  6037.                                 toggleCfg('sageReply');
  6038.                                 this._setSage();
  6039.                         }.bind(this)});
  6040.                         el = $parent(this.mail, 'LABEL') || this.mail;
  6041.                         if (el.nextElementSibling || el.previousElementSibling) {
  6042.                                 el.style.display = 'none';
  6043.                                 $after(this.subm, btn);
  6044.                         } else {
  6045.                                 $parent(this.mail, 'TR').style.display = 'none';
  6046.                                 $after(this.name || this.subm, btn);
  6047.                         }
  6048.                         setTimeout(this._setSage.bind(this), 0);
  6049.                         if (aib._2chru) {
  6050.                                 while (btn.nextSibling) {
  6051.                                         $del(btn.nextSibling);
  6052.                                 }
  6053.                         }
  6054.                 }
  6055.                 this.addTextPanel();
  6056.                 this.txta.style.cssText = 'display: inline-block; padding: 0; resize: both; width: ' +
  6057.                         Cfg.textaWidth + 'px; height: ' + Cfg.textaHeight + 'px; min-width: 0;';
  6058.                 this.txta.addEventListener('keypress', function (e) {
  6059.                         var code = e.charCode || e.keyCode;
  6060.                         if ((code === 33 || code === 34) && e.which === 0) {
  6061.                                 e.target.blur();
  6062.                                 window.focus();
  6063.                         }
  6064.                 }, false);
  6065.                 if (!aib.tiny) {
  6066.                         this.subm.value = Lng.reply[lang];
  6067.                 }
  6068.                 this.subm.addEventListener('click', function (e) {
  6069.                         var temp, val = this.txta.value;
  6070.                         if (aib._2chru && !aib.reqCaptcha) {
  6071.                                 GM_xmlhttpRequest({
  6072.                                         'method': 'GET',
  6073.                                         'url': '/' + brd + '/api/requires-captcha',
  6074.                                         'onreadystatechange': function (xhr) {
  6075.                                                 if (xhr.readyState !== 4 || xhr.status !== 200) {
  6076.                                                         return;
  6077.                                                 }
  6078.                                                 aib.reqCaptcha = true;
  6079.                                                 if (JSON.parse(xhr.responseText)['requires-captcha'] !== '1') {
  6080.                                                         this.subm.click();
  6081.                                                         return;
  6082.                                                 }
  6083.                                                 $id('captcha_tr').style.display = 'table-row';
  6084.                                                 $id('captchaimage').src = '/' + brd + '/captcha?' + Math.random();
  6085.                                                 $after(this.cap, $new('span', {
  6086.                                                         'class': 'shortened',
  6087.                                                         'style': 'margin: 0px 0.5em;',
  6088.                                                         'text': 'проверить капчу'}, {
  6089.                                                         'click': function () { GM_xmlhttpRequest({
  6090.                                                                 'method': 'POST',
  6091.                                                                 'url': '/' + brd + '/api/validate-captcha',
  6092.                                                                 'onreadystatechange': function (str) {
  6093.                                                                         if (str.readyState === 4 && str.status === 200) {
  6094.                                                                                 if (JSON.parse(str.responseText).status === 'ok') {
  6095.                                                                                         this.innerHTML = 'можно постить';
  6096.                                                                                 } else {
  6097.                                                                                         this.innerHTML = 'неверная капча';
  6098.                                                                                         setTimeout(function (el) {
  6099.                                                                                                 el.innerHTML = 'проверить капчу';
  6100.                                                                                         }, 1000, this);
  6101.                                                                                 }
  6102.                                                                         }
  6103.                                                                 }.bind(this)
  6104.                                                         }) }
  6105.                                                 }))
  6106.                                         }.bind(this)
  6107.                                 });
  6108.                                 $pd(e);
  6109.                                 return;
  6110.                         }
  6111.                         if (Cfg.warnSubjTrip && this.subj && /#.|##./.test(this.subj.value)) {
  6112.                                 $pd(e);
  6113.                                 $alert(Lng.subjHasTrip[lang], 'upload', false);
  6114.                                 return;
  6115.                         }
  6116.                         if (spells.haveOutreps) {
  6117.                                 val = spells.outReplace(val);
  6118.                         }
  6119.                         if (this.tNum && pByNum[this.tNum].subj === 'Dollchan Extension Tools') {
  6120.                                 temp = '\n\n' + this._wrapText(aib.formButtons.bb[5], aib.formButtons.tag[5],
  6121.                                         '-'.repeat(50) + '\n' + nav.ua + '\nv' + version + ' [' + nav.scriptInstall + ']')[1];
  6122.                                 if (!val.contains(temp)) {
  6123.                                         val += temp;
  6124.                                 }
  6125.                         }
  6126.                         this.txta.value = val;
  6127.                         if (Cfg.ajaxReply) {
  6128.                                 $alert(Lng.checking[lang], 'upload', true);
  6129.                         }
  6130.                         if (Cfg.favOnReply && this.tNum) {
  6131.                                 pByNum[this.tNum].thr.setFavorState(true);
  6132.                         }
  6133.                         if (this.video && (val = this.video.value) && (val = val.match(new YouTube().ytReg))) {
  6134.                                 this.video.value = 'http://www.youtube.com/watch?v=' + val[1];
  6135.                         }
  6136.                         if (this.isQuick) {
  6137.                                 this.pForm.style.display = 'none';
  6138.                                 this.qArea.style.display = 'none';
  6139.                                 $after(this._pBtn[+this.isTopForm], this.pForm);
  6140.                         }
  6141.                 }.bind(this), false);
  6142.                 $each($Q('input[type="text"], input[type="file"]', this.form), function (node) {
  6143.                         node.size = 30;
  6144.                 });
  6145.                 if (Cfg.noGoto && this.gothr) {
  6146.                         this.gothr.style.display = 'none';
  6147.                 }
  6148.                 if (Cfg.noPassword && this.passw) {
  6149.                         $parent(this.passw, 'TR').style.display = 'none';
  6150.                 }
  6151.                 window.addEventListener('load', function () {
  6152.                         if (Cfg.userName && this.name) {
  6153.                                 setTimeout(PostForm.setUserName, 1e3);
  6154.                         }
  6155.                         if (this.passw) {
  6156.                                 setTimeout(PostForm.setUserPassw, 1e3);
  6157.                         }
  6158.                 }.bind(this), false);
  6159.                 if (this.cap) {
  6160.                         if (!(aib.fch && doc.cookie.indexOf('pass_enabled=1') > -1)) {
  6161.                                 this.capTr = $parent(this.cap, 'TR');
  6162.                                 this.txta.addEventListener('focus', this._captchaInit.bind(this, this.capTr.innerHTML), false);
  6163.                                 if (this.file) {
  6164.                                         this.file.addEventListener('click', this._captchaInit.bind(this, this.capTr.innerHTML), false);
  6165.                                 }
  6166.                                 if (!aib.krau) {
  6167.                                         this.capTr.style.display = 'none';
  6168.                                 }
  6169.                                 this.capTr.innerHTML = '';
  6170.                         }
  6171.                         this.cap = null;
  6172.                 }
  6173.                 if (Cfg.ajaxReply === 2) {
  6174.                         if (aib.krau) {
  6175.                                 this.form.removeAttribute('onsubmit');
  6176.                         }
  6177.                         setTimeout(function () {
  6178.                                 this.form.onsubmit = function (e) {
  6179.                                         $pd(e);
  6180.                                         if (aib.krau) {
  6181.                                                 aib.addProgressTrack.click();
  6182.                                         }
  6183.                                         if (aib._2chru) {
  6184.                                                 doc.body.insertAdjacentHTML('beforeend', '<iframe class="ninja" id="csstest" src="/' +
  6185.                                                         brd + '/csstest.foo"></iframe>');
  6186.                                                 doc.body.lastChild.onload = function (e) {
  6187.                                                         $del(e.target);
  6188.                                                         new html5Submit(this.form, this.subm, checkUpload);
  6189.                                                 }.bind(this);
  6190.                                                 return;
  6191.                                         }
  6192.                                         new html5Submit(this.form, this.subm, checkUpload);
  6193.                                 }.bind(this);
  6194.                         }.bind(this), 0);
  6195.                 } else if (Cfg.ajaxReply === 1) {
  6196.                         this.form.target = 'de-iframe-pform';
  6197.                         this.form.onsubmit = null;
  6198.                 }
  6199.                 if (el = this.file) {
  6200.                         aib.fixFileInputs(el);
  6201.                         this.eventFiles(true);
  6202.                 }
  6203.         },
  6204.         _setSage: function () {
  6205.                 var c = Cfg.sageReply;
  6206.                 $id('de-sagebtn').innerHTML = '&nbsp;' + (
  6207.                         c ? '<span class="de-btn-sage"></span><b style="color: red;">SAGE</b>' :
  6208.                                 '<span>(no&nbsp;sage)</span>'
  6209.                 );
  6210.                 if (this.mail.type === 'text') {
  6211.                         this.mail.value = c ? 'sage' : aib.fch ? 'noko' : '';
  6212.                 } else {
  6213.                         this.mail.checked = c;
  6214.                 }
  6215.         },
  6216.         _toggleQuickReply: function (tNum) {
  6217.                 if (this.oeForm) {
  6218.                         $q('input[name="oek_parent"], input[name="replyto"]', this.oeForm).value = tNum;
  6219.                 }
  6220.                 if (this.form) {
  6221.                         $q('#thr_id, input[name*="thread"]', this.form).value = tNum;
  6222.                         if (aib.pony) {
  6223.                                 $q('input[name="quickreply"]', this.form).value = tNum || '';
  6224.                         }
  6225.                 }
  6226.         },
  6227.         _captchaInit: function (html) {
  6228.                 if (this.capInited) {
  6229.                         return;
  6230.                 }
  6231.                 this.capTr.innerHTML = html;
  6232.                 this.cap = $q('input[type="text"][name*="aptcha"]:not([name="recaptcha_challenge_field"])', this.capTr);
  6233.                 if (aib.fch) {
  6234.                         $script('loadRecaptcha()');
  6235.                 }
  6236.                 if (aib.tire) {
  6237.                         $script('show_captcha()');
  6238.                 }
  6239.                 if (aib.krau) {
  6240.                         aib.initCaptcha.click();
  6241.                         $id('captcha_image').setAttribute('onclick',  'requestCaptcha(true);');
  6242.                 }
  6243.                 if (aib.dvachnet) {
  6244.                         $script('get_captcha()');
  6245.                 }
  6246.                 if (aib.mak) {
  6247.                         aib.updateCaptcha(false);
  6248.                         pr.txta.tabIndex = 999;
  6249.                         this.capInited = true;
  6250.                         return;
  6251.                 }
  6252.                 setTimeout(this._captchaUpd.bind(this), 100);
  6253.         },
  6254.         _captchaUpd: function () {
  6255.                 var img, a;
  6256.                 if ((this.recap = $id('recaptcha_response_field')) && (img = $id('recaptcha_image'))) {
  6257.                         this.cap = this.recap;
  6258.                         img.setAttribute('onclick', 'Recaptcha.reload()');
  6259.                         img.style.cssText = 'width: 300px; cursor: pointer;';
  6260.                 } else if (aib.fch) {
  6261.                         setTimeout(this._captchaUpd.bind(this), 100);
  6262.                         return;
  6263.                 }
  6264.                 this.capInited = true;
  6265.                 this.cap.autocomplete = 'off';
  6266.                 this.cap.onkeypress = (function () {
  6267.                         var ru = 'йцукенгшщзхъфывапролджэячсмитьбюё',
  6268.                                 en = 'qwertyuiop[]asdfghjkl;\'zxcvbnm,.`';
  6269.                         return function (e) {
  6270.                                 if (!Cfg.captchaLang || e.which === 0) {
  6271.                                         return;
  6272.                                 }
  6273.                                 var i, code = e.charCode || e.keyCode,
  6274.                                         chr = String.fromCharCode(code).toLowerCase();
  6275.                                 if (Cfg.captchaLang === 1) {
  6276.                                         if (code < 0x0410 || code > 0x04FF || (i = ru.indexOf(chr)) === -1) {
  6277.                                                 return;
  6278.                                         }
  6279.                                         chr = en[i];
  6280.                                 } else {
  6281.                                         if (code < 0x0021 || code > 0x007A || (i = en.indexOf(chr)) === -1) {
  6282.                                                 return;
  6283.                                         }
  6284.                                         chr = ru[i];
  6285.                                 }
  6286.                                 $pd(e);
  6287.                                 $txtInsert(e.target, chr);
  6288.                         };
  6289.                 })();
  6290.                 if (aib.krau) {
  6291.                         return;
  6292.                 }
  6293.                 if (aib.abu || aib.dobr || aib.dvachnet || this.recap || !(img = $q('img', this.capTr))) {
  6294.                         this.capTr.style.display = '';
  6295.                         return;
  6296.                 }
  6297.                 if (!aib.kus && !aib.tinyIb) {
  6298.                         this._lastCapUpdate = Date.now();
  6299.                         this.cap.onfocus = function () {
  6300.                                 if (this._lastCapUpdate && (Date.now() - this._lastCapUpdate > 3e5)) {
  6301.                                         this.refreshCapImg(false);
  6302.                                 }
  6303.                         }.bind(this);
  6304.                         if (!TNum && this.isQuick) {
  6305.                                 this.refreshCapImg(false);
  6306.                         }
  6307.                 }
  6308.                 img.title = Lng.refresh[lang];
  6309.                 img.alt = Lng.loading[lang];
  6310.                 img.style.cssText = 'vertical-align: bottom; border: none; cursor: pointer;';
  6311.                 img.onclick = this.refreshCapImg.bind(this, true);
  6312.                 if ((a = img.parentNode).tagName === 'A') {
  6313.                         $after(a, img);
  6314.                         $del(a);
  6315.                 }
  6316.                 this.capTr.style.display = '';
  6317.         },
  6318.         _wrapText: function (isBB, tag, text) {
  6319.                 var str, m;
  6320.                 if (isBB) {
  6321.                         if (text.contains('\n')) {
  6322.                                 str = '[' + tag + ']' + text + '[/' + tag + ']';
  6323.                                 return [str.length, str];
  6324.                         }
  6325.                         m = text.match(/^(\s*)(.*?)(\s*)$/);
  6326.                         str = m[1] + '[' + tag + ']' + m[2] + '[/' + tag + ']' + m[3];
  6327.                         return [m[2].length === 0 ? m[1].length + tag.length + 2 : str.length, str];
  6328.                 }
  6329.                 for (var rv = '', i = 0, arr = text.split('\n'), len = arr.length; i < len; ++i) {
  6330.                         m = arr[i].match(/^(\s*)(.*?)(\s*)$/);
  6331.                         rv += '\n' + m[1] + (tag === '^H' ? m[2] + '^H'.repeat(m[2].length) :
  6332.                                 tag + m[2] + tag) + m[3];
  6333.                 }
  6334.                 return [i === 1 && m[2].length === 0 && tag !== '^H' ? m[1].length + tag.length :
  6335.                         rv.length - 1, rv.slice(1)];
  6336.         }
  6337. }
  6338.  
  6339. function FileInput(form, el, prev) {
  6340.         this.el = el;
  6341.         this.place = el.parentNode;
  6342.         this.form = form;
  6343.         this.prev = prev;
  6344.         if (prev) {
  6345.                 prev.next = this;
  6346.         }
  6347. }
  6348. FileInput.prototype = {
  6349.         empty: true,
  6350.         next: null,
  6351.         imgFile: null,
  6352.         thumb: null,
  6353.         clear: function () {
  6354.                 var newEl, form = this.form,
  6355.                         oldEl = this.el;
  6356.                 oldEl.insertAdjacentHTML('afterend', oldEl.outerHTML);
  6357.                 newEl = this.el.nextSibling;
  6358.                 newEl.obj = this;
  6359.                 newEl.addEventListener('change', this, false);
  6360.                 if (form.file === oldEl) {
  6361.                         form.file = newEl;
  6362.                 }
  6363.                 this.el = newEl;
  6364.                 $del(oldEl);
  6365.                 this.empty = true;
  6366.                 this.hideInputs();
  6367.         },
  6368.         delUtils: function () {
  6369.                 var mParent;
  6370.                 if (Cfg.fileThumb) {
  6371.                         this.thumb.classList.add('de-file-off');
  6372.                         if (this._mediaEl) {
  6373.                                 window.URL.revokeObjectURL(this._mediaEl.src);
  6374.                                 mParent = this._mediaEl.parentNode;
  6375.                                 mParent.title = Lng.clickToAdd[lang];
  6376.                                 $del(this._mediaEl);
  6377.                                 this._mediaEl = null;
  6378.                         }
  6379.                 }
  6380.                 $del(this._delUtil);
  6381.                 $del(this._rjUtil);
  6382.                 this.imgFile = this._delUtil = this._rjUtil = null;
  6383.                 this._changeFilesCount(-1);
  6384.                 this.clear();
  6385.         },
  6386.         updateUtils: function () {
  6387.                 this.init(true);
  6388.                 if (this._delUtil) {
  6389.                         $after(this._buttonsPlace, this._delUtil);
  6390.                 }
  6391.                 if (this._rjUtil) {
  6392.                         $after(this._buttonsPlace, this._rjUtil);
  6393.                 }
  6394.         },
  6395.         handleEvent: function (e) {
  6396.                 switch (e.type) {
  6397.                 case 'change': this._onFileChange(); break;
  6398.                 case 'click':
  6399.                         if (e.target === this._delUtil) {
  6400.                                 this.delUtils();
  6401.                         } else if (e.target === this._rjUtil) {
  6402.                                 this._addRarJpeg();
  6403.                         } else if (e.target.className === 'de-file-img') {
  6404.                                 this.el.click();
  6405.                         }
  6406.                         e.stopPropagation();
  6407.                         $pd(e);
  6408.                         break;
  6409.                 case 'dragover':
  6410.                         this.thumb.classList.add('de-file-drag');
  6411.                         $after(this.thumb, this.el);
  6412.                         break;
  6413.                 case 'dragleave':
  6414.                 case 'drop':
  6415.                         setTimeout(function () {
  6416.                                 this.thumb.classList.remove('de-file-drag');
  6417.                                 var el = this.place.firstChild;
  6418.                                 if (el) {
  6419.                                         $before(el, this.el);
  6420.                                 } else {
  6421.                                         this.place.appendChild(this.el);
  6422.                                 }
  6423.                         }.bind(this), 10);
  6424.                         break;
  6425.                 case 'mouseover': this.thumb.classList.add('de-file-hover'); break;
  6426.                 case 'mouseout': this.thumb.classList.remove('de-file-hover');
  6427.                 }
  6428.         },
  6429.         hideInputs: function () {
  6430.                 var hideThumbs = Cfg.fileThumb, inp = this.next;
  6431.                 while (inp && inp.empty) {
  6432.                         inp = inp.next;
  6433.                 }
  6434.                 if (!inp) {
  6435.                         inp = this;
  6436.                         while (inp.prev && inp.prev.empty) {
  6437.                                 inp = inp.prev;
  6438.                         }
  6439.                         while (inp = inp.next) {
  6440.                                 if (hideThumbs) {
  6441.                                         inp.thumb.style.display = 'none';
  6442.                                 } else {
  6443.                                         inp._wrap.style.display = 'none';
  6444.                                 }
  6445.                         }
  6446.                 }
  6447.         },
  6448.         init: function (update) {
  6449.                 var imgTD;
  6450.                 if (Cfg.fileThumb) {
  6451.                         this.form.fileTd.parentNode.style.display = 'none';
  6452.                         imgTD = this.form.fileImageTD;
  6453.                         imgTD.insertAdjacentHTML('beforeend', '<div class="de-file de-file-off"><div class="de-file-img">' +
  6454.                                 '<div class="de-file-img" title="' + Lng.clickToAdd[lang] + '"></div></div></div>');
  6455.                         this.thumb = imgTD.lastChild;
  6456.                         this.thumb.addEventListener('mouseover', this, false);
  6457.                         this.thumb.addEventListener('mouseout', this, false);
  6458.                         this.thumb.addEventListener('click', this, false);
  6459.                         this.thumb.addEventListener('dragover', this, false);
  6460.                         this.el.addEventListener('dragleave', this, false);
  6461.                         this.el.addEventListener('drop', this, false);
  6462.                         if (update) {
  6463.                                 this._showPviewImage();
  6464.                         } else if (this.prev) {
  6465.                                 this.thumb.style.display = 'none';
  6466.                         }
  6467.                 } else if (update) {
  6468.                         this._wrap.style.display = '';
  6469.                         this.form.fileTd.parentNode.style.display = '';
  6470.                         if (this._mediaE) {
  6471.                                 window.URL.revokeObjectURL(this._mediaE.src);
  6472.                         }
  6473.                         $del(this.thumb);
  6474.                         this.thumb = this._mediaEl = null;
  6475.                 }
  6476.                 if (!update) {
  6477.                         this.el.addEventListener('change', this, false);
  6478.                 }
  6479.         },
  6480.  
  6481.         _mediaEl: null,
  6482.         _delUtil: null,
  6483.         _rjUtil: null,
  6484.         get _buttonsPlace() {
  6485.                 return Cfg.fileThumb ? this.thumb.firstChild : this.el;
  6486.         },
  6487.         get _wrap() {
  6488.                 return aib.getFileWrap(this.el);
  6489.         },
  6490.         _addRarJpeg: function () {
  6491.                 var el = this.form.rarInput;
  6492.                 el.onchange = function (e) {
  6493.                         $del(this._rjUtil);
  6494.                         var file = e.target.files[0],
  6495.                                 fr = new FileReader(),
  6496.                                 btnsPlace = this._buttonsPlace;
  6497.                         btnsPlace.insertAdjacentHTML('afterend',
  6498.                                 '<span><span class="de-wait"></span>' + Lng.wait[lang] + '</span>');
  6499.                         this._rjUtil = btnsPlace.nextSibling;
  6500.                         fr.onload = function (file, node, e) {
  6501.                                 if (this._buttonsPlace.nextSibling === node) {
  6502.                                         node.className = 'de-file-rarmsg de-file-utils';
  6503.                                         node.title = this.el.files[0].name + ' + ' + file.name;
  6504.                                         node.textContent = this.el.files[0].name.replace(/^.+\./, '') + ' + ' +
  6505.                                                 file.name.replace(/^.+\./, '')
  6506.                                         this.imgFile = e.target.result;
  6507.                                 }
  6508.                         }.bind(this, file, btnsPlace.nextSibling);
  6509.                         fr.readAsArrayBuffer(file);
  6510.                 }.bind(this);
  6511.                 el.click();
  6512.         },
  6513.         _changeFilesCount: function (val) {
  6514.                 if (aib.dobr) {
  6515.                         var el = this.form.fileTd.firstElementChild,
  6516.                                 val = +el.value + val;
  6517.                         el.value = val < 1 ? 1 : val;
  6518.                 }
  6519.         },
  6520.         _onFileChange: function () {
  6521.                 if (Cfg.fileThumb) {
  6522.                         this._showPviewImage();
  6523.                 } else {
  6524.                         this.form.eventFiles(false);
  6525.                 }
  6526.                 if (this.empty) {
  6527.                         this.empty = false;
  6528.                         this._changeFilesCount(+1);
  6529.                         $after(this._buttonsPlace, this._delUtil = $new('span', {
  6530.                                 'class': 'de-file-del de-file-utils',
  6531.                                 'title': Lng.removeFile[lang]}, {
  6532.                                 'click': this
  6533.                         }));
  6534.                 } else if (this.imgFile) {
  6535.                         this.imgFile = null;
  6536.                 }
  6537.                 if (this.next) {
  6538.                         if (Cfg.fileThumb) {
  6539.                                 this.next.thumb.style.display = '';
  6540.                         } else {
  6541.                                 this.next._wrap.style.display = '';
  6542.                         }
  6543.                 }
  6544.                 if (aib.fch || nav.Presto || !/^image\/(?:png|jpeg)$/.test(this.el.files[0].type)) {
  6545.                         return;
  6546.                 }
  6547.                 if (this._rjUtil) {
  6548.                         $del(this._rjUtil);
  6549.                         this._rjUtil = null;
  6550.                 }
  6551.                 $after(this._buttonsPlace, this._rjUtil = $new('span', {
  6552.                         'class': 'de-file-rar de-file-utils',
  6553.                         'title': Lng.helpAddFile[lang]}, {
  6554.                         'click': this
  6555.                 }));
  6556.         },
  6557.         _showPviewImage: function () {
  6558.                 var fr, files = this.el.files;
  6559.                 if (files && files[0]) {
  6560.                         fr = new FileReader();
  6561.                         fr.onload = function (e) {
  6562.                                 this.form.eventFiles(false);
  6563.                                 var file = this.el.files[0],
  6564.                                         thumb = this.thumb;
  6565.                                 if (this.empty) {
  6566.                                         return;
  6567.                                 }
  6568.                                 thumb.classList.remove('de-file-off');
  6569.                                 thumb = thumb.firstChild.firstChild;
  6570.                                 thumb.title = file.name + ', ' + (file.size/1024).toFixed(2) + 'KB';
  6571.                                 thumb.insertAdjacentHTML('afterbegin', file.type === 'video/webm' ?
  6572.                                         '<video class="de-file-img" loop autoplay muted src=""></video>' :
  6573.                                         '<img class="de-file-img" src="">');
  6574.                                 this._mediaEl = thumb = thumb.firstChild;
  6575.                                 thumb.src = window.URL.createObjectURL(new Blob([e.target.result]));
  6576.                                 thumb = thumb.nextSibling;
  6577.                                 if (thumb) {
  6578.                                         window.URL.revokeObjectURL(thumb.src);
  6579.                                         $del(thumb);
  6580.                                 }
  6581.                         }.bind(this);
  6582.                         fr.readAsArrayBuffer(files[0]);
  6583.                 }
  6584.         }
  6585. }
  6586.  
  6587.  
  6588. // SUBMIT
  6589. // ===========================================================================================================
  6590.  
  6591. function getSubmitError(dc) {
  6592.         var i, els, el, err = '', form = $q(aib.qDForm, dc);
  6593.         if (dc.body.hasChildNodes() && !form) {
  6594.                 for (i = 0, els = $Q(aib.qError, dc); el = els[i++];) {
  6595.                         err += el.innerHTML + '\n';
  6596.                 }
  6597.                 if (!(err = err.replace(/<a [^>]+>Назад.+|<br.+/, ''))) {
  6598.                         err = Lng.error[lang] + ':\n' + dc.body.innerHTML;
  6599.                 }
  6600.                 err = /:null|successful|uploaded|updating|обновл|удален[о\.]/i.test(err) ? '' : err.replace(/"/g, "'");
  6601.         }
  6602.         return err;
  6603. }
  6604.  
  6605. function checkUpload(dc) {
  6606.         if (aib.krau) {
  6607.                 pr.form.action = pr.form.action.split('?')[0];
  6608.                 $id('postform_row_progress').style.display = 'none';
  6609.                 aib.btnZeroLUTime.click();
  6610.         }
  6611.         var el, err = getSubmitError(dc);
  6612.         if (err) {
  6613.                 if (pr.isQuick) {
  6614.                         pr.setReply(true, false);
  6615.                 }
  6616.                 if (/captch|капч|подтвер|verifizie/i.test(err)) {
  6617.                         pr.refreshCapImg(true);
  6618.                 }
  6619.                 $alert(err, 'upload', false);
  6620.                 updater.sendErrNotif();
  6621.                 return;
  6622.         }
  6623.         pr.txta.value = '';
  6624.         if (pr.file) {
  6625.                 pr.delFilesUtils();
  6626.         }
  6627.         if (pr.video) {
  6628.                 pr.video.value = '';
  6629.         }
  6630.         Cfg.stats[pr.tNum ? 'reply' : 'op']++;
  6631.         saveComCfg(aib.dm, Cfg);
  6632.         if (!pr.tNum) {
  6633.                 window.location = aib.getThrdUrl(brd, aib.getTNum($q(aib.qDForm, dc)));
  6634.                 return;
  6635.         }
  6636.         el = !aib.tiny && !aib.kus &&
  6637.                 (aib.qPostRedir === null || $q(aib.qPostRedir, dc)) ? $q(aib.qDForm, dc) : null;
  6638.         if (TNum) {
  6639.                 firstThr.clearPostsMarks();
  6640.                 if (el) {
  6641.                         firstThr.loadNewFromForm(el);
  6642.                         closeAlert($id('de-alert-upload'));
  6643.                         if (Cfg.scrAfterRep) {
  6644.                                 scrollTo(0, pageYOffset + firstThr.last.el.getBoundingClientRect().top);
  6645.                         }
  6646.                 } else {
  6647.                         firstThr.loadNew(function (eCode, eMsg, np, xhr) {
  6648.                                 infoLoadErrors(eCode, eMsg, 0);
  6649.                                 closeAlert($id('de-alert-upload'));
  6650.                                 if (Cfg.scrAfterRep) {
  6651.                                         scrollTo(0, pageYOffset + firstThr.last.el.getBoundingClientRect().top);
  6652.                                 }
  6653.                         }, true);
  6654.                 }
  6655.         } else {
  6656.                 if (el) {
  6657.                         pByNum[pr.tNum].thr.loadFromForm(visPosts, false, el);
  6658.                         closeAlert($id('de-alert-upload'));
  6659.                 } else {
  6660.                         pByNum[pr.tNum].thr.load(visPosts, false, closeAlert.bind(window, $id('de-alert-upload')));
  6661.                 }
  6662.         }
  6663.         pr.closeQReply();
  6664.         pr.refreshCapImg(false);
  6665. }
  6666.  
  6667. function endDelete() {
  6668.         var el = $id('de-alert-deleting');
  6669.         if (el) {
  6670.                 closeAlert(el);
  6671.                 $alert(Lng.succDeleted[lang], 'deleted', false);
  6672.         }
  6673. }
  6674.  
  6675. function checkDelete(dc) {
  6676.         var el, i, els, len, post, tNums, num, err = getSubmitError(dc);
  6677.         if (err) {
  6678.                 $alert(Lng.errDelete[lang] + err, 'deleting', false);
  6679.                 updater.sendErrNotif();
  6680.                 return;
  6681.         }
  6682.         tNums = [];
  6683.         num = (doc.location.hash.match(/\d+/) || [null])[0];
  6684.         if (num && (post = pByNum[num])) {
  6685.                 if (!post.isOp) {
  6686.                         post.el.className = aib.cReply;
  6687.                 }
  6688.                 doc.location.hash = '';
  6689.         }
  6690.         for (i = 0, els = $Q('.' + aib.cRPost + ' input:checked', dForm), len = els.length; i < len; ++i) {
  6691.                 el = els[i];
  6692.                 el.checked = false;
  6693.                 if (!TNum && tNums.indexOf(num = aib.getPostEl(el).post.tNum) === -1) {
  6694.                         tNums.push(num);
  6695.                 }
  6696.         }
  6697.         if (TNum) {
  6698.                 firstThr.clearPostsMarks();
  6699.                 firstThr.loadNew(function (eCode, eMsg, np, xhr) {
  6700.                         infoLoadErrors(eCode, eMsg, 0);
  6701.                         endDelete();
  6702.                 }, false);
  6703.         } else {
  6704.                 tNums.forEach(function (tNum) {
  6705.                         pByNum[tNum].thr.load(visPosts, false, endDelete);
  6706.                 });
  6707.         }
  6708. }
  6709.  
  6710. function html5Submit(form, button, fn) {
  6711.         this.boundary = '---------------------------' + Math.round(Math.random() * 1e11);
  6712.         this.data = [];
  6713.         this.busy = 0;
  6714.         this.error = false;
  6715.         this.url = form.action;
  6716.         this.fn = fn;
  6717.         $each($Q('input:not([type="submit"]):not([type="button"]), textarea, select', form),
  6718.                 this.append.bind(this));
  6719.         this.append(button);
  6720.         this.submit();
  6721. }
  6722. html5Submit.prototype = {
  6723.         append: function (el) {
  6724.                 var file, fName, idx, fr,
  6725.                         pre = '--' + this.boundary + '\r\nContent-Disposition: form-data; name="' + el.name + '"';
  6726.                 if (el.type === 'file' && el.files.length > 0) {
  6727.                         file = el.files[0];
  6728.                         fName = file.name;
  6729.                         this.data.push(pre + '; filename="' + (
  6730.                                 !Cfg.removeFName ? fName : ' ' + fName.substring(fName.lastIndexOf('.'))
  6731.                         ) + '"\r\nContent-type: ' + file.type + '\r\n\r\n', null, '\r\n');
  6732.                         idx = this.data.length - 2;
  6733.                         if (!/^image\/(?:png|jpeg)$|^video\/webm$/.test(file.type)) {
  6734.                                 this.data[idx] = file;
  6735.                                 return;
  6736.                         }
  6737.                         fr = new FileReader();
  6738.                         fr.onload = function (name, e) {
  6739.                                 var dat = this.clearImage(e.target.result, el.obj.imgFile,
  6740.                                         Cfg.postSameImg && String(Math.round(Math.random() * 1e6)));
  6741.                                 if (dat) {
  6742.                                         this.data[idx] = new Blob(dat);
  6743.                                         this.busy--;
  6744.                                         this.submit();
  6745.                                 } else {
  6746.                                         this.error = true;
  6747.                                         $alert(Lng.fileCorrupt[lang] + name, 'upload', false);
  6748.                                 }
  6749.                         }.bind(this, fName);
  6750.                         fr.readAsArrayBuffer(file);
  6751.                         this.busy++;
  6752.                 } else if ((el.type !== 'checkbox' && el.type !== 'radio') || el.checked) {
  6753.                         this.data.push(pre + '\r\n\r\n' + el.value + '\r\n');
  6754.                 }
  6755.         },
  6756.         submit: function () {
  6757.                 if (this.error || this.busy !== 0) {
  6758.                         return;
  6759.                 }
  6760.                 this.data.push('--' + this.boundary + '--\r\n');
  6761.                 $xhr({
  6762.                         'method': 'POST',
  6763.                         'headers': {'Content-type': 'multipart/form-data; boundary=' + this.boundary},
  6764.                         'data': new Blob(this.data),
  6765.                         'url': nav.fixLink(this.url),
  6766.                         'onreadystatechange': function (xhr) {
  6767.                                 if (xhr.readyState === 4) {
  6768.                                         if (xhr.status === 200) {
  6769.                                                 this($DOM(xhr.responseText));
  6770.                                         } else {
  6771.                                                 $alert(xhr.status === 0 ? Lng.noConnect[lang] :
  6772.                                                         'HTTP [' + xhr.status + '] ' + xhr.statusText, 'upload', false);
  6773.                                         }
  6774.                                 }
  6775.                         }.bind(this.fn)
  6776.                 });
  6777.         },
  6778.         readExif: function (data, off, len) {
  6779.                 var i, j, dE, tag, tgLen, xRes = 0,
  6780.                         yRes = 0,
  6781.                         resT = 0,
  6782.                         dv = new DataView(data, off),
  6783.                         le = String.fromCharCode(dv.getUint8(0), dv.getUint8(1)) !== 'MM';
  6784.                 if (dv.getUint16(2, le) !== 0x2A) {
  6785.                         return null;
  6786.                 }
  6787.                 i = dv.getUint32(4, le);
  6788.                 if (i > len) {
  6789.                         return null;
  6790.                 }
  6791.                 for (tgLen = dv.getUint16(i, le), j = 0; j < tgLen; j++) {
  6792.                         tag = dv.getUint16(dE = i + 2 + 12 * j, le);
  6793.                         if (tag === 0x0128) {
  6794.                                 resT = dv.getUint16(dE + 8, le) - 1;
  6795.                         } else if (tag === 0x011A || tag === 0x011B) {
  6796.                                 dE = dv.getUint32(dE + 8, le);
  6797.                                 if (dE > len) {
  6798.                                         return null;
  6799.                                 }
  6800.                                 if (tag === 0x11A) {
  6801.                                         xRes = Math.round(dv.getUint32(dE, le) / dv.getUint32(dE + 4, le));
  6802.                                 } else {
  6803.                                         yRes = Math.round(dv.getUint32(dE, le) / dv.getUint32(dE + 4, le));
  6804.                                 }
  6805.                         }
  6806.                 }
  6807.                 xRes = xRes || yRes;
  6808.                 yRes = yRes || xRes;
  6809.                 return new Uint8Array([resT & 0xFF, xRes >> 8, xRes & 0xFF, yRes >> 8, yRes & 0xFF]);
  6810.         },
  6811.         clearImage: function (data, extraData, rand) {
  6812.                 var tmp, i, len, deep, val, lIdx, jpgDat, img = new Uint8Array(data),
  6813.                         rExif = !!Cfg.removeEXIF,
  6814.                         rv = extraData ? rand ? [img, extraData, rand] : [img, extraData] : rand ?
  6815.                                 [img, rand] : [img];
  6816.                 if (!Cfg.postSameImg && !rExif && !extraData) {
  6817.                         return rv;
  6818.                 }
  6819.                 // JPG
  6820.                 if (img[0] === 0xFF && img[1] === 0xD8) {
  6821.                         for (i = 2, deep = 1, len = img.length - 1, val = [null, null], lIdx = 2, jpgDat = null; i < len; ) {
  6822.                                 if (img[i] === 0xFF) {
  6823.                                         if (rExif) {
  6824.                                                 if (!jpgDat && deep === 1) {
  6825.                                                         if (img[i + 1] === 0xE1 && img[i + 4] === 0x45) {
  6826.                                                                 jpgDat = this.readExif (data, i + 10, (img[i + 2] << 8) + img[i + 3]);
  6827.                                                         } else if (img[i + 1] === 0xE0 && img[i + 7] === 0x46 &&
  6828.                                                                   (img[i + 2] !== 0 || img[i + 3] >= 0x0E ||
  6829.                                                                   img[i + 15] !== 0xFF))
  6830.                                                         {
  6831.                                                                 jpgDat = img.subarray(i + 11, i + 16);
  6832.                                                         }
  6833.                                                 }
  6834.                                                 if ((img[i + 1] >> 4) === 0xE || img[i + 1] === 0xFE) {
  6835.                                                         if (lIdx !== i) {
  6836.                                                                 val.push(img.subarray(lIdx, i));
  6837.                                                         }
  6838.                                                         i += 2 + (img[i + 2] << 8) + img[i + 3];
  6839.                                                         lIdx = i;
  6840.                                                         continue;
  6841.                                                 }
  6842.                                         } else if (img[i + 1] === 0xD8) {
  6843.                                                 deep++;
  6844.                                                 i++;
  6845.                                                 continue;
  6846.                                         }
  6847.                                         if (img[i + 1] === 0xD9 && --deep === 0) {
  6848.                                                 break;
  6849.                                         }
  6850.                                 }
  6851.                                 i++;
  6852.                         }
  6853.                         i += 2;
  6854.                         if (!extraData && len - i > 75) {
  6855.                                 i = len;
  6856.                         }
  6857.                         if (lIdx === 2) {
  6858.                                 if (i !== len) {
  6859.                                         rv[0] = new Uint8Array(data, 0, i);
  6860.                                 }
  6861.                                 return rv;
  6862.                         }
  6863.                         val[0] = new Uint8Array([0xFF, 0xD8, 0xFF, 0xE0, 0, 0x0E, 0x4A, 0x46, 0x49, 0x46, 0, 1, 1]);
  6864.                         val[1] = jpgDat || new Uint8Array([0, 0, 1, 0, 1]);
  6865.                         val.push(img.subarray(lIdx, i));
  6866.                         if (extraData) {
  6867.                                 val.push(extraData);
  6868.                         }
  6869.                         if (rand) {
  6870.                                 val.push(rand);
  6871.                         }
  6872.                         return val;
  6873.                 }
  6874.                 // PNG
  6875.                 if (img[0] === 0x89 && img[1] === 0x50) {
  6876.                         for (i = 0, len = img.length - 7; i < len && (img[i] !== 0x49 ||
  6877.                                 img[i + 1] !== 0x45 || img[i + 2] !== 0x4E || img[i + 3] !== 0x44); i++) {}
  6878.                         i += 8;
  6879.                         if (i !== len && (extraData || len - i <= 75)) {
  6880.                                 rv[0] = new Uint8Array(data, 0, i);
  6881.                         }
  6882.                         return rv;
  6883.                 }
  6884.                 // WEBM
  6885.                 if (img[0] === 0x1a && img[1] === 0x45 && img[2] === 0xDF && img[3] === 0xA3) {
  6886.                         return new WebmParser(data).addData(rand).getData();
  6887.                 }
  6888.                 return null;
  6889.         }
  6890. };
  6891.  
  6892. WebmParser = function (data) {
  6893.         var EBMLId = 0x1A45DFA3,
  6894.                 segmentId = 0x18538067,
  6895.                 voidId = 0xEC;
  6896.         function WebmElement(data, dataLength, offset) {
  6897.                 var num, clz, id, size, headSize = 0;
  6898.                 if (offset + 4 >= dataLength) {
  6899.                         return;
  6900.                 }