// ==UserScript==
// @name Nico Search History
// @namespace http://efcl.info/
// @description ニコニコ動画の検索履歴を保存する
// @include http://www.nicovideo.jp/tag/*
// @include http://www.nicovideo.jp/search/*
// @version 0.0.1.20140518104251
// ==/UserScript==
function Sandbox() {
// 引数は配列argsに変換
var args = Array.prototype.slice.call(arguments),
// 配列の最後はコールバック関数なので取り出す
callback = args.pop(),
// Sandbox(["A","B"],callback) or Sandbox("A","B",callback) どちらでも可能なように
modules = (args[0] && typeof args[0] === "string") ? args : args[0];
// newを付けないで呼ばれたときにコンストラクタとして呼び直す
if (!(this instanceof Sandbox)) {
return new Sandbox(modules, callback);
}
// now add modules to the core `this` object
// moduleがない=パラメータレス or *を指定したときは全てのモジュールを使う
if (!modules || modules === '*') {
modules = [];
for (i in Sandbox.modules) {
if (Sandbox.modules.hasOwnProperty(i)) {
modules.push(i);
}
}
}
// 必要なモジュールの初期化
for (var i = 0,len = modules.length; i < len; i++) {
Sandbox.modules[modules[i]](this);
}
// Sandboxのコールバック(処理本体)を呼ぶ
callback(this);
}
Sandbox.modules = {};
// すべてのprototypeプロパティとして必要部分
// モジュール内からならbox.constructor.prototypeって書いたやつと同じ
Sandbox.prototype = {
name: "Nico Search History",
version: "1.0",
getName: function () {
return this.name;
}
};
Sandbox.add = function(name, method) {
Sandbox.modules[name] = method;
return this;// メソッドチェーン用
};
// 最近の履歴をローカル内で作る
Sandbox.add("history", (function() {
function history() {
this.initialize.apply(this, arguments);
}
history.prototype = {
initialize: function(name, limit) {
this.name = name;
this.limit = limit;
this.value = this.load() || [];
},
save :function(v) {
v && GM_setValue(this.name, JSON.stringify(v));
},
load :function() {
var v = GM_getValue(this.name, null);
if (v) {
return JSON.parse(v);
} else {
return false;
}
},
// keyをvalueにpush
record :function(key) {
this.value.push(key);
if (this.value && this.value.length > this.limit) {
this.value.shift();
}
this.save(this.value);
},
// keyに一致するものを削除
remove : function(key) {
var idx = this.value.indexOf(key);
this.value.splice(idx, 1);
this.save(this.value);
},
// idx番目のkeyを移動
moveheadinAry : function (idx, ary) {
return ary.push(ary.splice(idx, 1)[0]);
},
// 同じものが既にあるかどうか
checkIfExist :function(target) {
var that = this;
if (this.value) {
return this.value.some(function(val, idx, ary) {
// 既にあったら先頭に入れ替える
if (val === target) {
// console.log(idx-1, ary, ary[idx]);
that.moveheadinAry(idx, ary)
that.save(ary);
return true;
}
});
}
}
};
return function(box) {
box.history = history;
}
})());
// テンプレートモジュール
Sandbox.add("templete", function(box) {
box.aryToList = function(searchAry, mode) {
return array2list(searchAry, mode);
};
// array to li
function array2list(array, mode) {
if (array.length < 1) return;
var doc = document;
var list = doc.createElement('ul'), li;
list.className = "GM_search_key";
Sandbox("history", function(box) {
delegation(list, "GM_history_register", "click", function(evt) {
var tar = evt.target,
tarKeys = JSON.parse(tar.dataset.GM_history_register),
page = tarKeys.page,
query = tarKeys.query,
searchKey = page + " " + query;// 種類 検索単語の+区切り
tar["textContent" || "innerText"] = (mode === "fav") ? "☆" : "★";
var searchFav = new box.history("fav", 100);
// favの★なら削除、searchの☆ならFavにaddする
if (mode === "fav") {
searchFav.remove(searchKey);
} else {
if (!searchFav.checkIfExist(searchKey)) {
searchFav.record(searchKey);
}
}
});
});
for (var i = array.length - 1; i >= 0; i--) {
var item = array[i];
var itemType = Object.prototype.toString.call(item);
if (itemType === '[object Array]') {
if (!li) li = list.appendChild(doc.createElement('li'));
li.appendChild(arguments.callee(item, ordered, doc));
} else {
li = list.appendChild(doc.createElement('li'));
if (itemType === '[object Number]') item = String(item);
else if (itemType === '[object String]') item = searchKeyTolink(item, mode);
li.appendChild(item);
}
}
return list;
}
function delegation(ele, className, evtType, callback) {
ele.addEventListener(evtType, function(evt) {
if (evt.target.classList.contains(className)) {
evt.preventDefault();
callback(evt);
}
}, true);
}
function searchKeyTolink(searchKey, mode) {
var searchKey = searchKey.split(" "),
page = searchKey[0],
query = searchKey[1];
var linkGroup = document.createDocumentFragment();
var a = document.createElement("a");
a.href = "http://www.nicovideo.jp/" + page + "/" + query;
a["textContent" || "innerText"] = decodeURIComponent(query);
var starBt = document.createElement("a");
starBt["textContent" || "innerText"] = (mode === "fav") ? "★" : "☆";
starBt.className = "GM_history_register";
starBt.dataset.GM_history_register = JSON.stringify({
page : page,
query: query
});
linkGroup.appendChild(starBt);
linkGroup.appendChild(a);
return linkGroup;
}
});
// 検索履歴の保存
Sandbox("history", function(box) {
var searchHistory = new box.history("search", 100),
historyAry = searchHistory.value;
var searchFav = new box.history("fav", 100),
favAry = searchFav.value;
//log(favAry);
var locationPathname = location.pathname.split("/"),
page = locationPathname[1],
query = locationPathname[2].replace(/\s/g, "+") + location.search,
searchKey = page + " " + query;// 種類 検索単語の+区切り
// 検索キーの保存
if (!searchHistory.checkIfExist(searchKey)) {
searchHistory.record(searchKey);
}
Sandbox("templete", function(box) {
var listTagHistory = box.aryToList(historyAry, "search"),
listTagFav = box.aryToList(favAry, "fav");
log(listTagHistory, listTagFav);
var div = document.createElement("div");
div.id = "GM_search_history_window";
listTagFav && div.appendChild(listTagFav);
listTagHistory && div.appendChild(listTagHistory);
var refresh = document.createElement("a");
refresh["textContent" || "innerText"] = "検索履歴";
refresh.id = "GM_search_history_button";
refresh.addEventListener("click", function(evt) {
evt.preventDefault();
if (div.style.display == "block") {
div.style.display = "none";
} else {
div.style.display = "block";
}
}, false);
// スタイルの追加
addCSS();
document.body.appendChild(refresh);
document.body.appendChild(div);
function addCSS() {
GM_addStyle(['',
' #GM_search_history_button {',
' cursor:default;',
' position: absolute;',
' overflow: auto;',
' top: 30px;',
' right: 0;',
' background: #FFFFFF url(/img/bg.png) repeat-x scroll 0 0;',
' -moz-border-radius: 0 0 0 8px;',
' display: block;',
' width: 5em;',
' color: #FFF;',
' text-decoration: none;',
' cursor: pointer;',
' padding: 4px;',
' opacity: 0.8;',
' z-index: 10000;',
' }',
' #GM_search_history_window {',
' display: none;',
' max-height: 300px;',
' width:300px;',
' overflow-y: scroll;',
' overflow-x: hidden;',
' border: 1px dotted;',
' padding: 5px 15px 5px 5px;',
' background-color: #fff;',
' position: absolute;',
' top: 55px;',
' left: 750px;',
' z-index: 1;',
' }',
' ul.GM_search_key{',
' list-style: none outside none;',
' padding: 3px 10px;',
' white-space:nowrap;',
' }',
' ul.GM_search_key > li{',
' margin: 1px 0;',
' line-height:120%;',
' }',
''].join("\n"));
}
})
});
function log(m) {
var w = this.unsafeWindow || window;
w.console && w.console.log.apply(this, arguments);
}