nicosearchで自動補完
by
htsign
2013-05-28 [2013/05/28 03:43:42]
niconicoにおいて、検索ワードのサジェストを表示します
@@ -136,7 +136,6 @@
var xdr = new XDomainRequest();
xdr.open("GET", REQUEST_URL + "?" + query);
xdr.onload = function(){
- console.log(word);
var xml = DOMParser().parseFromString(xdr.responseText, "application/xml");
cache[word.toLowerCase()] = xml; // キャッシュに追加
createSuggestWindow(xml);
/*
* @title nicosearchで自動補完
* @description niconicoにおいて、検索ワードのサジェストを表示します
* @include http://www.nicovideo.jp/*
* @include http://seiga.nicovideo.jp/*
* @include http://live.nicovideo.jp/*
* @include http://ch.nicovideo.jp/*
* @include http://com.nicovideo.jp/*
* @include http://dic.nicovideo.jp/*
* @license NYSL
*/
// 個人的にニコニコ静画、ニコニコ生放送などの"ニコニコ動画以外"を全く使わないので、動作検証は不十分です。
// IE9以降と最新のFirefox, Chromeで動作すると思います。Operaは知りません。
if (!Element.prototype.matchesSelector) {
Object.defineProperty(Element.prototype, "matchesSelector", {
enumerable: false,
configurable: true,
value: function(property){
if (this.webkitMatchesSelector) {
return this.webkitMatchesSelector(property);
}
else if (this.mozMatchesSelector) {
return this.mozMatchesSelector(property);
}
else if (this.msMatchesSelector) {
return this.msMatchesSelector(property);
}
else if (this.oMatchesSelector) {
return this.oMatchesSelector(property);
}
else
return null;
}
});
}
// ref: http://nicoapi.com/docs/nicosearchapi.html
// ref: https://chrome.google.com/webstore/detail/pafehjbkojbbenbdklnlajbmlojnecjl
(function main(){
var REQUEST_METHOD = "post";
var REQUEST_URL = "http://nicoapi.com/ns/suggest.xml";
var RESPONSE_LENGTH = 10;
var cache = {}; // 検索ワードと結果のペアを保持
var walker = null; // カーソルキーで動かした場合のTreeWalkerを保持
(function createStyle(){
if (document.querySelector("#nicosearch-style")) {
return false;
}
var style = document.createElement("style"), i = 0;
style.id = "nicosearch-style";
document.getElementsByTagName("head")[0].appendChild(style);
([
"#nicosearch-suggestbox {\
position: absolute;\
border: 1px solid #778899;\
background-color: #fff;\
z-index: 100000;\
-webkit-user-select: none;\
-khtml-user-select: none;\
-moz-user-select: none;\
-ms-user-select: none;\
-o-user-select: none;\
user-select: none;\
}",
"#nicosearch-suggestbox > div {\
position: relative;\
}",
"#nicosearch-suggestbox > div.selected\
,#nicosearch-suggestbox > div:hover {\
background-color: #afeeee;\
}",
"#nicosearch-suggestbox a {\
text-align: left;\
}",
".nicosearch-dic-icon {\
padding-top: 1px;\
padding-bottom: 1px;\
}",
".nicosearch-name {\
position: absolute;\
top: 0;\
padding-left: 18px;\
height: 18px;\
font-size: 14px;\
cursor: pointer;\
}"
]).forEach(function(val){
style.sheet.insertRule(val, i++);
});
})();
var searchBox = (function getSearchBox(){
var target = document.getElementById("searchWord") // トップページ
|| document.getElementById("bar_search") // ニコニコ動画
|| document.getElementById("search_united") // 検索結果
|| document.querySelector(".searchText input") // 動画ページ
|| document.getElementById("bar_search") // ニコニコ静画
|| document.querySelector(".search_program .search_word") // ニコニコ生放送
|| document.getElementById("searchtxt") // ニコニコチャンネル
|| document.getElementById("search_text") // ニコニココミュニティ
|| document.getElementById("search-box") // ニコニコ大百科
;
return target;
})();
if (searchBox === null) {
window.alert("入力欄が見つかりません。");
return false;
}
var XmlHttp = function(word){
var xhr = new XMLHttpRequest();
xhr.open(REQUEST_METHOD, REQUEST_URL, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.addEventListener("readystatechange", function(evt){
if (xhr.readyState === 4) {
switch(xhr.status) {
case 200:
cache[word.toLowerCase()] = xhr.responseXML; // キャッシュに追加
createSuggestWindow(xhr.responseXML);
break;
}
}
}, false);
return xhr;
};
// どうやらIE9はXMLHttpRequestをクロスドメインには使えないみたいで…
var Xdr = function(word, query){
var xdr = new XDomainRequest();
xdr.open("GET", REQUEST_URL + "?" + query);
xdr.onload = function(){
var xml = DOMParser().parseFromString(xdr.responseText, "application/xml");
cache[word.toLowerCase()] = xml; // キャッシュに追加
createSuggestWindow(xml);
};
return xdr;
};
document.addEventListener("click", function(evt){
if (!evt.target.matchesSelector("#nicosearch-suggestbox") || evt.target !== searchBox) {
removeSuggestWindow();
}
}, false);
searchBox.addEventListener("keydown", function(evt){
var suggestbox = document.getElementById("nicosearch-suggestbox");
if (suggestbox) {
if (!walker) walker = document.createTreeWalker(
suggestbox,
NodeFilter.SHOW_ELEMENT,
function(node){
return node.parentElement === suggestbox
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP;
},
false
);
switch(evt.keyCode) {
case 38: // ↑
if (!suggestbox.querySelector(".selected")) {
walker.lastChild();
walker.currentNode.className = "selected";
}
else if (walker.previousNode()) {
walker.currentNode.nextElementSibling.className = "";
walker.currentNode.className = "selected";
}
searchBox.value = walker.currentNode.textContent;
break;
case 40: // ↓
if (!suggestbox.querySelector(".selected")) {
walker.firstChild();
walker.currentNode.className = "selected";
}
else if (walker.nextNode()) {
walker.currentNode.previousElementSibling.className = "";
walker.currentNode.className = "selected";
}
searchBox.value = walker.currentNode.textContent;
break;
case 13: // Enter
if (suggestbox.querySelector(".selected")) {
walker.currentNode.getElementsByTagName("p")[0].click();
}
break;
default:
if (suggestbox.querySelector(".selected")) {
suggestbox.getElementsByClassName("selected")[0].className = "";
}
}
}
}, false);
searchBox.addEventListener("keyup", function(evt){
window.clearTimeout(sendRequest);
// 選択状態の場合はなにもしない
var suggestbox = document.getElementById("nicosearch-suggestbox");
if (suggestbox && suggestbox.querySelector(".selected")) {
return false;
}
var word = evt.target.value;
// 文字列が空の場合はサジェストを消す
if (word.length === 0) {
removeSuggestWindow();
return false;
}
// キャッシュをチェック
// 既に結果をもらっている場合は二度送らない
if (cache[word.toLowerCase()]) {
createSuggestWindow(cache[word.toLowerCase()]);
}
else {
window.setTimeout(function(){
sendRequest(word);
}, 500);
}
}, false);
function sendRequest(word){
var xhr;
var query = "i=" + encodeURI(word) + "&l=" + RESPONSE_LENGTH;
try {
xhr = new XmlHttp(word);
} catch(e) {
xhr = new Xdr(word, query); // IE9用
}
xhr.send(query);
}
function removeSuggestWindow(){
var suggestbox = document.getElementById("nicosearch-suggestbox");
walker = null;
if (suggestbox) {
suggestbox.parentElement.removeChild(suggestbox);
}
}
function createSuggestWindow(xml){
if (xml.documentElement.getAttribute("status") !== "ok") return false;
walker = null;
var wrapper = document.getElementById("nicosearch-suggestbox");
if (!wrapper) {
wrapper = (function createWrapper(){
var wrapper = document.createElement("div");
var boxRect = searchBox.getBoundingClientRect();
wrapper.id = "nicosearch-suggestbox";
wrapper.style.top = boxRect.top + boxRect.height + "px";
wrapper.style.left = boxRect.left + "px";
wrapper.style.minWidth = searchBox.offsetWidth + "px";
return wrapper;
})();
}
else {
while (wrapper.hasChildNodes()) {
wrapper.removeChild(wrapper.firstChild);
}
}
var img = (function createImage(){
var img = document.createElement("img");
img.className = "nicosearch-dic-icon";
img.src = "data:image/png;base64,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";
return img;
})();
Array.prototype.forEach.call(xml.getElementsByTagName("item"), function(val){
var div = document.createElement("div");
var a = (function createAnchor(){
var a = document.createElement("a");
a.href = "http://dic.nicovideo.jp/a/" + encodeURIComponent(val.textContent);
a.target = "_blank";
return a;
})();
var p = (function createParagraph(){
var p = document.createElement("p");
p.className = "nicosearch-name";
return p;
})();
p.addEventListener("click", function(evt){
searchBox.value = val.textContent;
removeSuggestWindow();
}, false);
a.appendChild(img.cloneNode(false));
div.appendChild(a);
p.appendChild(document.createTextNode(val.textContent));
div.appendChild(p);
wrapper.appendChild(div);
});
document.body.appendChild(wrapper);
}
})();
- Permalink
- このページへの個別リンクです。
- RAW
- 書かれたコードへの直接のリンクです。
- Packed
- 文字列が圧縮された書かれたコードへのリンクです。
- Userscript
- Greasemonkey 等で利用する場合の .user.js へのリンクです。
- Loader
- @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
- Metadata
- コード中にコメントで @xxx と書かれたメタデータの JSON です。