Greasy Fork is available in English.

Search posts

Search for posts by user(-s) or(and) text in current topic

От 15.08.2018. Виж последната версия.

// ==UserScript==
// @name           Search posts
// @name:ru        Поиск постов
// @description    Search for posts by user(-s) or(and) text in current topic
// @description:ru Найти посты по пользователю(-ям) и(или тексту в текущей теме
// @version        3.2.3
// @date           16.08.2018
// @author         Halibut
// @namespace      https://greasyfork.org/en/users/145947-halibut
// @homepageURL    https://greasyfork.org/en/scripts/36319-search-posts-by-user
// @supportURL     https://forum.ru-board.com/topic.cgi?forum=2&topic=5673&glp
// @license        HUG-WARE
// @include        http*://forum.ru-board.com/topic.cgi?forum=*&topic=*
// @noframes
// @run-at         document-start
// @grant          none
// ==/UserScript==

/******************************************************************************
 * "THE HUG-WARE LICENSE" (Revision 2): As long as you retain this notice you *
 * can do whatever you want with this stuff. If we meet some day, and you     *
 * think this stuff is worth it, you can give me/us a hug.                    *
******************************************************************************/

window.addEventListener("DOMContentLoaded", function f() {
    'use strict';
    this.removeEventListener("DOMContentLoaded", f);
    const body = document.body,
          style = document.head.appendChild(document.createElement("style")),
          dats = [...document.getElementsByClassName("tb")].map(el => el.getElementsByClassName("dats")[0]),
          listener = {
              options: {
                  // <--- ОПЦИИ СКРИПТА --->
                  showAlerts: false // Показывать алерты по окончании поиска
                  , scrollToSearchResults: true  // Автоматически прокручивать страницу к результатам поиска
                                                 // (если showAlerts == true, то, при подтверждении, все-равно прокрутит к результатам)
                  , hideSearchButton: false // Показывпть кнопку поиска только при наведении курсора на пост
                  , autoOpenSpoilerWithResult: false // Раскрывать спойлер с результатами поиска автоматически
                  , reversPostSorting: true // обратить сортировку по дате для найденных постов (от новых к старым)
                  , headToResult: false // Включать шапку темы (если она создана выбранным пользоватем или в ней найден текст) в результаты поиска
                  , createCnMenu: true // Добавить в контекстное меню пункт запускающий поиск (только для FF)
                  , searchOnProfileLinksByRMB: true // Поиск при клике по ссылкам ведущим на профиль пользователя
                                                    // (только если нет выделенного текста и не нажаты клавиши модификаторы)
                  , fillSearchFormWithNames: true // Заполнять в диалоговом окне поиска по тексту поле именем(-ами)
                  , nameToFillSrchForm: null // Впишите сюда имя (в кавычках) которым всегда будет заполняться поле имени в форма поиска по ПКМ
                  // Список имен которые можно будет выбрать из выпадающего меню в форме поиска --->
                  , namesToSearch: ['Name1', 'Name2', 'Name3', 'etc...']
              }
              , names: []
              , txt: ''
              , sort: null
              , ttlShow: '\u25BA Показать результаты поиска'
              , ttlHide: '\u25BC Скрыть результаты поиска'
              , ttlLdng: '\u23F3 Поиск...'
              , ttlNotFnd: '\u26A0 Постов не найдено!'
              , get name() {
                  return this.name = this.getName()
              }
              , set name(str) {
                  return this.setName(str)
              }
              , get text() {
                  const text = this.txt;
                  delete this.txt;
                  return text;
              }
              , set text(str) {
                  if (str)
                      return this.txt = str;
              }
              , get win() {
                  delete this.win;
                  return this.win = window.top
              }
              , get loc() {
                  delete this.loc;
                  return this.loc = this.win.location.href
              }
              , get actnBox() {
                  delete this.actnBox;
                  return this.actnBox = [...document.getElementsByTagName("table")].find(el => el.querySelector("a[href^='post.cgi?action=new']")
                                                                                         || !el.getElementsByTagName('td')[0].children.length)
              }
              , get isFF() {
                  delete this.isFF;
                  return this.isFF = this.win.navigator && this.win.navigator.userAgent && this.win.navigator.userAgent.toLowerCase().includes("firefox")
              }
              , get spHd() {
                  delete this.spHd;
                  return this.spHd = this.getSpHd()
              }
              , get spBd() {
                  delete this.spBd;
                  return this.spBd = this.getSpBd()
              }
              , get spTTl() {
                  delete this.spTTl;
                  return this.spTTl = this.spHd && this.spHd.getElementsByTagName('td')[0]
              }
              , getSel() {
                  return this.win.getSelection && this.win.getSelection().toString() || ""
              }
              , spawn(gen) {
                  const continuer = (verb, arg) => {
                            let result;
                            try {
                                result = generator[verb](arg);
                            } catch (err) {
                                return Promise.reject(err);
                            }
                            if (result.done) {
                                return result.value;
                            } else {
                                return Promise.resolve(result.value).then(onFulfilled, onRejected);
                            };
                        },
                        generator = gen(),
                        onFulfilled = continuer.bind(continuer, 'next'),
                        onRejected = continuer.bind(continuer, 'throw');
                  return onFulfilled();
              }
              , prompt(name, txt) {
                  return new Promise((res, rej) => {
                      delete this.sort;
                      body.appendChild(document.createElement('div')).outerHTML = html;
                      const mdlOverlay = document.getElementById('spu-modal'),
                            clsBtn = document.getElementById('spu-close'),
                            regChkbx = document.getElementById('spu-regexp'),
                            csChkbx = document.getElementById('spu-case'),
                            sntChkbx = document.getElementById('spu-sentence'),
                            wholeWord = document.getElementById('spu-whole'),
                            flgsFrm = document.getElementById('spu-flags'),
                            txtFrm = document.getElementById('spu-txtarea'),
                            nmsFrm = document.getElementById('spu-names'),
                            nmsBtn = document.getElementById('spu-names-list'),
                            srchBtn = document.getElementById('spu-srch-strt'),
                            fromOld= document.getElementById('spu-fromold'),
                            fromNew= document.getElementById('spu-fromnew'),
                            nmsMenu = document.getElementById('spu-nms-menu'),
                            hdToResult = document.getElementById('spu-head');
                      name && (nmsFrm.value = name); txt && (txtFrm.value = txt); txtFrm.focus();
                      for (const nm of this.options.namesToSearch) {
                          if (nm) {
                              const menuitem = nmsMenu.appendChild(document.createElement('li'));
                              menuitem.textContent = nm;
                              menuitem.onclick = () => nmsFrm.value += (nmsFrm.value ? ',' + nm : nm);
                          }
                      }
                      hdToResult.checked = this.options.headToResult;
                      hdToResult.onclick = () => this.options.headToResult = hdToResult.checked;
                      if (this.options.reversPostSorting)
                          fromNew.checked = true;
                      else
                          fromOld.checked = true;
                      txt && txt.startsWith('"') && txt.endsWith('"') && (sntChkbx.checked = true);
                      for (const el of [...mdlOverlay.getElementsByClassName('clear-form')])
                          el && (el.onclick = () => el.nextElementSibling.value = '');
                      clsBtn.onclick = () => {mdlOverlay.remove(); body.onwheel = null; this.names.length = 0; res(null)};
                      mdlOverlay.onclick = mdlOverlay.oncontextmenu = mdlOverlay.onwheel = e => {
                          if (e.target != mdlOverlay) return;
                          e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                      };
                      body.onwheel = e => {
                          e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                      };
                      nmsBtn.onclick = () => nmsMenu.hidden = !nmsMenu.hidden;
                      sntChkbx.onclick = () => {
                          let val = txtFrm.value.trim(),
                              quoted = val && val.startsWith('"') && val.endsWith('"');
                          sntChkbx.checked
                              ? !quoted && (val = '"' + val + '"')
                              : quoted && (val = val.slice(val.indexOf('"') + 1, val.lastIndexOf('"')));
                          wholeWord.disabled = sntChkbx.checked;
                          txtFrm.value = val;
                      }
                      fromOld.onclick = fromNew.onclick = () => this.sort = fromNew.checked;
                      regChkbx.onclick = e => {
                          flgsFrm.disabled = !regChkbx.checked;
                          sntChkbx.disabled = csChkbx.disabled = wholeWord.disabled = regChkbx.checked;
                          !regChkbx.checked && (flgsFrm.value = '');
                      }
                      srchBtn.onclick = () => {
                          const val = {n: nmsFrm.value, t: regChkbx.checked ? txtFrm.value : txtFrm.value.trim(), rgxp: regChkbx.checked, flags: flgsFrm.value, cs: csChkbx.checked, ww: wholeWord.checked};
                          body.onwheel = null; mdlOverlay.remove();
                          res(val);
                      }
                  })
              }
              , getPosts([url, page], name, txt) {
                  return new Promise((res, rej) => {
                      let posts;
                      if (url == this.loc) {
                          posts = document.getElementsByClassName('tb');
                          if (posts && !!posts.length) {
                              posts = this.filterPosts(posts, name, txt, page);
                              return res(posts.map(el => el.cloneNode(true)));
                          }
                      };
                      const xhr = new XMLHttpRequest();
                      xhr.open("GET", url, true);
                      xhr.onload = re => {
                          if (!(xhr.readyState == 4 && xhr.status == 200)) return rej("error");
                          posts = xhr.responseXML.getElementsByClassName('tb');
                          if (posts && !!posts.length) {
                              posts = this.filterPosts(posts, name, txt, page);
                              return res(posts);
                          }
                      };
                      xhr.onerror = xhr.onabort = xhr.ontimeout = () => rej("error");
                      xhr.responseType = "document";
                      xhr.send();
                  })
              }
              , procesPosts(posts) {
                  return new Promise((res, rej) => {
                      posts = this.flatPosts(posts).filter(post => !!post);
                      if (posts && !!posts.length) {
                          if (typeof this.sort == "boolean")
                              this.sort && (posts = posts.reverse());
                          else if (this.options.reversPostSorting)
                              posts = posts.reverse();
                          posts = this.stylePosts(posts);
                          for (const post of posts)
                              this.spBd.appendChild(post);
                          res(posts.length)
                      }
                      else
                          rej("not found");
                  })
              }
              , filterPosts(posts, name, txt, page) {
                  if (txt) {
                      var txtToSearch = txt.val;
                      if (txt.type != 'rgxp') {
                          const rgxp = str => {
                              str = str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
                              return new RegExp('(?:[^а-яА-Яa-zA-Z0-9]|^)' + str + '(?:(?!(?=[а-яА-Яa-zA-Z0-9]))|$)')
                          };
                          txtToSearch = txtToSearch.map(sntnc => sntnc.replace(/\s+/g, ' '));
                          txt.cs && (txtToSearch = txtToSearch.map(sntnc => sntnc.toLowerCase()));
                          txt.ww && (txtToSearch = txtToSearch.map(sntnc => rgxp(sntnc)))
                      }
                  };
                  return [...posts].filter(post => {
                      const isUser = name && name.includes(post.querySelector("td.dats b").textContent.toLowerCase().replace(/\s/g, '_')),
                            chkHead = this.options.headToResult && page == 1 || !post.querySelector('a.tpc[href$="&postno=1"]'),
                            isText = () => {
                                let pstEl = post.getElementsByClassName('post')[0],
                                    pstTxt = pstEl.textContent;
                                if (!(pstTxt && !!pstTxt.length)) return false;
                                if (txt.type != 'rgxp') {
                                    pstTxt = pstTxt.replace(/\s+/g, ' ');
                                    txt.cs && (pstTxt = pstTxt.toLowerCase())
                                };
                                switch (txt.type) {
                                    case 'all': {
                                        return txtToSearch.every(sntnc => !txt.ww ? pstTxt.indexOf(sntnc) != -1 : sntnc.test(pstTxt))
                                    }; break;
                                    case 'any': {
                                        return txtToSearch.some(sntnc => !txt.ww ? pstTxt.indexOf(sntnc) != -1 : sntnc.test(pstTxt))
                                    }; break;
                                    case 'rgxp': {
                                        return txtToSearch.test(pstTxt)
                                    }; break;
                                    case 'snt': {
                                        return !txt.ww ? pstTxt.indexOf(txtToSearch) != -1 : txtToSearch[0].test(pstTxt)
                                    }; break;
                                    case 'some': {
                                        return txtToSearch.some(sntnc => !txt.ww ? pstTxt.indexOf(sntnc) != -1 : sntnc.test(pstTxt))
                                    }; break;
                                }
                            };
                      if (name) {
                          if (chkHead && isUser) {
                              return txt ? isText() : true
                          }
                      }
                      else {
                          return chkHead && isText()
                      }
                  });
              }
              , stylePosts(posts) {
                  const color1 = "#EEEEEE",
                        color2 = "#FFFFFF"
                  return posts.map((post,indx) => {
                      const tdEls =  post.getElementsByTagName("td");
                      for (const td of tdEls) {
                          td.bgColor && (td.bgColor = ((indx + 1) % 2) ? color1 : color2);
                      }
                      return post
                  })
              }
              , flatPosts(posts) {
                  return posts.reduce((a, b) => a.concat(b), [])
              }
              , getName() {
                  const name = [...new Set(this.flatPosts(this.names))].map(nm => nm && nm.replace(/(?:\s+)?,(?:\s+)?/).replace(/\s/g, "_").toLowerCase()).filter(nm => !!nm);
                  if (name && !!name.length)
                      return name
              }
              , setName(str) {
                  if (str)
                      return this.names.push(str);
              }
              , getSearchQuery(str, cs, ww) {
                  const query = {type: null, val: null, cs: cs, ww: ww};
                  if (Array.isArray(str)){
                      query.type = 'rgxp'; try {query.val = new RegExp(str[0], str[1])} catch(err) {listener.names.length = 0; return this.win.alert(err)}
                      return query
                  }
                  if (str.includes('OR')){
                      query.type = 'some'; query.val = str.split(/(?:\s+)?OR(?:\s+)?/);
                      return query
                  }
                  if (str.includes('AND')){
                      query.type = 'all'; query.val = str.split(/(?:\s+)?AND(?:\s+)?/);
                      return query
                  }
                  if (str.startsWith('"') && str.endsWith('"')) {
                      query.type = 'snt'; query.val = [str.slice(str.indexOf('"') + 1, str.lastIndexOf('"'))];
                      return query
                  }
                  else {
                      query.type = 'any'; query.val = str.trim().split(/\s+/)
                      return query
                  }
                  return null
              }
              , getPages() {
                  const paginator = [...document.getElementsByTagName("p")].find(el => el.textContent && el.textContent.startsWith("Страницы: "));
                  if (paginator)
                      return [...paginator.getElementsByTagName("td")[0].children].filter(el => !el.title).map((el, indx) => [el.href || this.loc, indx + 1]);
                  else
                      return [[this.loc, 1]];
              }
              , getSpHd() {
                  let spoilerHead = document.getElementById("spu-spoiler-head");
                  if (!spoilerHead) {
                      const dummyNode = this.actnBox.parentNode.insertBefore(document.createElement('div'), this.actnBox.nextElementSibling);
                      dummyNode.outerHTML = '<table id="spu-spoiler-head" width="95%" cellspacing="1" cellpadding="3" bgcolor="#999999" align="center" border="0"><tbody><tr><td valign="middle" bgcolor="#dddddd" align="left"></td></tr><td class="small" bgcolor="#dddddd" align="center">Найдено: <span id="spu-fndd"></span>   Ошибок: <span id="spu-errs"></span></td></tbody></table>';
                      spoilerHead = this.actnBox.nextElementSibling;
                      spoilerHead.id = "spu-spoiler-head";
                      spoilerHead.hidden = true;
                      const spTitle = spoilerHead.getElementsByTagName('td')[0];
                      spoilerHead.onclick = e => {
                          e.preventDefault(); e.stopPropagation();
                          const spoilerBody = this.spBd;
                          if (e.button != 0 || !spoilerBody || spoilerHead.hasAttribute("notready")) return;
                          spTitle.textContent = spoilerBody.hidden ? this.ttlHide : this.ttlShow;
                          spoilerBody.hidden = !spoilerBody.hidden;
                      }
                  }
                  return spoilerHead;
              }
              , getSpBd() {
                  let spoilerBody = document.getElementById("spu-spoiler-body");
                  if (!spoilerBody) {
                      const spoilerHead = this.spHd;
                      spoilerBody = spoilerHead.parentNode.insertBefore(document.createElement("table"), spoilerHead.nextElementSibling);
                      spoilerBody.id = "spu-spoiler-body";
                      spoilerBody.align = "center";
                      spoilerBody.width = "95%";
                      spoilerBody.hidden = true
                  }
                  return spoilerBody;
              }
              , endNotify(fndd,errs,ntfnd) {
                  if (fndd) {
                      const spoilerHead = this.spHd,
                            spoilerBody = this.spBd,
                            confirm = this.win.confirm("Поиск окончен!\nНайдено: " + fndd + (errs ? "\nОшибок: " + errs : "") + "\nПерейти к резульататам?");
                      if (confirm) {
                          spoilerHead.scrollIntoView({behavior:"smooth"});
                          if (this.options.autoOpenSpoilerWithResult) {
                              this.spTTl.textContent = this.ttlHide;
                              spoilerBody.hidden = false;
                          }
                      }
                  }
                  else
                      this.win.alert("Постов не найдено!" + (errs ? "\nОшибок: " + errs : ""));
              }
              , handleEvent(e, name, prmpt) {
                  e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                  let cs, ww, txt, sel = listener.getSel();
                  // spawn function by Jake Archibald https://gist.github.com/jakearchibald/31b89cba627924972ad6
                  this.spawn(function*() {
                      if (sel && !prmpt && !e.ctrlKey) {
                          const confirm = listener.win.prompt('На странице был выделен текст.\nЗапустить поиск с именем пользователя из выделенного текста (нажать "Да"),\nили по тексту, с открытием диалога для редактирования (нажать "Отмена")?', sel);
                          confirm ? (name = confirm) : (prmpt = true);
                      }
                      if (name && !prmpt)  {
                          listener.name = name;
                          if (e.ctrlKey) return;
                      }
                      if (prmpt) {
                          listener.name = (e.button == 2 && listener.options.nameToFillSrchForm)
                              ? listener.options.nameToFillSrchForm
                              : name;
                          sel && (txt = sel);
                          const rVal = yield listener.prompt(listener.options.fillSearchFormWithNames && listener.name, txt);
                          listener.names.length = 0;
                          if (!rVal) return;
                          cs = rVal.cs; ww = rVal.ww;
                          listener.name = rVal.n;                          
                          listener.text = rVal.rgxp ? [rVal.t, rVal.flags] : rVal.t;
                      }
                      const nmsToSearch = listener.name,
                            txtToSearch = listener.text,
                            isNames = nmsToSearch && !!nmsToSearch.length,
                            isTexxt = txtToSearch && !!txtToSearch.length,
                            txtQuery = isTexxt && listener.getSearchQuery(txtToSearch, cs, ww);
                      if (!isNames && !isTexxt || isTexxt && !txtQuery) return;
                      const pageLinks = listener.getPages(),
                            spoilerHead = listener.spHd,
                            spoilerBody = listener.spBd,
                            spoilerTitle = listener.spTTl,
                            errsLbl = document.getElementById("spu-errs"),
                            findedLbl = document.getElementById("spu-fndd");
                      spoilerHead.hidden = false; spoilerTitle.textContent = listener.ttlLdng;
                      spoilerHead.setAttribute("notready", "true"); spoilerBody.hidden = true;
                      listener.options.scrollToSearchResults && !listener.options.showAlerts && spoilerHead.scrollIntoView({behavior:"smooth"});
                      let errorsCntr = 0;
                      errsLbl.textContent = findedLbl.textContent = 0;
                      if (spoilerBody.children && !!spoilerBody.children.length)
                          for (const node of [...spoilerBody.children])
                              node.remove();
                      Promise.all(pageLinks.map(link => {
                          return listener.getPosts(link, isNames && nmsToSearch, isTexxt && txtQuery).catch(err => {
                              errorsCntr += 1; errsLbl.textContent = errorsCntr
                          })
                      })).then(
                          posts => {
                              return listener.procesPosts(posts)
                          }
                      ).then(
                          founded => {
                              findedLbl.textContent = founded;
                              spoilerTitle.textContent = listener.ttlShow;
                              spoilerHead.removeAttribute("notready");
                              if (listener.options.showAlerts)
                                  listener.endNotify(founded, errorsCntr);
                              else if (listener.options.autoOpenSpoilerWithResult) {
                                  spoilerBody.hidden = false
                                  spoilerTitle.textContent = listener.ttlHide;
                              };
                              return
                          }
                      ).catch(err => {
                          if (err == "not found") {
                              spoilerTitle.textContent = listener.ttlNotFnd;
                              listener.options.showAlerts && listener.endNotify(null, errorsCntr, notfound);
                              return
                          }
                          errorsCntr += 1; errsLbl.textContent = errorsCntr
                      });
                      listener.names.length = 0;
                  });
              }
          },
          prmptTxt = 'На странице был выделен текст.\nЗапустить поиск с именем пользователя из выделенного текста (нажать "Да"),\nили по тексту, с открытием диалога для редактирования (нажать "Отмена")?',
          hlpNms = 'Чтобы искать посты для всех пользователей, по тексту, - оставьте поле имени пустым.\nИмена в форму поиска вводить без учета регистра, разделяя запятой (без пробела).\nИмена с пробелами - ищутся и с пробелом и с _.',
          hlpTxt =
          [
              'Чтобы искать только посты по именам пользователей - оставьте поле текста пустым.',
              '',
              'Регулярные выражения вводить включив чекбокс.',
              'Без начального и закрывающего слеша.',
              'Для ввода флагов - отдельное поле,',
              'Регулярки не проверяются на валидность.',
              '',
              'Поиск без регулярок:',
              '\tраз два три - искать любое из слов',
              '\tраз два OR три OR пять - искать любую из фраз: &quot;раз два&quot;, &quot;три&quot;, &quot;пять&quot;',
              '\tраз два AND три AND пять - искать все фразы',
              '\t&quot;раз два три&quot; - (в двойных кавычках) искать фразу целиком',
              'Комбинировать операторы пока нельзя.',
              '',
              'Поиск с учетом регистра по умолчанию. Для поиска без учета - включить чекбокс.',
              'При поиске без регулярок все пробелы, переносы, табуляции - заменяются на единичный пробел,',
              'как в задании для поиска, так и в тексте постов.',
              'Слово &quot;поле&quot; - найдется и в &quot;Наполеон&quot;.',
          ].join('\n'),
          wholeHlp = 'Искать слово(-а) или фразу(-ы) только если предыдущий и последующий символы не являются буквенно-цифровыми',
          nmBtnHlp = 'Открыть меню для добавления имен из списка сохраненных.\nИмена задаются в опциях скрипта, в коде\n(при обновлении скрипта пользовательские изменения сбрасываются, делайте копию списка)',
          clsImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDoAABSCAABFVgAADqXAAAXb9daH5AAAAEhSURBVHja1FMxS8NgEH1XxCUuri24VKmLY6qDPyDoIF0k0Dmj4GBoQZBWBLEVOnaI0LEiXTIo/gAHO7sIOjm4SsEu4nfPJQGNIRk6iA/ecI/jeHePE5KYBQXMiL8fMJcUVja2VwG4ALrP45spACyvb1kAfACXT/fXj5kOSHVJBal+uepY5apjkepHmpu7wklj74JGEdGPCBrF6eF+kOyXlBjlKrwtNo7PvYKIAoCShbOjg2B3x3kFwEwHIsJefzBR84nv7PUHExFhroOltc34YGnovjzcTTMdqDG+GgM1Bp12M+i0m0FcqzG/B5P8wVrds4sVuzUchSWSQlKGo7BUrNitWt2zk/1pR5wHsADgLd6ZpABYBPAO4CMvhX/2C18DAHuyrpoNc5ujAAAAAElFTkSuQmCC',
          questionMark = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAn1BMVEUAAAAAbfMAbfEAbe8AbfAAbfAAbfEAbu8AcO8AAP8Abu8AbfAAbfAAbfEAgP8AbPAAbfAAbPIAa/IAbvAAbfAAbPEAbe0AcO8AbPAAbfAAceMAbfAAbfAAbfAAbPAAbfAAbfAAZv8AafAAbfEAavQAVf8AbPEAcfEAbf8AbfAAbfAAbe8Abe8AbfAAbfAAbfAAa/IAbfAAbfAAbfAAAADuFkhRAAAAM3RSTlMAKn606eKxgCABcvj0bgK19ztMl+1cDhA0+gnw4fnI09sFEcMYA6ASB77xcJOvzc4T8kSdjffTAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+IIDBMlMtO6eB4AAAChSURBVBjTTY/pDoMgEIQHAVHE2lNr7/uyt+//bl3Ypun8YL5dks0MQBKRVDo2CVipzdogl3f83Cnan4qUFjlB1/T6gyGBBRJHPkJZYUyQCRiyelJOXYUZYYSY3vli2a7Wm5pQQvO57W5/8K6geHE8nYNryOCX5sofMSKG253dQISYDzw5LMW3Hl7vJixyX+U/OpfJHY+ZTb99ExNrJSPh+QOxnx6dvxBTegAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wOC0xMlQxOTozNzo1MCswMjowMEnQ2mMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDgtMTJUMTk6Mzc6NTArMDI6MDA4jWLfAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAABJRU5ErkJggg==',
          html =
          [
              '<div id="spu-modal">',
              '    <div>',
              '        <button class="spu" id="spu-close" title="Отменить поиск">X</button><br>',
              '        <fieldset>',
              '            <legend>Сортировать найденное</legend>',
              '            <label for="spu-fromnew">От новых к старым</label>',
              '            <input id="spu-fromnew" name="sort" type="radio">',
              '            <label for="spu-fromold">От старых к новым</label>',
              '            <input id="spu-fromold" name="sort" type="radio">',
              '            <label for="spu-head">Включить шапку темы в результат поиска</label>',
              '            <input id="spu-head" type="checkbox">',
              '        </fieldset>',
              '        <fieldset>',
              '            <legend>Введите имя(-ена)',
              '                <img src="' + questionMark + '" title="' + hlpNms + '">',
              '            </legend>',
              '            <li>',
              '                <button class="spu" id="spu-names-list" title="' + nmBtnHlp + '">Добавить имена</button>',
              '                <div>',
              '                    <ul id="spu-nms-menu" hidden="true">',
              '                    </ul>',
              '                </div>',
              '            </li>',
              '            <img class="spu clear-form" src="' + clsImg + '" title="Очистить форму">',
              '            <input id="spu-names">',
              '        </fieldset>',
              '        <fieldset id="spu-col">',
              '            <legend>Введите текст',
              '                <img src="' + questionMark + '" title="' + hlpTxt + '">',
              '            </legend>',
              '            <fieldset>',
              '                <legend>Опции поиска</legend> ',
              '                <input id="spu-sentence" type="checkbox">',
              '                <label for="spu-sentence">Фразу</label>',
              '                <input id="spu-whole" type="checkbox">',
              '                <label for="spu-whole" title="' + wholeHlp + '">Точное соответствие</label>',
              '                <input id="spu-case" type="checkbox">',
              '                <label for="spu-case">Без учета регистра</label>',
              '                <input id="spu-regexp" type="checkbox">',
              '                <label for="spu-regexp">RegExp</label>',
              '                <label for="spu-flags">Флаги: </label>',
              '                <input id="spu-flags" size="6" disabled="" type="text">',
              '            </fieldset>',
              '            <div>',
              '                <img class="spu clear-form" src="' + clsImg + '" title="Очистить форму">',
              '                <textarea id="spu-txtarea"></textarea>',
              '            </div>',
              '        </fieldset><br>',
              '        <div></div>',
              '        <div id="spu-sticky">',
              '            <button id="spu-srch-strt" class="spu">Начать поиск</button>',
              '        </div>',
              '    </div>',
              '</div>'
          ].join('\n'),
          css =
          [
              '#spu-spoiler-head {',
              '    font-family: Segoe UI Emoji, bitstreamcyberbit, quivira, Verdana, Arial, Helvetica, sans-serif;',
              '    -moz-user-select: none !important;',
              '    -webkit-user-select: none !important;',
              '    -ms-user-select: none !important;',
              '    user-select: none !important;',
              '    cursor: pointer !important',
              '}',
              '#spu-spoiler-head[notready] {',
              '    cursor: not-allowed !important;',
              '}',
              '#spu-spoiler-body {',
              '    border: 1px solid black !important;',
              '    padding: 1em .5em !important;',
              '}',
              '#spu-spoiler-body button.spu' + (listener.options.hideSearchButton ? ', .tb:not(:hover) button.spu' : '') + '{',
              '    visibility: hidden !important;',
              '}',
              '.spu {',
              '    padding: .05em .5em .1em !important;',
              '    background: rgb(251,251,251) !important;',
              '    color: rgb(51, 51, 51) !important;',
              '    border: 1px solid rgba(66,78,90,0.2) !important;',
              '    border-radius: 2px !important;',
              '    box-shadow: none !important;',
              '    font-size: 1em !important;',
              '}',
              '.spu:hover {',
              '    background: rgb(235,235,235) !important;',
              '}',
              '.spu:active {',
              '    background: rgb(225,225,225) !important;',
              '}',
              '#spu-modal {',
              '    position: fixed !important;',
              '    top: 0 !important;',
              '    right: 0 !important;',
              '    bottom: 0 !important;',
              '    left: 0 !important;',
              '    background: rgba(0,0,0,0.8) !important;',
              '    z-index: 2147483647 !important;',
              '}',
              '#spu-modal > div {',
              '    position: relative !important;',
              '    width: 45vw;',
              '    max-height: 80vh !important;',
              '    display: flex !important;',
              '    flex-flow: column !important;',
              '    margin: 10vh auto !important;',
              '    padding: 20px !important;',
              '    border-radius: 3px !important;',
              '    background: rgb(251, 251, 251) !important;',
              '    resize: both !important;',
              '    overflow: auto !important;',
              '}',
              '#spu-modal fieldset,',
              '#spu-modal fieldset div {',
              '    display: flex !important;',
              '    position: relative !important;',
              '    flex-flow: column !important;',
              '    font-weight: 600 !important;',
              '}',
              '#spu-modal legend img {',
              '    cursor: help !important;',
              '    padding: 0 5px !important;',
              '    vertical-align: bottom !important;',
              '}',
              '#spu-modal .clear-form {',
              '    position: absolute !important;',
              '    padding: 0 !important;',
              '    border-radius: 100% !important;',
              '}',
              '#spu-modal fieldset > .clear-form {',
              '    top: -5px !important;',
              '    right: 0 !important;',
              '}',
              '#spu-modal div > .clear-form {',
              '    top: 0 !important;',
              '    right: -7px !important;',
              '}',
              '#spu-modal fieldset:not([id]) {',
              '    display: flex;',
              '    flex-flow: wrap !important;',
              '    align-items: center !important;',
              '    justify-content: space-evenly !important;',
              '}',
              '#spu-modal fieldset:nth-of-type(3),',
              '#spu-modal fieldset > div,',
              '#spu-modal input:not([type]) {',
              '    flex-grow: 1 !important;',
              '}',
              '#spu-modal label {',
              '    font-weight: 400 !important;',
              '}',
              '#spu-modal input:not([type]),',
              '#spu-modal #spu-names-list {',
              '    height: 2.5em !important;',
              '}',
              '#spu-modal textarea {',
              '    resize: none !important;',
              '    flex-basis: 30vh !important;',
              '    flex-grow: 1 !important;',
              '    margin: 10px 2px 0 !important;',
              '}',
              '#spu-modal button {',
              '    font-weight: 600 !important;',
              '}',
              '#spu-modal #spu-close {',
              '    position: absolute !important;',
              '    top: 0 !important;',
              '    right: 0 !important;',
              '    padding: 0 15px !important;',
              '}',
              '#spu-modal #spu-srch-strt {',
              '    padding: 5px 0 !important;',
              '    flex-grow: 1 !important;',
              '}',
              '#spu-modal div:empty {',
              '    min-height: 20px;',
              '}',
              '#spu-modal #spu-sticky {',
              '    display: flex !important;',
              '    position: sticky !important;',
              '    bottom: -20px;',
              '}',
              '#spu-modal fieldset > li {',
              '    display: inline-block !important;',
              '    position: relative !important;',
              '}',
              '#spu-modal #spu-nms-menu li:not(:last-child) {',
              '    border-bottom: solid .5px lightgrey !important;',
              '}',
              '#spu-modal #spu-nms-menu li {',
              '    padding: .25em !important;',
              '    padding-left: 1em !important;',
              '    cursor: default !important;',
              '    text-align: start !important;',
              '    font-weight: 400 !important;',
              '}',
              '#spu-modal #spu-nms-menu li:hover {',
              '    color: #fff !important;',
              '    background: linear-gradient(to bottom, #6f81f5, #3f51f2) repeat-x !important;',
              '}',
              '#spu-modal li {',
              '    list-style-type: none !important;',
              '}',
              '#spu-modal li > div {',
              '    position: absolute !important;',
              '    top: 100% !important;',
              '    left: 0 !important;',
              '    right: 0 !important;',
              '    z-index: 999',
              '}',
              '#spu-modal #spu-nms-menu {',
              '    margin-top: 0 !important;',
              '    padding: 0 !important;',
              '    background: #fff !important;',
              '    border: 1px solid rgba(66,78,90,0.2) !important;',
              '}',
              '@media screen and (-webkit-min-device-pixel-ratio:0) {',
              '    #spu-modal fieldset:not([id]) {',
              '        display: inline !important;',
              '        text-align: center !important;',
              '    }',
              '    #spu-modal input:not([type]) {',
              '        width: calc(100% - 121px) !important;',
              '    }',
              '    #spu-modal fieldset > .clear-form {',
              '        top: 8px !important;',
              '        right: 2px !important;',
              '    }',
              '    #spu-modal #spu-sticky {',
              '        bottom: 0px !important;',
              '    }',
              '    #spu-modal div:empty {',
              '        min-height: 0 !important;',
              '    }',
              '    #spu-modal #spu-srch-strt {',
              '        height: max-content !important;',
              '    }',
              '}'
          ].join('\n');
    style.type = "text/css"; style.textContent = css;
    if (dats)
        for (const dat of dats) {
            const button = dat.appendChild(document.createElement("br")) && dat.appendChild(document.createElement("br"))
                               && dat.appendChild(document.createElement("button")),
                  name = dat.getElementsByTagName("b")[0].textContent;
            button.className = "spu";
            button.textContent = "Найти посты";
            button.title = "Ctrl + ЛКМ: Добавить этого пользователя в задание для поиска\nЛКМ:\n\tПоиск постов для пользователя(-ей)\n\tПри наличии выделенного текста - поиск по тексту и/или по имени\n\tс диалогом выбора и редактирования\nПКМ: Показать форму для поиска";
            button.onclick = button.oncontextmenu = e => listener.handleEvent(e, name, e.button == 2);
        }
    if (listener.options.searchOnProfileLinksByRMB)
        body.addEventListener("contextmenu", e => {
            const target = e.target.closest("a[href*='?action=show&member=']"),
                  name = target && target.search.split("&member=")[1];
            if (!target || !name || e.altKey || e.shiftKey || e.ctrlKey || e.metaKey
                || !!listener.getSel() || target.closest("#spu-spoiler-body")) return;
            listener.handleEvent(e, name)
        });
    if (listener.isFF && listener.options.createCnMenu) {
        const context = body.getAttribute("contextmenu"),
              menu = context
                        ? document.getElementById(context)
                        : body.appendChild(document.createElement("menu")),
              mitem = menu.appendChild(document.createElement("menuitem"));
        if (!context) {
            menu.id = "GM_page-actions";
            menu.type = "context";
            body.setAttribute("contextmenu", "GM_page-actions");
        };
        mitem.label = "Найти собщения пользователя(-ей)";
        mitem.onclick = e => listener.handleEvent(e, null, !listener.getSel());
    };
});