// ==UserScript==
// @name Search posts
// @name:ru Поиск постов
// @description Search for posts by user(-s) or(and) text in current topic
// @description:ru Найти посты по пользователю(-ям) и(или тексту в текущей теме
// @version 3.4.0
// @date 20.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 // Включать шапку темы (если она создана выбранным пользоватем или в ней найден текст) в результаты поиска
, srchInSigs: false // Искать ли, при по тексту, в подписях пользователей
, srchInQuotes: false // Искать ли, при по тексту, в цитатах
, imgsToLinks: true // Заменять изображения в найденных постах ссылками на них
, createCnMenu: true // Добавить в контекстное меню пункт запускающий поиск (только для FF)
, searchOnProfileLinksByRMB: true // Поиск при клике по ссылкам ведущим на профиль пользователя
// (только если нет выделенного текста и не нажаты клавиши модификаторы)
, fillSearchFormWithNames: true // Заполнять в диалоговом окне поиска по тексту поле именем(-ами)
, nameToFillSrchForm: null // Впишите сюда имя (в кавычках) которым всегда будет заполняться поле имени в форма поиска по ПКМ
// Список имен которые можно будет выбрать из выпадающего меню в форме поиска --->
, namesToSearch: ['Name1', 'Name2', 'Name3', 'etc...']
}
, names: []
, xhrPool: []
, 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]
}
, get navBox() {
delete this.navBox;
return this.navBox = this.getNavBox()
}
, getSel() {
return this.win.getSelection && this.win.getSelection().toString() || ""
}
, abortXhrs() {
if (!this.xhrPool.length) return;
for (const xhr of this.xhrPool)
xhr && xhr.abort();
this.xhrPool.length = 0
}
, getCrntPost(arr) {
const clst = this.isFF ? 0 : parseInt(this.win.innerHeight / 2);
return arr.reduce((prev, curr) => (Math.abs(curr - clst) < Math.abs(prev - clst) ? curr : prev))
}
, 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) => {
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'),
sig = document.getElementById('spu-sig'),
qt = document.getElementById('spu-qt'),
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'),
imgs = document.getElementById('spu-imgs');
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
}
imgs.checked = this.options.imgsToLinks;
imgs.onclick = () => {
this.options.imgsToLinks = hdToResult.checked
}
sig.checked = this.options.srchInSigs;
sig.onclick = () => {
this.options.srchInSigs = sig.checked
}
qt.checked = this.options.srchInQuotes;
qt.onclick = () => {
this.options.srchInQuotes = qt.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('"')));
txtFrm.value = val;
}
fromOld.onclick = fromNew.onclick = () => {
this.reversPostSorting = fromNew.checked
}
regChkbx.onclick = e => {
flgsFrm.disabled = flgsFrm.hidden = !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) => {
const rmv = itm => this.xhrPool.splice(this.xhrPool.indexOf(itm), 1);
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 && el.cloneNode(true)));
};
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = re => {
if (!(xhr.readyState == 4 && xhr.status == 200)) {
rmv(xhr);
return res(null)
}
posts = xhr.responseXML && xhr.responseXML.getElementsByClassName('tb');
if (posts && !!posts.length) {
rmv(xhr);
posts = this.filterPosts(posts, name, txt, page);
return res(posts)
}
else {
rmv(xhr);
return res(null)
}
}
xhr.onerror = xhr.ontimeout = xhr.onabort = () => {
rmv(xhr);
return res(null)
}
xhr.responseType = "document";
this.xhrPool.push(xhr);
xhr.send();
})
}
, procesPosts(posts) {
return new Promise((res, rej) => {
posts = this.flatPosts(posts).filter(post => !!post);
if (posts && !!posts.length) {
if (this.options.reversPostSorting)
posts = posts.reverse();
if (this.options.imgsToLinks)
posts = this.imgsToLinks(posts);
posts = this.stylePosts(posts);
for (const post of posts)
this.spBd.appendChild(post);
this.spBd.appendChild(this.navBox);
res(posts.length)
}
else
rej("not found");
})
}
, filterPosts(posts, name, txt, page) {
let txtToSearch = txt && txt.val;
if (txt) {
if (txt.type != 'rgxp') {
const rgxp = str => {
str = str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
return new RegExp('(?:[^а-яА-Яa-zA-Z0-9]|^)' + str + '(?:(?!(?=[а-яА-Яa-zA-Z0-9]))|$)', txt.cs ? 'i' : '')
};
txtToSearch = txtToSearch.map(sntnc => sntnc.replace(/\s+/g, ' '));
!txt.ww && 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 = () => {
const pstEl = post.getElementsByClassName('post')[0];
let pstTxt;
if (!this.options.srchInSigs || !this.options.srchInQuotes) {
const cpEl = pstEl.cloneNode(true);
if (!this.options.srchInSigs) {
const lstChld = cpEl.lastElementChild;
lstChld && lstChld.className == 'sing' && lstChld.remove();
}
if (!this.options.srchInQuotes) {
const qtHdr = [...cpEl.getElementsByTagName('small')].filter(el => el.textContent == 'Цитата:'),
qtBd = qtHdr.map(el => el.nextElementSibling).filter(el => el && el.localName == 'table');
for (const qts of [...qtHdr, ...qtBd])
qts && qts.remove();
}
pstTxt = cpEl.textContent;
}
else
pstTxt = pstEl.textContent;
if (!(pstTxt && !!pstTxt.length)) return false;
if (txt.type != 'rgxp') {
pstTxt = pstTxt.replace(/\s+/g, ' ');
!txt.ww && 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), [])
}
, imgsToLinks(posts) {
return posts.map(post => {
const postEl = post.getElementsByClassName('post')[0],
imgs = [...postEl.getElementsByTagName('img')].filter(el => el && el.src && !el.src.includes('://forum.ru-board.com/board/s/'));
for (const img of imgs) {
const imgLink = img.closest('a[href]'),
link = document.createElement('a');
link.href = img.src; link.target = '_blank'; link.textContent = '[IMG][/IMG]';
img.parentNode.replaceChild(link, img);
if (imgLink) {
imgLink.parentNode.insertBefore(link, imgLink);
imgLink.textContent = '[URL][/URL]';
}
}
return post
})
}
, getName() {
const name = [...new Set(this.flatPosts(this.names))].join(',').replace(/(?:\s+)?,(?:\s+)?/g, ',').replace(/\s/g, "_").toLowerCase().split(',').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) return;
if (spoilerHead.hasAttribute("notready")) {
this.abortXhrs()
}
else {
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;
}
, getNavBox() {
let navBox = document.getElementById("spu-nav");
if (!navBox) {
navBox = document.createElement('div');
const spoilerHead = this.spHd,
spoilerBody = this.spBd,
nodes = spoilerBody.children,
navBlock = navBox.appendChild(document.createElement('ul')),
navHome = navBlock.appendChild(document.createElement('li')),
navUp = navBlock.appendChild(document.createElement('li')),
navDown = navBlock.appendChild(document.createElement('li')),
navEnd = navBlock.appendChild(document.createElement('li')),
crntPst = () => {
const posts = [...nodes].slice(0, -1),
possY = posts.map(el => el.getClientRects()[0].y);
return posts[possY.indexOf(this.getCrntPost(possY))]
},
scrollToPrev = () => {
const crnt = crntPst(),
prvPost = crnt && crnt.previousElementSibling;
prvPost && prvPost.scrollIntoView({behavior:"smooth"});
},
scrollToNext = () => {
const crnt = crntPst(),
nxtPst = crnt && crnt.nextElementSibling;
nxtPst && nxtPst != navBox && nxtPst.scrollIntoView({behavior:"smooth"});
};
navBox.id = 'spu-nav'; navUp.id = 'spu-nav-up'; navDown.id = 'spu-nav-down'; navEnd.id = 'spu-nav-end'; navHome.id = 'spu-nav-home';
navUp.className = navDown.className = navEnd.className = navHome.className = 'spu-nav-btn';
navUp.title = 'Предыдущий пост'; navDown.title = 'Следующий пост'; navEnd.title = 'В конец'; navHome.title = 'К началу';
navBox.onclick = e => {
if (!e.target.id) return;
switch (e.target.id) {
case 'spu-nav-home':
spoilerHead.scrollIntoView({behavior:"smooth"});
break;
case 'spu-nav-end':
spoilerBody.lastElementChild.previousElementSibling.scrollIntoView({behavior:"smooth"});
break;
case 'spu-nav-up':
scrollToPrev();
break;
case 'spu-nav-down':
scrollToNext();
break;
default: return
}
}
}
return navBox
}
, endNotify(fndd,errs) {
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();
listener.abortXhrs();
// 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, fnddCntr = 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).then(posts => {
if (posts && !!posts.length) {
fnddCntr += posts.length; findedLbl.textContent = fnddCntr;
}
else if (posts == null) {
errorsCntr += 1; errsLbl.textContent = errorsCntr;
}
return posts
}).catch(err => {
errorsCntr += 1; errsLbl.textContent = errorsCntr;
})
})).then(
posts => {
return listener.procesPosts(posts)
}
).then(
founded => {
spoilerTitle.textContent = listener.ttlShow;
spoilerHead.removeAttribute("notready");
if (listener.options.showAlerts)
listener.endNotify(fnddCntr, errorsCntr);
else if (listener.options.autoOpenSpoilerWithResult) {
spoilerBody.hidden = false;
spoilerTitle.textContent = listener.ttlHide;
};
}
).catch(err => {
if (err == 'not found') {
spoilerTitle.textContent = listener.ttlNotFnd;
}
else {
errorsCntr += 1; errsLbl.textContent = errorsCntr;
}
if (fnddCntr) {
spoilerHead.removeAttribute("notready");
spoilerTitle.textContent = listener.ttlShow;
if (listener.options.autoOpenSpoilerWithResult && !listener.options.showAlerts) {
spoilerBody.hidden = false;
spoilerTitle.textContent = listener.ttlHide;
};
}
listener.options.showAlerts && listener.endNotify(fnddCntr, errorsCntr);
});
listener.names.length = 0;
});
}
},
prmptTxt = 'На странице был выделен текст.\nЗапустить поиск с именем пользователя из выделенного текста (нажать "Да"),\nили по тексту, с открытием диалога для редактирования (нажать "Отмена")?',
hlpNms = 'Чтобы искать посты для всех пользователей, по тексту, - оставьте поле имени пустым.\nИмена в форму поиска вводить без учета регистра, разделяя запятой (без пробела).\nИмена с пробелами - ищутся и с пробелом и с _.',
hlpTxt =
[
'Чтобы искать только посты по именам пользователей - оставьте поле текста пустым.',
'',
'Регулярные выражения вводить включив чекбокс.',
'Без начального и закрывающего слеша.',
'Для ввода флагов - отдельное поле,',
'Регулярки не проверяются на валидность.',
'',
'Поиск без регулярок:',
'\tраз два три - искать любую из разделенных пробелами частей',
'\tраз два OR три OR пять - искать любую из фраз: "раз два", "три", "пять"',
'\tраз два AND три AND пять - искать все фразы',
'\t"раз два три" - (в двойных кавычках) искать фразу целиком',
'Комбинировать операторы пока нельзя.',
'',
'Поиск с учетом регистра по умолчанию. Для поиска без учета - включить чекбокс.',
'При поиске без регулярок все пробелы, переносы, табуляции - заменяются на единичный пробел,',
'как в задании для поиска, так и в тексте постов.',
'Слово "поле" - найдется и в "Наполеон" если не включен чекбокс "Точное соответствие".',
].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==',
upArrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAYnAAAGJwFNVNjHAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAGZQTFRFAJjmAJbmAJbnAJbmAJbmApfmCprnDZvnDpznE57oFp/oHqLpPa/sZsDwic/0ldP1tuH40Oz60+372/D74vP86fb96vb96/f97Pf97fj97/j97/n98fn98fn+9fv+9/z+/P7/////B9XeKgAAAAR0Uk5TNM/x8gg9m90AAACFSURBVCjPpdJJDoMwEETRts08JxDbmASo+1+SBSRiaEtI+du36y4iIdUlKYhEwCZI8iBJ8aBuQRzzEHVdxEI1jhUH+QC4nIFmBubmCoUDAFecIWwBAGjDE5SfFd7lERKLLZMcoJ6+MNV7yBx+9dkO0sdLa22MtbZ/pveP+C94X+sdg28+CxzeEOjnGCz7AAAAAElFTkSuQmCC',
downArrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAYnAAAGJwFNVNjHAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAGZQTFRFAJjmAJbmAJbnAJbmAJbmApfmCprnDZvnDpznE57oFp/oHqLpPa/sZsDwic/0ldP1tuH40Oz60+372/D74vP86fb96vb96/f97Pf97fj97/j97/n98fn98fn+9fv+9/z+/P7/////B9XeKgAAAAR0Uk5TNM/x8gg9m90AAACISURBVCjPrZLHEoAgDEQD2HsFrMj//6SODo4lnHSPeRM2WQJAKHuJEgDioCJAcUCB4YD9DsKqk1IKwTlvy/ACok6f6qPrU7kydZXfPAJhgAzu5ul41Kf0MZVbH6B2n+Mm/e6cvPcoFq2XAlkw3lqGGNs8m+cMjcRrGg/Pyve/pWv9Wusx2M5nBYeDEOi0fx6VAAAAAElFTkSuQmCC',
homeArrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAYnAAAGJwFNVNjHAAAAIGNIUk0AAHolAACAgwAA+f8AAIDoAABSCAABFVgAADqXAAAXb9daH5AAAAFCSURBVHja7NY9bsIwGAZgiwOwIDGys7NyAu6BOnGPHoDAgEBiYWSCmYoDpBJDByYWJ+RHiVGDTWP77QBqqZoIkhCpQy29kyU/8vfZsgkZ0hYxqEkMixGDHh4TixGDmmRIW+S8OEVJMclFKwmwGLls68dEc+bg6SXMlObMSUIOiUBn4SPr6Cz8PwQklai3ZrAiCZdL9NasWImS8my+I4o1uNQYvR3TFswHNKZ7BCf1VY5AKDSm+8cBy52A1N/1lhpY7sRjgPbcQyDUr6YGQqE994oBlQHFNoxTT842jFEZFAC6qxDspFMBdlLorsJ8QHVkwePq5vl3uUJ1ZGUH+psIXOqbAJca/U2UDahP7MTGpg1fKNQn9v1AbWzj1fvA/qjgXMXl53hX8YXCxo9RG9vFbnLO/AN3AeU/mSU/+iV/Wz4HAO0TO0w8lLnaAAAAAElFTkSuQmCC',
endArrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAYnAAAGJwFNVNjHAAAAIGNIUk0AAHolAACAgwAA+f8AAIDoAABSCAABFVgAADqXAAAXb9daH5AAAAE/SURBVHja7JY9bsJAEIVXHIAGiZKenpYTcA+UintwAAwFAomGkgpqIg7gSC6poFj/YMtewNiOvX4pYglQIP7BK6XISK9aaT7NvBnNEjKiLSJRmUgqIxI9liOVEYnKZERb5Ds5hSDJJKEJAqiMJGWJquD4DygGqE00KFYIy+cwvasOiYzLVfqF48P8RG2iZQfUpxosnyNr2D5Hfarla9FAceFFcWpyL4oxUNz8HlTHKg5eehWmx1Edq8VM7q4dsOA5hAUxumun+BRVhhRbJ3wK2DohKsMXx7S9MGE/MNz2OdoLs5w9WO183PodxcBq55e3aI2ZfleFHXA0Znq5m9yXz3DDGG4Yoy+fX9vk5tzA27tzp96GYX+KsD9F6G3Yj/fm3MgO6Cwt5I3O0vpDgEctStMvLRJ/MgUffcHflq8BAGM1Oto/EMphAAAAAElFTkSuQmCC',
html =
[
'<div id="spu-modal">',
' <div>',
' <button class="spu" id="spu-close" title="Отменить поиск">X</button><br>',
' <fieldset>',
' <legend>Сортировать найденное</legend>',
' <li>',
' <label for="spu-fromnew">От новых к старым</label>',
' <input id="spu-fromnew" name="sort" type="radio">',
' <input id="spu-fromold" name="sort" type="radio">',
' <label for="spu-fromold">От старых к новым</label>',
' </li>',
' <li>',
' <label for="spu-head">Шапку в результаты',
' <img src="' + questionMark + '" title="Включать шапку темы в результат поиска, при удовлетворении условиям">',
' </label>',
' <input id="spu-head" type="checkbox">',
' </li>',
' <li>',
' <label for="spu-imgs">Без картинок',
' <img src="' + questionMark + '" title="Заменить картинки в найденных постах ссылками на них (исключая смайлы)">',
' </label>',
' <input id="spu-imgs" type="checkbox">',
' </li>',
' </fieldset>',
' <fieldset>',
' <legend>Введите имя(-ена)',
' <img src="' + questionMark + '" title="' + hlpNms + '">',
' </legend>',
' <li>',
' <button class="spu" id="spu-names-list" title="' + nmBtnHlp + '">Добавить имена',
' <img src="' + questionMark + '">',
' </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> ',
' <li>',
' <label for="spu-sig">В подписях</label>',
' <input id="spu-sig" type="checkbox">',
' </li>',
' <li>',
' <label for="spu-qt">В цитатах</label>',
' <input id="spu-qt" type="checkbox">',
' </li>',
' <li>',
' <label for="spu-sentence">Фразу</label>',
' <input id="spu-sentence" type="checkbox">',
' </li>',
' <li>',
' <label for="spu-whole">Точное соответствие',
' <img src="' + questionMark + '" title="' + wholeHlp + '">',
' </label>',
' <input id="spu-whole" type="checkbox">',
' </li>',
' <li>',
' <label for="spu-case">Без учета регистра</label>',
' <input id="spu-case" type="checkbox">',
' </li>',
' <li>',
' <label for="spu-regexp">RegExp</label>',
' <input id="spu-regexp" type="checkbox">',
' <input id="spu-flags" size="6" disabled="" hidden="" placeholder="Флаги" type="text">',
' </li>',
' </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-top: 1em !important;',
'}',
'#spu-spoiler-body:not([hidden]) {',
' display: block !important;',
'}',
'#spu-spoiler-body {',
' border: 1px solid black !important;',
' padding: .5em 0 !important;',
' box-sizing: border-box !important;',
'}',
'#spu-spoiler-body button.spu' + (listener.options.hideSearchButton ? ', .tb:not(:hover) button.spu' : '') + '{',
' visibility: hidden !important;',
'}',
'#spu-spoiler-body > .tb:last-of-type {',
' border-bottom-width: 1px !important;',
' margin-bottom: -100px !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-nav {',
' position: -webkit-sticky !important;',
' position: sticky !important;',
' display: inline-block !important;',
' height: 0 !important;',
' top: 0 !important;',
' bottom: calc(50vh + 50px) !important;',
' left: 0 !important;',
' margin-bottom: 100px !important;',
'}',
'#spu-nav ul {',
' padding: 0 !important;',
' margin: 0 !important;',
'}',
'.spu-nav-btn {',
' list-style-position: inside !important;',
'}',
'#spu-nav-up {',
' list-style-image: url("' + upArrow + '") !important;',
'}',
'#spu-nav-down {',
' list-style-image: url("' + downArrow + '") !important;',
'}',
'#spu-nav-home {',
' list-style-image: url("' + homeArrow + '") !important;',
'}',
'#spu-nav-end {',
' list-style-image: url("' + endArrow + '") !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: 50vw;',
' max-width: 1200px;',
' 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 img:not([class]) {',
' cursor: help !important;',
' padding: 0 5px !important;',
' vertical-align: text-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 {',
' -moz-user-select: none !important;',
' -webkit-user-select: none !important;',
' user-select: none !important;',
' font-weight: 400 !important;',
' vertical-align: .1em !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: -webkit-sticky !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% - 148px) !important;',
' }',
' #spu-modal label {',
' vertical-align: .2em !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;',
' }',
' @supports not ((position: sticky) or (position: -webkit-sticky)) {',
' #spu-nav {',
' position: fixed !important;',
' bottom: 0 !important;',
' left: auto !important;',
' }',
' #spu-modal #spu-sticky {',
' bottom: 0px !important;',
' position: relative !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());
};
});