Skip to content

Instantly share code, notes, and snippets.

@megamit
Forked from samogot/search.plugin.js
Created December 6, 2016 09:46
Show Gist options
  • Save megamit/d883874f2eb1a649c3fe672caac91120 to your computer and use it in GitHub Desktop.
Save megamit/d883874f2eb1a649c3fe672caac91120 to your computer and use it in GitHub Desktop.
Last Update: 1. correct loading messages up and down without extra scrolling. 2. handle end of messages list - stop if nothing was found and cycle retry from begining (end) if something was. 3. search in full message (links, bold, cursive, any other tags except code and mentions if it isn't selected). 4. cool scrolling handle. if message visible…
//META{"name":"searchPlugin"}*//
class searchPlugin{
constructor(){
this.cancelFlag = false;
this.css = "<style class='searchBarPlugin'>"+
"#searchBarInner{"+
"float:right; position:relative; height: 40px; bottom:40px;color:#FFF; padding: 0 4px; border-radius: 4px;"+
'}'+
'.buttonClose{'+
'background: transparent url(https://discordapp.com/assets/14f734d6803726c94b970c3ed80c0864.svg); background-size: cover; transition: opacity .1s easeout; opacity: .5; width:12px; height: 12px; padding: 0'+
'}'+
'.buttonClose:hover{opacity:1;}'+
'#searchBar{'+
'height:0; width:auto;'+
'}'+
'#searchBar .btn{'+
'cursor: pointer;'+
'}'+
'#searchBarInner>*+*{'+
'margin-left:5px;'+
'}'+
'#searchBarInner>.buttonClose:last-child{'+
'margin-left:30px;'+
'}'+
'#searchBarStopper{'+
'width: auto; position: absolute; right: 0; top: -100%;'+
'}'+
'#searchBar input:not(:placeholder-shown)+.search-bar-clear:after,#searchBar input:not(:placeholder-shown)+.search-bar-clear:before{'+
' background:#373a3f;width:3px;height:14px;margin:6px 0 0 11px;'+
'}'+
'#searchBar input:not(:placeholder-shown)+.search-bar-clear{cursor:pointer;pointer-events:auto;}'+
'</style>';
this.defaultSettings = {"matchCase":false, "mentionsCheck":true, "namesCheck":true, "codeCheck":true, "searchTerm":""}
}
getUpdates(){
$.getJSON("https://megamit.github.io/repository/search/version.json",(data)=>{
let version = this.getVersion().split(".")
let latest = data[0].version.split(".")
if ( latest[0] > version[0] || (latest[0] == version[0] && latest[1] > version[1]) || (latest[0] == version[0] && latest[1] == version[1] && latest[2] > version[2] )){
let notice;
notice = $(`<div class="notice"><div class="notice-dismiss"></div> Version ${latest.join(".")} of Search is available: ${data[0].notes} <a class="btn btn-primary" href="${data[0].src}" target="_blank">Download</a></div>`).on("click",".notice-dismiss",()=>notice.remove()).appendTo(".app")
}
})
}
log(){
var args = Array.prototype.slice.call(arguments);
args.unshift("%c["+this.getName()+"]", 'font-weight: bold;color: green;');
console.log.apply(console,args);
}
stop(){
$('.searchBarPlugin').remove();
}
start(){
this.loadSettings()
window.addEventListener('keydown', (function(e){if(e.ctrlKey&&e.which==70){this.displaySearchbar()} }).bind(this) );
$('head').append(this.css);
this.getUpdates()
}
scrollInCenter(range){
var messages = $('.messages')[0];
var rngRect = range.getClientRects()[0];
var mesRect = messages.getBoundingClientRect();
mesRect = {
top: mesRect.top,
bottom: mesRect.bottom - $('#searchBarInner').height()};
console.log(range, rngRect, mesRect, messages.scrollTop);
if (rngRect.bottom < mesRect.bottom && rngRect.top > mesRect.top)
return;
messages.scrollTop += (rngRect.top+rngRect.bottom) / 2 - (mesRect.top+mesRect.bottom) / 2;
}
addStopper(){
this.removeStopper()
$("<div id='searchBarStopper' class='form'><button class='btn btn-primary red' type='button'><span>Stop Search</span></button></div>").insertBefore('#searchBarInner>*:last-child')
.click(() => {
this.stopSearch();
})
}
removeStopper(){ $("#searchBarStopper").remove(); }
containsCode(node){
return ( node.nodeName=="PRE" && node.firstChild.nodeName == "CODE" || node.nodeName == "CODE" ) ;
}
containsMention(node){
return false;
}
search(query_s,settings,startNode,startIndex){
if (this.cancelFlag) return (this.cancelFlag = false)
let { matchCase, mentionsCheck, namesCheck, codeCheck, direction } = settings,
query = matchCase?query_s:query_s.toLowerCase(),
messages = $(".message-group.compact .message-content, .message-group:not(.compact) .markup"+
(namesCheck?", .message-group.compact strong.user-name, .message-group:not(.compact) strong.user-name":"")
)
.map(function expand() {
if(this.nodeType === 3)
return this;
else if(this.nodeType === 1) {
var $this = $(this);
if((codeCheck || !$this.is('code')) && (mentionsCheck || !$this.is('.highlight') && !$this.is('.edited')))
return $(this).contents().map(expand).toArray();
}
}),
foundStart = false,
found = false;
let start, check, next;
if (direction=="down"){
start = 0;
check = (i) => i<messages.length;
next = (i) => i+1 ;
} else {
start = messages.length-1;
check = (i) => i>=0;
next = (i) => i-1;
}
let limit = 1000,
msg;
for (let i = start;check(i);i=next(i)){
msg = messages[i];
let msgtxt = matchCase?msg.nodeValue:msg.nodeValue.toLowerCase();
msgtxt = msgtxt.replace(/\u00a0/g, ' ');
if (!startNode||foundStart||startNode==msg){
foundStart = true;
if(startIndex==0&&startNode==msg) continue;
let index = startNode==msg?msgtxt.lastIndexOf(query,startIndex-1):msgtxt.lastIndexOf(query)
if (index!=-1){
found=true
let sel = window.getSelection();
let range = document.createRange();
let endpoint = query.length+index
range.setStart(msg,0);
range.setEnd(msg,endpoint);
range.setStart(msg,index);
this.scrollInCenter(range);
sel.removeAllRanges();
sel.addRange(range);
//this.log("found",msg,msg.parentElement.offsetTop,"from",startNode,startIndex)
break;
}
}
if (limit-- ==0) return console.error("LIMIT BREAK",i,messages.length)
}
if (this.cancelFlag){
//this.log("stop")
this.cancelFlag = false;
}
if(found||this.cancelFlag){
this.cancelFlag = false; this.removeStopper();
}else{
var res;
if (direction=="down")
res=this.scrollChatToBottom();
else
res=this.scrollChatToTop()
if(res)
this.cancelTO = setTimeout(() => {
this.search(query,settings,msg,0)
},50)
else if(startNode)
this.search(query,settings);
else
this.cancelTO = setTimeout(() => {
this.removeStopper();
},500)
}
}
scrollChatToTop(){
if(!$(".has-more:first-child,.loading-more:first-child").length) return false;
$(".has-more:first-child button").click()
return true;
}
scrollChatToBottom(){
if(!$(".has-more:last-child,.loading-more:last-child").length) return false;
$(".has-more:last-child button").click()
return true;
}
displaySearchbar(){
if($("#searchBar").length==0){
var sb = $("<form class='searchPlugin' id='searchBar'></form>").css("display","block")
var sbInner = $("<div id='searchBarInner' class='search-bar'></div>").css("float","right")
sb.append(sbInner)
sbInner.append(
$("<div class='search-bar-inner'></div>").append(
$("<input id='searchBarInput' placeholder='Find...'>").keydown((event) => {
if(event.keyCode == 13){
event.preventDefault()
$("#searchBarUp").click();
}
})
).append(
$(
'<div class="search-bar-clear"></div>'
).click(function(){
this.previousSibling.value = "";
this.previousSibling.focus();
})
)
);
const doSearch = (isDown) => {
var q = $("#searchBarInput").val()
if (q.length>0){
var sel = window.getSelection();
if (sel.type == "Range"&&sel.anchorNode.id != "searchBarForm"){
this.addStopper();
this.search($("#searchBarInput").val(),{
matchCase: document.getElementById('searchBarCaseCheck').checked,
mentionsCheck: document.getElementById('searchBarMentionsCheck').checked,
codeCheck: document.getElementById('searchBarCodeCheck').checked,
namesCheck: document.getElementById('searchBarNameCheck').checked,
direction: isDown?"down":"up"
},sel.anchorNode,sel.anchorOffset)
}else{
this.addStopper();
this.search($("#searchBarInput").val(),{
matchCase: document.getElementById('searchBarCaseCheck').checked,
mentionsCheck: document.getElementById('searchBarMentionsCheck').checked,
codeCheck: document.getElementById('searchBarCodeCheck').checked,
namesCheck: document.getElementById('searchBarNameCheck').checked,
direction: isDown?"down":"up"
})
}
}
}
var up = $("<input id='searchBarUp' class='btn' type='button' value='&#x25B2'>").click(() => {doSearch(false)})
sbInner.append(up);
var down = $("<input id='searchBarDown' class='btn' type='button' value='&#x25BC'>").click(() => {doSearch(true)})
sbInner.append(down);
//var caseCheck = $("<input name='caseCheck' id='searchBarCaseCheck' class='btn' type='checkbox'>")
var caseCheck = $('<div class="checkbox"><div class="checkbox-inner"><input id="searchBarCaseCheck" type="checkbox" '+(this.settings.matchCase?"checked":"")+'><span></span></div><span>Match Case</span></div>').click(function(){var x = document.getElementById('searchBarCaseCheck'); x.checked = !x.checked; this.settings.matchCase = x.checked; this.saveSettings();});
var namesCheck = $('<div class="checkbox"><div class="checkbox-inner"><input id="searchBarNameCheck" type="checkbox"'+(this.settings.namesCheck?"checked":"")+'><span></span></div><span>Names</span></div>').click(function(){var x = document.getElementById('searchBarNameCheck'); x.checked = !x.checked; this.settings.namesCheck= x.checked; this.saveSettings();});
var mentionsCheck = $('<div class="checkbox"><div class="checkbox-inner"><input id="searchBarMentionsCheck" type="checkbox"'+(this.settings.mentionsCheck?"checked":"")+'><span></span></div><span>Mentions</span></div>').click(function(){var x = document.getElementById('searchBarMentionsCheck'); x.checked = !x.checked;this.settings.mentionsCheck = x.checked; this.saveSettings();});
var codeCheck = $('<div class="checkbox"><div class="checkbox-inner"><input id="searchBarCodeCheck" type="checkbox"'+(this.settings.codeCheck?"checked":"")+'><span></span></div><span>Code</span></div>').click(function(){var x = document.getElementById('searchBarCodeCheck'); x.checked = !x.checked; this.settings.codeCheck = x.checked; this.saveSettings();});
//var caseLabel = $("<label for='caseCheck' id='searchBarCaseLabel' class='btn' type='checkbox'>Match Case</label>")
sbInner.append(caseCheck)
.append(namesCheck)
.append(mentionsCheck)
.append(codeCheck);
//caseCheck.after(caseLabel);
var closeX = $("<button id='searchBarClose' class='btn buttonClose' type='button'></button>").click( () => {
this.stopSearch();
$("#searchBar").remove();
});
sbInner.append(closeX);
$(".messages-wrapper").after(sb)
}
$("#searchBarInput").focus();
}
stopSearch(){
//this.log("stopping")
clearTimeout(this.cancelTO)
this.cancelFlag = true;
this.removeStopper();
setTimeout(function(){
this.cancelFlag = false;
},50)
}
saveSettings(){
localStorage.setItem(this.getName(),JSON.stringify(this.settings));
}
loadSettings(){
this.settings = $.extend({},this.defaultSettings,JSON.parse(localStorage.getItem(this.getName())))
this.saveSettings();
}
getName(){return "searchPlugin"};
getDescription(){return "The return of ctrl+f! until at least discord devs decide to add it"};
getVersion(){return "1.2.1"};
getAuthor(){return "Megamit/Mitchell"};
load(){};
unload(){};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment