Last active
September 24, 2017 10:30
-
-
Save udaken/4fd7c290ef1044566ed67a9e57a338c7 to your computer and use it in GitHub Desktop.
イラストサムネイルのリンク先を表示する小窓を表示します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name pixixxx - pixiv mini browser | |
// @namespace http://d.hatena.ne.jp/deraw/pixixxx | |
// @include https://www.pixiv.net/* | |
// @grant console.log | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @description イラストサムネイルのリンク先を表示する小窓を表示します。 | |
// @require http://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js | |
// @require http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js | |
// @downloadURL https://gist.githubusercontent.com/udaken/4fd7c290ef1044566ed67a9e57a338c7/raw/pixixxx.user.js | |
// ==/UserScript== | |
(function ($) { | |
'use strict'; | |
var $x; | |
if (typeof $x == 'undefined') { | |
// https://gist.github.com/3238/984c4cac9d700230448235ce512413bd6e4bc202 | |
// extend version of $x | |
// $x(exp); | |
// $x(exp, context); | |
// $x(exp, type); | |
// $x(exp, context, type); | |
$x = function (exp, context, type /* want type */ | |
) { | |
if (typeof context == 'function') { | |
type = context; | |
context = null; | |
} | |
if (!context) context = document; | |
exp = (context.ownerDocument || context).createExpression(exp, function (prefix) { | |
var o = document.createNSResolver(context) (prefix); | |
if (o) return o; | |
return (document.contentType == 'application/xhtml+xml') ? 'http://www.w3.org/1999/xhtml' : ''; | |
}); | |
switch (type) { | |
case String: | |
return exp.evaluate(context, XPathResult.STRING_TYPE, null).stringValue; | |
case Number: | |
return exp.evaluate(context, XPathResult.NUMBER_TYPE, null).numberValue; | |
case Boolean: | |
return exp.evaluate(context, XPathResult.BOOLEAN_TYPE, null).booleanValue; | |
case Array: | |
var result = exp.evaluate(context, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |
for (var ret = [ | |
], i = 0, len = result.snapshotLength; i < len; i++) { | |
ret.push(result.snapshotItem(i)); | |
} | |
return ret; | |
case undefined: | |
var result = exp.evaluate(context, XPathResult.ANY_TYPE, null); | |
switch (result.resultType) { | |
case XPathResult.STRING_TYPE: | |
return result.stringValue; | |
case XPathResult.NUMBER_TYPE: | |
return result.numberValue; | |
case XPathResult.BOOLEAN_TYPE: | |
return result.booleanValue; | |
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: | |
// not ensure the order. | |
var ret = [ | |
], | |
i = null; | |
while ((i = result.iterateNext())) ret.push(i); | |
return ret; | |
} | |
return null; | |
default: | |
throw (TypeError('$x: specified type is not valid type.')); | |
} | |
} | |
} | |
var OPACITY_MAGIC = 0.9999; | |
//class | |
function IllustFrame() { | |
var self = this; | |
this.current = ''; | |
this.frame = document.createElement('div'); | |
this.frame.id = 'pixixxx_frame'; | |
var dndframe = $(this.frame).css('top', '5px').css('left', '5px').css('position', 'fixed').css('border', '0 solid black').css('display', 'none').css('width', (970 + 20) + 'px').css('z-index', 100).css('background-color', 'rgba(255,255,255,0.7)') //80% 不透明の白色。子要素には適用されない | |
.css('box-shadow', '1px 1px 5px #000000').css('border-radius', '4px 4px 4px 4px').appendTo(document.body); | |
var wx; | |
var wy; // ウインドウの左上座標 | |
dndframe.draggable({ | |
handle: '#pixixxx_toolbar', | |
distance: 10, | |
start: function (evt, ui) { | |
dndframe.css('box-shadow', '1px 1px 5px #FF0000') | |
}, | |
stop: function (evt, ui) { | |
dndframe.css('box-shadow', '1px 1px 5px #000000') | |
} | |
} | |
); | |
this.hide = function () { | |
dndframe.animate({ | |
width: 'hide', | |
height: 'hide', | |
opacity: 'hide' | |
}, 500); | |
$('#wrapper').css('margin-right', ''); | |
$(document).unbind('keyup.pixixxx_esc'); | |
$(document).unbind('keyup.pixixxx_shortcut'); | |
} | |
this.show = function () { | |
$('#wrapper').css('margin-right', '1px'); | |
if (this.current == '') { | |
this.moveFirst(); | |
} | |
dndframe.show().css('height', ($(window).height() - 10) + 'px'); | |
wx = parseInt(dndframe.css('left')); | |
wy = parseInt(dndframe.css('top')); | |
console.log('wx:' + wx + ' wy:' + wy); | |
var self = this; | |
$(document).bind('keyup.pixixxx_esc', function (e) { | |
if (e.keyCode === 27) { //ESC | |
self.hide(); | |
return false; | |
} | |
}); | |
$(document).bind('keyup.pixixxx_shortcut', function (e) { | |
if (e.keyCode === 74) { //'J' | |
self.moveNextUnvisited(null); | |
return false; | |
} else if (e.keyCode === 75) { //'K' | |
self.movePrev(null); | |
return false; | |
} else if (e.keyCode === 79) { //'O' | |
self.showOriginal(); | |
return false; | |
} | |
}); | |
} | |
var toolbox = $('<div id="pixixxx_toolbar" />').css('width', '100%').css('height', '30px').css('margin', '0 auto') //center | |
.css('cursor', 'move').appendTo($(this.frame)); | |
var closeButton = $('<button />').text(' x ').addClass('submit_btn').css('padding-top', '5px').css('padding-bottom', '5px').css('position', 'absolute').css('right', '0px').css('top', '0px').css('text-align', 'center').css('width', '30px').bind('click', { | |
frame: this.frame, | |
_this: this | |
}, function (event) { | |
self.hide(); | |
return false; | |
}).appendTo(toolbox).get(0); | |
var refreshButton = $('<button />').text('○').addClass('submit_btn').css('position', 'absolute').css('left', '200px').css('top', '0px').bind('click', function (event) { | |
var histotryJson = GM_getValue('historyJson'); | |
var obj = histotryJson ? JSON.parse(histotryJson) : { | |
}; | |
var illustIdHistoryJson = GM_getValue('illustId-historyJson'); | |
var obj2 = illustIdHistoryJson ? JSON.parse(illustIdHistoryJson) : { | |
}; | |
for (var key in obj) { | |
var url = key; | |
var re = /illust_id=(\d+)/.exec(url); | |
if (re && re[1]) { | |
var illust_id = re[1]; | |
obj2[illust_id] = obj[key]; | |
//console.log(illust_id); | |
} | |
} | |
GM_setValue('illustId-historyJson', JSON.stringify(obj2) + '\n'); | |
return false; | |
}).appendTo(toolbox); | |
var loader = $('<div id="loader"/>').css('background', 'white').css('top', '32px').css('position', 'absolute').css('bottom', '20px').css('overflow', 'auto').css('margin-left', '0px').css('margin-right', '0px').css('margin-top', '1px').css('margin-bottom', '20px').css('width', '100%').appendTo(dndframe); | |
var timeoutId; | |
var toast = $('<div>aaaaa</div>').css('background', 'gray').css('color', 'white').css('border-radius', '5px 5px 5px 5px').css('z-index', 256) //.css('position', 'fixed') | |
.dialog({ | |
closeText: '', | |
autoOpen: false, | |
modal: true, | |
show: 'fadeIn', | |
height: '100', | |
open: function (event, ui) { | |
timeoutId = setTimeout(function () { | |
toast.dialog('close'); | |
}, 3000); | |
}, | |
close: function (event, ui) { | |
//console.log(timeoutId); | |
if (timeoutId) { | |
clearTimeout(timeoutId); | |
timeoutId = null; | |
} | |
}, | |
buttons: [ | |
] | |
}); | |
//.appendTo(document.body); | |
var skipTag = [ | |
'R-18G', | |
'ホモ', | |
'デブ', | |
'ゲイ', | |
'ケモホモ', | |
'オスケモ', | |
'ケモノ', | |
'デブショタ', | |
'タモリはタル', | |
'デブケモノ', | |
'デブケモ', | |
'進撃の腐人', | |
'龍キャス' | |
]; | |
var skipUserId = [ | |
'5046832', | |
'2036519', | |
'3994386', | |
'557114', | |
'44698', | |
'31899', | |
'11028', | |
'7007693', | |
'605816', | |
'944221', | |
'2719449', | |
'536151', | |
'7534083', | |
'4330987', | |
'3106394', | |
'1138638' | |
]; | |
this.moveTo = function (elemLink, button, skipFunc) { | |
var url = elemLink.href; | |
loader.empty(); | |
loader.load(url + ' #wrapper', function (responseText, textStatus, XMLHttpRequest) { | |
console.log('loader:' + textStatus); | |
if (textStatus == 'error') { | |
var msg = 'Sorry but there was an error: '; | |
loader.html(msg + XMLHttpRequest.status + ' ' + XMLHttpRequest.statusText); | |
alert(msg + XMLHttpRequest.status + ' ' + XMLHttpRequest.statusText); | |
} else { | |
pushHistory(url); | |
loader.find('a').attr('target', '_blank'); | |
loader.find('div.worksShare').css('display', 'none'); | |
loader.find('footer.footer').css('display', 'none'); | |
var title = loader.find('h1.title'); | |
var userId = loader.find('a.user-link').attr('href').match(/member\.php\?id=(\d+)/) [1]; | |
debugger; | |
if (skipFunc) { | |
var tags = loader.find('.tags-container .tag a.text'); | |
$.each(tags, function (key, val) { | |
if ($.inArray(val.text, skipTag) > - 1 || $.inArray(userId, skipUserId) > - 1) { | |
console.log('skip![' + title.text() + ']'); | |
//if (toast.dialog('isOpen')) { | |
// toast.dialog('close'); | |
//} | |
//toast.text('skip: ' + title.text() + '\n ' + tags.text()).dialog('open'); | |
skipFunc.call(self, null); | |
//loader.delay(2000).queue(function(){ skipFunc.call(self,null); }); | |
return false; | |
} | |
return true; | |
}); | |
} | |
} | |
loader.css('cursor', 'auto'); | |
}); | |
loader.css('cursor', 'wait'); | |
this.current = getRelatedPath(url); | |
console.log('current:' + this.current); | |
this.jqOpenAsNewWindow.attr('href', url); | |
//サムネに合わせてスクロールする。 | |
var pos = $(elemLink).offset().top; | |
$('html, body').animate({ | |
scrollTop: pos | |
}, 300, 'swing'); | |
} | |
this.showOriginal = function () { | |
var a = loader.find('.works_display a') [0]; | |
console.log(a); | |
a.click(); | |
} | |
this.moveFirst = function (btn) { | |
//var thumbs = $x('//a[starts-with(@href,"/member_illust.php?mode") and p or img]'); | |
var thumbs = $('a[href*="member_illust.php?mode="]'); | |
console.log('thumbs.length: ' + thumbs.length); | |
if (thumbs.length > 0) { | |
this.moveTo(thumbs[0], btn, this.moveNext); | |
} | |
} | |
this.moveLast = function (btn) { | |
var thumbs = $x('//a[starts-with(@href,"/member_illust.php?mode") and p or img]'); | |
console.log('thumbs.length: ' + thumbs.length); | |
if (thumbs.length > 0) { | |
this.moveTo(thumbs[thumbs.length - 1], btn, this.moveFirst); | |
} | |
} | |
this.moveNextUnvisited = function (btn) { | |
var current = this.current; | |
//現在表示しているサムネリンクの最後(autopagerize対応)以降のサムネリンク | |
//var q = '(//a[@href = "/' + current + '" and (p or img)])[last()]/following::a[contains(@href,"/member_illust.php?mode") and (p or img)]'; | |
var q = '(id("wrapper")//a[contains(@href,"' + current + '") and (div or img)])[last()]/../following::*//a[contains(@href,"member_illust.php?mode") and (div or img)]'; | |
console.log(q); | |
var thumbs = $x(q); | |
var historyObj = JSON.parse(GM_getValue('illustId-historyJson') || '{}'); | |
for (var i = 0; i < thumbs.length; i++) { | |
var next = thumbs[i]; | |
if (isVisited(next, historyObj)) { | |
continue; | |
} else { | |
this.moveTo(next, btn, this.moveNextUnvisited); | |
return; | |
} | |
} | |
console.log('次の要素がありません。(current=' + current + ')'); | |
alert('未見のイラストはありません'); | |
} | |
this.moveNext = function (btn) { | |
var current = this.current; | |
//現在表示しているサムネリンクの最後(autopagerize対応)以降のサムネリンク | |
//var q = '(//a[@href = "/' + current + '" and (p or img)])[last()]/following::a[contains(@href,"/member_illust.php?mode") and (p or img)]'; | |
var q = '(id("wrapper")//a[contains(@href,"' + current + '") and (div or img)])[last()]/../following::*//a[contains(@href,"member_illust.php?mode") and (div or img)]'; | |
console.log(q); | |
var thumbs = $x(q); | |
if (thumbs.length > 0) { | |
var next = thumbs[0]; | |
this.moveTo(next, btn, this.moveNext); | |
} else { | |
console.log('次の要素がありません。(current=' + current + ')'); | |
} | |
} | |
this.movePrevUnvisited = function (btn) { | |
var current = this.current; | |
//現在表示しているサムネリンクの最初(autopagerize対応)以前のサムネリンク | |
//var q = '(//a[@href = "/' + current + '" and (p or img)])[1]/preceding::a[contains(@href,"/member_illust.php?mode") and (p or img)]'; | |
var q = '(id("wrapper")//a[contains(@href,"' + current + '") and (div or img)])[1]/../preceding::*//a[contains(@href,"member_illust.php?mode") and (div or img)]'; | |
var thumbs = $x(q); | |
var historyObj = JSON.parse(GM_getValue('illustId-historyJson') || '{}'); | |
for (var i = thumbs.length - 1; i >= 0; i--) { | |
var next = thumbs[i]; | |
if (isVisited(next, historyObj)) { | |
continue; | |
} else { | |
self.moveTo(next, btn, this.movePrevUnvisited); | |
return; | |
} | |
} | |
console.log('前の要素がありません。(current=' + current + ')'); | |
alert('未見のイラストはありません'); | |
} | |
this.movePrev = function (btn) { | |
var current = self.current; | |
//現在表示しているサムネリンクの最初(autopagerize対応)以前のサムネリンク | |
//var q = '(//a[@href = "/' + current + '" and (p or img)])[1]/preceding::a[contains(@href,"/member_illust.php?mode") and (p or img)]'; | |
var q = '(id("wrapper")//a[contains(@href,"' + current + '") and (div or img)])[1]/../preceding::*//a[contains(@href,"member_illust.php?mode") and (div or img)]'; | |
console.log(q); | |
var thumbs = $x(q); | |
if (thumbs.length > 0) { | |
var next = thumbs[thumbs.length - 1]; | |
self.moveTo(next, btn, this.movePrev); | |
} else { | |
console.log('前の要素がありません。(current=' + current + ')'); | |
} | |
} | |
this.jqOpenAsNewWindow = $('<a />').text('新しいウィンドウで開く').attr('target', '_blank').css('text-align', 'center').css('width', '100%').css('margin', '2px') //.css('top','1px') | |
//.css('left','1px') | |
//.css('position','absolute') | |
.prependTo(toolbox); | |
var nextButton = $('<button />').text('>').css('right', '0px').css('top', '48%').click(function () { | |
self.moveNext(nextButton); | |
return false; | |
}).appendTo(self.frame).get(0); | |
var nextUnvisitedButton = $('<button />').text('->').css('right', '0px').css('top', '52%').click(function () { | |
self.moveNextUnvisited(nextUnvisitedButton); | |
return false; | |
}).appendTo(self.frame).get(0); | |
var prevButton = $('<button />').text('<').css('left', '0px').css('top', '48%').click(function () { | |
self.movePrev(prevButton); | |
return false; | |
}).appendTo(self.frame).get(0); | |
var prevUnvisitedButton = $('<button />').text('<-').css('left', '0px').css('top', '52%').click(function () { | |
self.movePrevUnvisited(prevUnvisitedButton); | |
return false; | |
}).appendTo(self.frame).get(0); | |
var firstButton = $('<button />').text('|<').css('left', '0px').css('bottom', '0px').click(function () { | |
self.moveFirst(firstButton); | |
return false; | |
}).appendTo(self.frame).get(0); | |
var lastButton = $('<button />').text('>|').css('right', '0px').css('bottom', '0px').click(function () { | |
self.moveLast(lastButton); | |
return false; | |
}).appendTo(this.frame).get(0); | |
this.moveButtons = $([nextButton, | |
nextUnvisitedButton, | |
prevButton, | |
prevUnvisitedButton, | |
firstButton, | |
lastButton]).addClass('submit_btn').css('padding-top', '5px').css('padding-bottom', '5px').css('position', 'absolute').css('text-align', 'center').css('width', '30px').click(function (e) { | |
var button = this; | |
button.disabled = true; | |
$(button).delay(300).queue(function () { | |
button.disabled = false; | |
$(button).dequeue(); | |
}); | |
}).get(); | |
} | |
function clearHistory(historyObj) { | |
var newHistoryObj = { | |
}; | |
var now = new Date().getTime(); | |
GM_setValue('illustId-historyJson.' + now, JSON.stringify(historyObj)); | |
console.log('clearHistory length of history(before):' + Object.keys(historyObj).length); | |
jQuery.each(historyObj, function (url, dateString) { | |
var days30 = 90 * 24 * 60 * 60 * 1000; | |
var now = new Date().getTime(); | |
if (now - new Date(dateString).getTime() < days30) { | |
newHistoryObj[url] = dateString; | |
} | |
}); | |
console.log('clearHistory length of history:' + Object.keys(historyObj).length); | |
return newHistoryObj; | |
} | |
function pushHistory(url) { | |
var historyObj = JSON.parse(GM_getValue('illustId-historyJson') || '{}'); | |
console.log('pushHistory: ' + url); | |
var re = /illust_id=(\d+)/.exec(url); | |
if (Object.keys(historyObj).length > 20000) | |
historyObj = clearHistory(historyObj); | |
if (re && re[1]) { | |
var illust_id = re[1]; | |
historyObj[illust_id] = new Date(); | |
try { | |
GM_setValue('illustId-historyJson', JSON.stringify(historyObj)); | |
} | |
catch (e) | |
{ | |
console.error('pushHistory error.url:' + url + ' length:' + Object.keys(historyObj).length); | |
alert('履歴が多すぎて、保存できません'); | |
throw e; | |
} | |
} else { | |
console.error('pushHistory error:' + url); | |
} | |
} | |
function isVisited(elemLink, historyObj) { | |
var url = elemLink.href; | |
var re = /illust_id=(\d+)/.exec(url); | |
if (re && re[1]) { | |
var illust_id = re[1]; | |
return historyObj[illust_id]; | |
} | |
return false; | |
} | |
function getRelatedPath(url) { | |
return url.substring(document.documentURI.lastIndexOf('/') + 1); | |
} //小窓内(iframe)では実行しない | |
if (window == window.parent && !(document.location.toString().indexOf('mode=manga&illust_id=') >= 0)) { | |
var button = document.createElement('button'); | |
button.textContent = '小窓を表示'; | |
button.style.fontWeigh = 'bold'; | |
button.style.position = 'fixed'; | |
button.style.left = '10px'; | |
button.style.bottom = '30px'; | |
button.className = 'submit_btn'; | |
document.body.appendChild(button); | |
var illustFrame = new IllustFrame(); | |
button.addEventListener('click', function () { | |
illustFrame.show(); | |
}, false); | |
if (document.location.toString().indexOf('illust_id=') >= 0) { | |
pushHistory(document.location.toString()); | |
} | |
} | |
}) (jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment