NicoDicBBSViewer

ニコニコ大百科のBBSの拡張

  1. // ==UserScript==
  2. // @name NicoDicBBSViewer
  3. // @description ニコニコ大百科のBBSの拡張
  4. // @namespace http://threeaster.net
  5. // @include http://dic.nicovideo.jp/a/*
  6. // @include http://dic.nicovideo.jp/b/*
  7. // @include http://dic.nicovideo.jp/l/*
  8. // @include http://dic.nicovideo.jp/v/*
  9. // @include http://dic.nicovideo.jp/i/*
  10. // @include http://dic.nicovideo.jp/u/*
  11. // @include https://dic.nicovideo.jp/a/*
  12. // @include https://dic.nicovideo.jp/b/*
  13. // @include https://dic.nicovideo.jp/l/*
  14. // @include https://dic.nicovideo.jp/v/*
  15. // @include https://dic.nicovideo.jp/i/*
  16. // @include https://dic.nicovideo.jp/u/*
  17. // @require https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js
  18. // @grant GM_getValue
  19. // @grant GM_setValue
  20. // @version 1.2.1
  21. // ==/UserScript==
  22. $.noConflict();
  23. var net_threeaster_NicoDicBBSViewer = {};
  24. (function($){
  25. //-----UrlAnalyzer-----
  26. function UrlAnalyzer(){};
  27.  
  28. UrlAnalyzer.prototype.getNowUrl = function(){
  29. return document.URL;
  30. };
  31.  
  32. UrlAnalyzer.prototype.inArticlePage = function(){
  33. return this.getNowUrl().indexOf("//dic.nicovideo.jp/b/") === -1;
  34. };
  35.  
  36. UrlAnalyzer.prototype.getBBSURLs = function(pager){
  37. if(pager.size() === 0){
  38. return [];
  39. }
  40. var urls = pager.find("a").not(".navi").map(function(){return this.href}).get();
  41. var bbsURLs = [];
  42. if(urls.length){
  43. var lastURLParts = urls[urls.length - 1].split("/");
  44. var lastNumber = lastURLParts[lastURLParts.length - 1].replace("-", "");
  45. if(!this.inArticlePage()){
  46. var nowURLParts = this.getNowUrl().split("#")[0].split("/");
  47. var nowNumber = nowURLParts[nowURLParts.length - 1].replace("-", "");
  48. lastNumber = (lastNumber - 0 >= nowNumber - 0) ? lastNumber : nowNumber;
  49. }
  50. lastURLParts.pop();
  51. var basicURL = lastURLParts.join("/") + "/";
  52. for(var i = lastNumber; i > 0; i -= 30){
  53. bbsURLs.unshift(basicURL + i + "-");
  54. }
  55. }else{
  56. var url = this.getNowUrl();
  57. if(url.indexOf("#") !== -1){
  58. url = url.substring(0, url.indexOf("#"));
  59. if(url.indexOf("-") === -1){
  60. url = url + "-";
  61. }
  62. }
  63. bbsURLs.push(url);
  64. }
  65. return bbsURLs;
  66. };
  67.  
  68. UrlAnalyzer.prototype.isPageOf = function(url){
  69. var nowUrl = this.getNowUrl();
  70. type = this.getPageType(url);
  71. nowType = type !== undefined ? this.getPageType(nowUrl): undefined; //idPageOfの仕様のつじつま合わせ
  72. url = this.getPageName(url);
  73. nowUrl = this.getPageName(nowUrl);
  74. return type === nowType && url === nowUrl;
  75. };
  76.  
  77. UrlAnalyzer.prototype.getPageName = function(url){
  78. var type = this.getPageType(url);
  79. if(type !== undefined && url.indexOf(type) !== -1){
  80. url = url.split(type + "/")[1];
  81. }
  82. url = url.split("/")[0];
  83. url = url.split(":")[0];
  84. url = url.split("#")[0];
  85. return url;
  86. }
  87.  
  88. UrlAnalyzer.prototype.getNowPageName = function(){
  89. return this.getPageName(this.getNowUrl());
  90. }
  91.  
  92. UrlAnalyzer.prototype.getPageType = function(url) {
  93. if(url.indexOf('//dic.nicovideo.jp') !== -1){
  94. url = url.replace('//dic.nicovideo.jp', '');
  95. }
  96. var parts = url.split('/');
  97. if(parts[1] === 'b'){
  98. return parts[2];
  99. }else{
  100. return parts[1];
  101. }
  102. };
  103.  
  104. UrlAnalyzer.prototype.getNowPageType = function(){
  105. return this.getPageType(this.getNowUrl());
  106. }
  107.  
  108. UrlAnalyzer.prototype.changeNumber = function(url){
  109. if(this.inArticlePage()){
  110. return url;
  111. }else{
  112. var parts = url.split("/");
  113. var last = parts.pop();
  114. var lastParts = last.split("-");
  115. var lastNum = lastParts.shift();
  116. var lastTail = lastParts.join("-");
  117. var base = parts.join("/");
  118. var nowNum = this.getNowUrl().split("/").pop().split("-")[0];
  119. var newUrl = base + "/" + nowNum + "-" + lastTail;
  120. return newUrl;
  121. }
  122. }
  123.  
  124. //-----ResCollection-----
  125. function ResCollection(ana){
  126. if(ana === undefined){
  127. this.urlAnalyzer = new UrlAnalyzer();
  128. }else{
  129. this.urlAnalyzer = ana;
  130. }
  131.  
  132. }
  133.  
  134. ResCollection.prototype.createResList = function(dl){
  135. dl.find("dt").each(function(){
  136. var self = $(this);
  137. self.attr("data-number", self.find("a").eq(0).attr("name"));
  138. self.attr("data-name", self.find(".st-bbs_name").text());
  139. var id = self.text().split(":");
  140. id = id[id.length - 1].split("[");
  141. id = id[0];
  142. self.attr("data-id", $.trim(id));
  143. });
  144. var resheads = dl.find("dt");
  145. var resbodies = dl.find("dd");
  146. this.resList = new Array(resheads.size());
  147. for(var i = 0; i < resheads.size(); i++){
  148. this.resList[i] = new Res(resheads.eq(i), resbodies.eq(i), this.urlAnalyzer);
  149. }
  150. };
  151.  
  152. ResCollection.prototype.createResListById = function(){
  153. this.resListById = {};
  154. for(var i = 0; i < this.resList.length; i++){
  155. if(!this.resListById[$(this.resList[i].reshead).attr("data-id")]){
  156. this.resListById[$(this.resList[i].reshead).attr("data-id")] = [];
  157. }
  158. this.resListById[$(this.resList[i].reshead).attr("data-id")].push(this.resList[i]);
  159. }
  160. };
  161.  
  162. ResCollection.prototype.createResListByNumber = function(){
  163. this.resListByNumber = [];
  164. for(var i = 0; i < this.resList.length; i++){
  165. var res = this.resList[i]
  166. this.resListByNumber[res.reshead.attr("data-number")] = res;
  167. }
  168. };
  169.  
  170. ResCollection.prototype.makeTooltips = function(){
  171. var cannotMakeTooltip = !GM_getValue("tooltipOnDicPage") && this.urlAnalyzer.inArticlePage();
  172. for(var i = 0; i < this.resList.length; i++){
  173. this.resList[i].makeIDDiv(this.resListById, !cannotMakeTooltip);
  174. this.resList[i].makeNumberDiv(this.resList);
  175. if(cannotMakeTooltip){
  176. continue;
  177. }
  178. if(GM_getValue("showIDTooltip")){
  179. this.resList[i].makeIDTooltip(this.resListById);
  180. }
  181. if(GM_getValue("showResAnchorTooltip")){
  182. this.resList[i].makeNumTooltip(this.resListByNumber);
  183. }
  184. if(GM_getValue("showResNumberTooltip")){
  185. this.resList[i].makeLinkedNumberTooltip();
  186. }
  187. if(GM_getValue("showResHandleTooltip")){
  188. this.resList[i].makeNumberHandleTooltip(this.resListByNumber);
  189. }
  190. }
  191. };
  192.  
  193. ResCollection.prototype.showRes = function(){
  194. var dl = $(".st-bbs-contents dl");
  195. for(var i = 0; i < this.resList.length; i++){
  196. dl.append(this.resList[i].reshead);
  197. dl.append(this.resList[i].resbody);
  198. }
  199. };
  200.  
  201. ResCollection.prototype.revivalAllRes = function(){
  202. for(var i = 0; i < this.resList.length; i++){
  203. if(this.resList[i].reshead.hasClass("deleted")){
  204. this.resList[i].reshead.removeClass("deleted").find(".name").html(this.resList[i].trueReshead.attr("data-name"));//ここで.nameと.tripが一緒になる。.tripを個別に処理する場合は修正すること
  205. this.resList[i].resbody.html("").append(this.resList[i].trueResbody.clone(true).contents()).removeClass("deleted");
  206. }
  207. }
  208. };
  209.  
  210. ResCollection.prototype.setContextMenu = function(){
  211. for(var i = 0; i < this.resList.length; i++){
  212. this.resList[i].reshead.find(".ID, .IDMulti, .IDMany").unbind("click").click(function(e){
  213. $(this).parent(".st-bbs_resInfo").append($("#contextMenu").css({left : e.pageX, top : e.pageY}).show());
  214. e.stopPropagation();
  215. });
  216. }
  217. $("html").unbind("click").click(function(){
  218. $("#contextMenu").hide();
  219. });
  220. };
  221.  
  222. //-----Res-----
  223.  
  224. function Res(reshead, resbody, ana){
  225. this.reshead = reshead;
  226. this.resbody = resbody;
  227. if(ana === undefined){
  228. this.urlAnalyzer = new UrlAnalyzer();
  229. }else{
  230. this.urlAnalyzer = ana;
  231. }
  232. };
  233.  
  234. Res.prototype.backupRes = function(){
  235. this.trueReshead = this.reshead.clone(true);
  236. this.trueResbody = this.resbody.clone(true);
  237. }
  238.  
  239. Res.prototype.makeIDDiv = function(resListById){
  240. var reflectSameId = GM_getValue("classificationID") && (GM_getValue("tooltipOnDicPage") || !this.urlAnalyzer.inArticlePage());
  241. var addOrdinalAndTotal = function(res, sameIDRes){
  242. if(reflectSameId){
  243. return "[" + (sameIDRes.indexOf(res) + 1) + "/" + sameIDRes.length + "]"
  244. }else{
  245. return "";
  246. }
  247. }
  248. var insertFractionIntoDiv = function(html, fraction){
  249. return html.replace('</div>', fraction + '</div>')
  250. }
  251. var sameIDRes = resListById[this.reshead.attr("data-id")];
  252. if(reflectSameId){
  253. var addIDMulti = "IDMulti";
  254. var addIDMany = "IDMany";
  255. }else{
  256. var addIDMulti = "ID";
  257. var addIDMany = "ID";
  258. }
  259. if(this.reshead.find(".ID, .IDMulti, .IDMany").size() === 0){
  260. var s = this.reshead.html().split(":");
  261. if(sameIDRes.length == 1){
  262. s[s.length - 2] = s[s.length - 2].replace("ID", "<div class='ID'>ID</div>");
  263. }else if(sameIDRes.length < 5){
  264. s[s.length - 2] = s[s.length - 2].replace("ID", "<div class='" + addIDMulti + "'>ID</div>");
  265. s[s.length - 1] = insertFractionIntoDiv(s[s.length - 1], addOrdinalAndTotal(this, sameIDRes));
  266. }else{
  267. s[s.length - 2] = s[s.length - 2].replace("ID", "<div class='" + addIDMany + "'>ID</div>");
  268. s[s.length - 1] = insertFractionIntoDiv(s[s.length - 1], addOrdinalAndTotal(this, sameIDRes));
  269. }
  270. this.reshead.html(s.join(":"));
  271. }else if(this.reshead.find(".ID").size() !== 0){
  272. if(sameIDRes.length == 1){
  273. }else if(sameIDRes.length < 5){
  274. this.reshead.find(".ID, .IDMulti, .IDMany").removeClass("ID IDMulti IDMany").addClass(addIDMulti);
  275. var s = this.reshead.html().split(":");
  276. s[s.length - 1] = insertFractionIntoDiv(s[s.length - 1], addOrdinalAndTotal(this, sameIDRes));
  277. this.reshead.html(s.join(":"));
  278. }else{
  279. this.reshead.find(".ID, .IDMulti, .IDMany").removeClass("ID IDMulti IDMany").addClass(addIDMany);
  280. var s = this.reshead.html().split(":");
  281. s[s.length - 1] = insertFractionIntoDiv(s[s.length - 1], addOrdinalAndTotal(this, sameIDRes));
  282. this.reshead.html(s.join(":"));
  283. }
  284. }else{
  285. if(sameIDRes.length < 5){
  286. this.reshead.find(".ID, .IDMulti, .IDMany").removeClass("ID IDMulti IDMany").addClass(addIDMulti);
  287. var s = this.reshead.html().split("[");
  288. s[s.length - 1] = addOrdinalAndTotal(this, sameIDRes);
  289. this.reshead.html(s.join(""));
  290. }else{
  291. this.reshead.find(".ID, .IDMulti, .IDMany").removeClass("ID IDMulti IDMany").addClass(addIDMany);
  292. var s = this.reshead.html().split("[");
  293. s[s.length - 1] = addOrdinalAndTotal(this, sameIDRes);
  294. this.reshead.html(s.join(""));
  295. }
  296. }
  297. }
  298.  
  299. Res.prototype.makeNumberDiv = function(resList){
  300. this.linkedResponds = [];
  301. var myNumber = this.reshead.attr("data-number") - 0;
  302. for(var i = 0; i < resList.length; i++){
  303. var numberAnchorsWrapset = resList[i].resbody.find("a.dic");
  304. var numberAnchors = [];
  305. if(numberAnchorsWrapset.size() !== 0){
  306. numberAnchorsWrapset.each(function(){
  307. numberAnchors.push($(this).html().split("&gt;").join(""));
  308. });
  309. }else{
  310. continue;
  311. }
  312. for(var j = 0; j < numberAnchors.length; j++){
  313. var num = numberAnchors[j];
  314. if(num.indexOf("-") === -1 && myNumber === num - 0){
  315. this.linkedResponds.push(resList[i]);
  316. break;
  317. }else if(num.indexOf("-") !== -1){
  318. num = num.split("-");
  319. if(num[0] <= myNumber && myNumber <= num[1]){
  320. this.linkedResponds.push(resList[i]);
  321. break;
  322. }
  323. }
  324. }
  325. }
  326. this.reshead.find("div.Number, div.NumberMulti, div.NumberMany").contents().unwrap();
  327. if(this.linkedResponds.length === 0){
  328. }else if(!GM_getValue("classificationResNumber") || this.linkedResponds.length === 1){
  329. this.reshead.find('.st-bbs_resNo').html("<div class='Number'>" + this.reshead.find('.st-bbs_resNo').html() + "</div>")
  330. }else if(this.linkedResponds.length <= 3){
  331. this.reshead.find('.st-bbs_resNo').html("<div class='NumberMulti'>" + this.reshead.find('.st-bbs_resNo').html() + "</div>")
  332. }else{
  333. this.reshead.find('.st-bbs_resNo').html("<div class='NumberMany'>" + this.reshead.find('.st-bbs_resNo').html() + "</div>")
  334. }
  335. }
  336.  
  337.  
  338. Res.prototype.makeIDTooltip = function(resListById){
  339. var sameIDRes = resListById[this.reshead.attr("data-id")];
  340. var divID = this.reshead.find("div[class^='ID']");
  341. var that = this;
  342. divID.unbind("mouseenter").unbind("mouseleave").hover(function(){
  343. var tooltip = $("<div></div>").click(function(e){e.stopPropagation();});
  344. for(var i = 0; i < sameIDRes.length; i++){
  345. tooltip.append(sameIDRes[i].reshead.clone().find("a").removeAttr("id").end());
  346. tooltip.append(sameIDRes[i].resbody.clone().find("a").removeAttr("id").end());
  347. }
  348. divID.append(tooltip);
  349. divID.focus();
  350. that.adjustHeightOfTooltip(tooltip);
  351. }, function(){
  352. divID.find("div").remove();
  353. });
  354. };
  355.  
  356. Res.prototype.makeNumTooltip = function(resListByNumber){
  357. var that = this;
  358. this.resbody.find(".numTooltip > a.dic").unwrap();
  359. this.resbody.find("a.dic").filter(function(){return $(this).html().indexOf("&gt;&gt;") !== -1}).each(function(){
  360. var self = $(this);
  361. var num = self.html().split("&gt;").join("").split("-");
  362. for(var i = 0; i < num.length; i++){
  363. num[i] = num[i] - 0;
  364. }
  365. self.wrap("<span class='numTooltip'></span>").parent().unbind("mouseenter").unbind("mouseleave").hover(function(){
  366. var self = $(this);
  367. var tooltip = $("<div></div>");
  368. if(num.length === 1 || !num[1]){
  369. var res = resListByNumber[num[0]];
  370. if(res === undefined){
  371. return;
  372. }
  373. var cloneBody = res.resbody.clone();
  374. cloneBody.find(".numTooltip > a.dic").unwrap();
  375. tooltip.append(res.reshead.clone().find("a").removeAttr("id").end());
  376. tooltip.append(cloneBody.find("a").removeAttr("id").end());
  377. }else{
  378. for(var i = num[0]; i <= num[1]; i++){
  379. var res = resListByNumber[i];
  380. if(res === undefined){
  381. continue;
  382. }
  383. var cloneBody = res.resbody.clone();
  384. cloneBody.find(".numTooltip > a.dic").unwrap();
  385. tooltip.append(res.reshead.clone().find("a").removeAttr("id").end());
  386. tooltip.append(cloneBody.find("a").removeAttr("id").end());
  387. }
  388. if(tooltip.html() === $("<div></div>").html()){
  389. return;
  390. }
  391. }
  392. self.append(tooltip);
  393. self.focus();
  394. that.adjustHeightOfTooltip(tooltip);
  395. }, function(){
  396. $(this).find("div").remove();
  397. });
  398. });
  399. };
  400.  
  401. Res.prototype.makeLinkedNumberTooltip = function(){
  402. var divNumber = this.reshead.find("div[class^='Number']");
  403. var linkedResponds = this.linkedResponds;
  404. var that = this;
  405. divNumber.unbind("mouseenter").unbind("mouseleave").hover(function(){
  406. var tooltip = $("<div></div>").click(function(e){e.stopPropagation();});
  407. for(var i = 0; i < linkedResponds.length; i++){
  408. tooltip.append(linkedResponds[i].reshead.clone().find("a").removeAttr("id").end());
  409. tooltip.append(linkedResponds[i].resbody.clone().find("a").removeAttr("id").end());
  410. }
  411. divNumber.append(tooltip);
  412. divNumber.focus();
  413. that.adjustHeightOfTooltip(tooltip);
  414. }, function(){
  415. divNumber.find("div").remove();
  416. });
  417. }
  418.  
  419. Res.prototype.makeNumberHandleTooltip = function(resListByNumber){
  420. var nameSpan = this.reshead.find(".st-bbs_name");
  421. var name = nameSpan.html();
  422. var transformedName = name.replace(/[0123456789]/g, function(c){return "0123456789".indexOf(c);});
  423. var that = this;
  424. if(/^[0-9]+$/.test(transformedName)){
  425. nameSpan.wrap("<span class='NumberHandle'></span>").parent().unbind("mouseenter").unbind("mouseleave").hover(function(){
  426. var self = $(this);
  427. var tooltip = $("<div></div>");
  428. var res = resListByNumber[transformedName];
  429. if(res === undefined){
  430. return;
  431. }
  432. tooltip.append(res.reshead.clone().find("a").removeAttr("id").end());
  433. tooltip.append(res.resbody.clone().find("a").removeAttr("id").end());
  434. self.append(tooltip);
  435. self.focus();
  436. that.adjustHeightOfTooltip(tooltip);
  437. }, function(){
  438. $(this).find("div").remove();
  439. });
  440. }
  441. }
  442.  
  443. Res.prototype.adjustHeightOfTooltip = function(tooltip){
  444. var a = $("html").scrollTop() + $("#topline").height();
  445. var b = tooltip.offset().top;
  446. var c = $(window).height() - $("#topline").height();
  447. var d = tooltip.height();
  448. if(a < b && b < a + c && a < b + d && b + d < a + c){
  449. }else if(d < c){
  450. if(b > a){
  451. tooltip.offset({top : (a + c - d) });
  452. }else{
  453. tooltip.offset({top : a});
  454. }
  455. }else{
  456. tooltip.offset({top : a});
  457. tooltip.height(c - $("#topline").height());
  458. }
  459. };
  460.  
  461. //-----NgOperator-----
  462. function NgOperator(ana){
  463. this.ngList = {};
  464. this.ngList.ngid = [];
  465. this.ngList.ngname = [];
  466. this.ngList.ngword = [];
  467. this.ngList.ngres = [];
  468. if(ana === undefined){
  469. this.urlAnalyzer = new UrlAnalyzer();
  470. }else{
  471. this.urlAnalyzer = ana;
  472. }
  473. }
  474. NgOperator.prototype.initNg = function(){
  475. this.ngList = {};
  476. this.ngList.ngidText = removeUselessLines(GM_getValue("ngid"));
  477. if(this.ngList.ngidText){
  478. this.ngList.ngid = this.ngList.ngidText.split("\n");
  479. for(var i = 0; i < this.ngList.ngid.length; i++){
  480. this.ngList.ngid[i] = $.trim(this.ngList.ngid[i]);
  481. }
  482. }else{
  483. this.ngList.ngid = [];
  484. }
  485. this.ngList.ngnameText = removeUselessLines(GM_getValue("ngname"));
  486. if(this.ngList.ngnameText){
  487. this.ngList.ngname = this.ngList.ngnameText.split("\n");
  488. for(var i = 0; i < this.ngList.ngname.length; i++){
  489. this.ngList.ngname[i] = $.trim(this.ngList.ngname[i]);
  490. }
  491. }else{
  492. this.ngList.ngname = [];
  493. }
  494. this.ngList.ngwordText = removeUselessLines(GM_getValue("ngword"));
  495. if(this.ngList.ngwordText){
  496. this.ngList.ngword = this.ngList.ngwordText.split("\n");
  497. for(var i = 0; i < this.ngList.ngword.length; i++){
  498. this.ngList.ngword[i] = $.trim(this.ngList.ngword[i]);
  499. }
  500. }else{
  501. this.ngList.ngword = [];
  502. }
  503. this.ngList.ngresText = removeUselessLines(GM_getValue("ngres"));
  504. if(this.ngList.ngresText){
  505. this.ngList.ngres = this.ngList.ngresText.split("\n");
  506. for(var i = 0; i < this.ngList.ngres.length; i++){
  507. this.ngList.ngres[i] = $.trim(this.ngList.ngres[i]);
  508. }
  509. }else{
  510. this.ngList.ngres = [];
  511. }
  512. };
  513.  
  514. NgOperator.prototype.applyNg = function(resList){
  515. for(var i = 0; i < resList.length; i++){
  516. var r = resList[i];
  517. var applied = false;
  518. if(GM_getValue("useNG")){
  519. var id = r.trueReshead.attr("data-id");
  520. var name = r.trueReshead.attr("data-name");
  521. for(var j = 0; !applied && j < this.ngList.ngid.length; j++){
  522. if(this.ngList.ngid[j] === id){
  523. applied = true;
  524. }
  525. }
  526. for(var j = 0; !applied && j < this.ngList.ngname.length; j++){
  527. if(name.indexOf(this.ngList.ngname[j]) !== -1){
  528. applied = true;
  529. }
  530. }
  531. for(var j = 0; !applied && j < this.ngList.ngword.length; j++){
  532. if(r.trueResbody.text().indexOf(this.ngList.ngword[j]) !== -1){
  533. applied = true;
  534. }
  535. }
  536. for(var j = 0; !applied && j < this.ngList.ngres.length; j++){
  537. var ngres = this.ngList.ngres[j].split(":");
  538. var number = ngres.pop();
  539. var URL = ngres.join(":");
  540. if(this.urlAnalyzer.isPageOf(URL) && r.reshead.attr("data-number") == number){
  541. applied = true;
  542. }
  543. }
  544. }
  545.  
  546. if(applied){
  547. $("#contextMenu").insertAfter("#ng");
  548. r.reshead.find(".name").html("削除しました");
  549. r.reshead.find(".trip").remove();
  550. r.reshead.addClass("deleted");
  551. r.resbody.html("削除しました").addClass("deleted");
  552. }else if(r.reshead.hasClass("deleted")){
  553. r.reshead.removeClass("deleted").find(".name").html(r.trueReshead.attr("data-name"));//ここで.nameと.tripが一緒になる。.tripを個別に処理する場合は修正すること
  554. r.resbody.html("").append(r.trueResbody.clone(true).contents()).removeClass("deleted");
  555. }
  556.  
  557. var css = $("#nicoDicBBSViewerCSS");
  558. if(GM_getValue("seethroughNG")){
  559. if(css.html().indexOf("deleted") === -1){
  560. css.html(css.html() + ".deleted{display:none;}");
  561. }
  562. }else{
  563. if(css.html().indexOf("deleted") !== -1){
  564. css.html(css.html().replace(".deleted{display:none;}", ""));
  565. }
  566. }
  567. }
  568. };
  569. //-----MenuOperator-----
  570.  
  571. function MenuOperator(resCollection, ngOperator){
  572. this.resCollection = resCollection;
  573. this.ngOperator = ngOperator;
  574. this.urlAnalyzer = new UrlAnalyzer();
  575. this.bbsScroll = 0;
  576. };
  577.  
  578.  
  579. MenuOperator.prototype.bindContextMenu = function(){
  580. var self = this;
  581. $("#ngidMenu").click(function(){
  582. $("#contextMenu").hide();
  583. if($(this).parents(".st-bbs_reshead").hasClass("deleted")){
  584. return false;
  585. }
  586. var id = $(this).parents(".st-bbs_reshead").attr("data-id");
  587. var gm_ngid = GM_getValue("ngid") ? GM_getValue("ngid") : "";
  588. var ngidText = gm_ngid + "\n" + id;
  589. ngidText = removeUselessLines(ngidText);
  590. $("#ngidTextarea").val(ngidText);
  591. GM_setValue("ngid", ngidText);
  592. self.ngOperator.initNg();
  593. self.ngOperator.applyNg(self.resCollection.resList);
  594. });
  595.  
  596. $("#ngnameMenu").click(function(){
  597. $("#contextMenu").hide();
  598. if($(this).parents(".st-bbs_reshead").hasClass("deleted")){
  599. return false;
  600. }
  601. var name = $(this).parents(".st-bbs_reshead").attr("data-name");
  602. var gm_ngname = GM_getValue("ngname") ? GM_getValue("ngname") : "";
  603. var ngnameText = gm_ngname + "\n" + name;
  604. ngnameText = removeUselessLines(ngnameText);
  605. $("#ngnameTextarea").val(ngnameText);
  606. GM_setValue("ngname", ngnameText);
  607. self.ngOperator.initNg();
  608. self.ngOperator.applyNg(self.resCollection.resList);
  609. });
  610.  
  611. $("#ngresMenu").click(function(){
  612. $("#contextMenu").hide();
  613. if($(this).parents(".st-bbs_reshead").hasClass("deleted")){
  614. return false;
  615. }
  616. var number = $(this).parents(".st-bbs_reshead").attr("data-number");
  617. var gm_ngresText = GM_getValue("ngres") ? GM_getValue("ngres") : "";
  618. var pageName = self.urlAnalyzer.getNowPageName();
  619. var ngresText = gm_ngresText + "\n" + pageName + ":" + number;
  620. ngresText = removeUselessLines(ngresText);
  621. $("#ngresTextarea").val(ngresText);
  622. GM_setValue("ngres", ngresText);
  623. self.ngOperator.initNg();
  624. self.ngOperator.applyNg(self.resCollection.resList);
  625. });
  626. };
  627.  
  628.  
  629. MenuOperator.prototype.insertConfigHtml = function(){
  630. var self = this;
  631. var appendNgTextarea = function(labelcore, idcore){
  632. var text = "";
  633. text = text + '<div style="float:left; width:24%"><p>改行で区切って' + labelcore + 'を入力or削除してください。</p>';
  634. text = text + '<textarea id="' + idcore + 'Textarea" cols="20" rows="10" placeholder="' + labelcore + 'を改行で区切って入力してください。">';
  635. text = text + (GM_getValue(idcore) ? GM_getValue(idcore) : "");
  636. text = text + '</textarea></div>';
  637. $("#ng").append(text);
  638. }
  639. var appendConfigLi = function(parent, id, label){
  640. var text = "";
  641. text = text + '<li><input id="' + id + 'Checkbox" type="checkbox" ' + (GM_getValue(id) ? "checked = 'checked'" : "") + '/>' + label + '</li>';
  642. parent.append(text);
  643. }
  644. var appendSubList = function(parent, list, label){
  645. var li = $("<li>" + label + "</li>");
  646. li.append(list);
  647. parent.append(li);
  648. }
  649. var getSubUl = function(){
  650. return $('<ul style="list-style-type: none; margin-left:5px;"></ul>');
  651. }
  652. $("#topbarLogoutMenu").after('<li>NicoDicBBSViewer</li><li id="bbsLi" class="selected"><a href="#">掲示板を表示する</a></li><li id="ngLi"><a href="#">設定画面を表示する</a></li>');
  653. $(".st-bbs-contents").after('<div id="ng"></div>');
  654. appendNgTextarea("NGID", "ngid");
  655. appendNgTextarea("NGName", "ngname");
  656. appendNgTextarea("NGワード", "ngword");
  657. appendNgTextarea("NGレスを(BBSのURL:レス番号)の書式で", "ngres");
  658.  
  659. $("#ng").append('<div style="clear:left;"><form><ul style="list-style-type: none;"></ul></form><div>');
  660. var parentUl = $("#ng form ul");
  661. //appendConfigLi(parentUl, "addToOnePage", '一つのページに継ぎ足す(更新時有効)');
  662. //appendConfigLi(parentUl, "autoLoad", "下までスクロールした時に次のページを読み込む");
  663.  
  664. var ngUl = getSubUl();
  665. appendConfigLi(ngUl, "useNG", "NG機能を使用する");
  666. appendConfigLi(ngUl, "seethroughNG", "NGが適用されたレスを表示しない");
  667. appendSubList(parentUl, ngUl, "NG機能");
  668.  
  669. appendConfigLi(parentUl, "tooltipOnDicPage", "記事ページでもID、番号の色分けやツールチップを表示する");
  670.  
  671. var tooltipUl = getSubUl();
  672. appendConfigLi(tooltipUl, "showIDTooltip", 'ID(<span style="text-decoration:underline;">ID</span>)ツールチップを表示する');
  673. appendConfigLi(tooltipUl, "showResAnchorTooltip", 'レスアンカー(<span style="color: rgb(0, 102, 204);">>>1</span>)ツールチップを表示する');
  674. appendConfigLi(tooltipUl, "showResNumberTooltip", 'レス番(<span style="text-decoration:underline;">1</span>)ツールチップを表示する');
  675. appendConfigLi(tooltipUl, "showResHandleTooltip", 'レス番ハンドル(<span style="color: rgb(0, 136, 0); font-weight: bold;">1</span>)ツールチップを表示する');
  676. appendSubList(parentUl, tooltipUl, "ツールチップ(更新時有効)");
  677.  
  678. var colorUl = getSubUl();
  679. appendConfigLi(colorUl, "classificationID", "IDを色分けし、そのIDのレスの回数を表示する");
  680. appendConfigLi(colorUl, "classificationResNumber", "参照されているレス番を色分けする");
  681. appendSubList(parentUl, colorUl, "色分け(更新時有効)");
  682.  
  683. $("#ng").append('<button id="decideNG">保存</button> <button id="cancelNG">キャンセル</button> <button id="backToBbsButton">掲示板に戻る</button></div>' +
  684. ' <ul id="contextMenu"><li id="ngidMenu">NGIDに追加</li><li id="ngnameMenu">NGNameに追加</li><li id="ngresMenu">このレスを削除</li></ul>');
  685. };
  686.  
  687. MenuOperator.prototype.bindMenu = function(){
  688. var self = this;
  689. var contents = $(".st-bbs-contents, #ng");
  690. var backBBS = function(){
  691. if($(".selected").attr("id") === "bbsLi"){
  692. self.bbsScroll = $("html").scrollTop();
  693. }
  694. $(".selected").removeClass("selected");
  695. $("#bbsLi").addClass("selected");
  696. contents.not(".st-bbs-contents").css("display", "none");
  697. $(".st-bbs-contents").css("display", "block");
  698. $("html").scrollTop(self.bbsScroll);
  699. return false;
  700. };
  701. $("#bbsLi").click(backBBS);
  702. $("#backToBbsButton").click(backBBS);
  703.  
  704. $("#ngLi").click(function(){
  705. if($(".selected").attr("id") === "bbsLi"){
  706. self.bbsScroll = $("html").scrollTop();
  707. }
  708. $(".selected").removeClass("selected");
  709. $(this).addClass("selected");
  710. contents.not("#ng").css("display", "none");
  711. $("#ng").css("display", "block");
  712. $("html").scrollTop($("#ng").offset().top - $("#topline").height());
  713. return false;
  714. });
  715.  
  716. var setcbConfig = function(id){
  717. GM_setValue(id, $("#" + id + "Checkbox").is(":checked"));
  718. }
  719.  
  720. var checkcbConfig = function(id){
  721. if(GM_getValue(id)){
  722. $("#" + id + "Checkbox").attr("checked", true);
  723. }else{
  724. $("#" + id + "Checkbox").attr("checked", false);
  725. }
  726. }
  727.  
  728. $("#decideNG").click(function(){
  729. GM_setValue("ngid", $("#ngidTextarea").val());
  730. GM_setValue("ngname", $("#ngnameTextarea").val());
  731. GM_setValue("ngword", $("#ngwordTextarea").val());
  732. GM_setValue("ngres", $("#ngresTextarea").val());
  733. setcbConfig("seethroughNG");
  734. setcbConfig("loadAll");
  735. setcbConfig("addToOnePage");
  736. setcbConfig("autoLoad");
  737. setcbConfig("useNG");
  738. setcbConfig("tooltipOnDicPage");
  739. setcbConfig("showIDTooltip");
  740. setcbConfig("showResAnchorTooltip");
  741. setcbConfig("showResNumberTooltip");
  742. setcbConfig("showResHandleTooltip");
  743. setcbConfig("classificationID");
  744. setcbConfig("classificationResNumber");
  745. setcbConfig("switcherInTopMenu");
  746. self.ngOperator.initNg();
  747. self.ngOperator.applyNg(self.resCollection.resList);
  748. });
  749.  
  750. $("#cancelNG").click(function(){
  751. $("#ngidTextarea").val(GM_getValue("ngid") ? GM_getValue("ngid") : "");
  752. $("#ngnameTextarea").val(GM_getValue("ngname") ? GM_getValue("ngname") : "");
  753. $("#ngwordTextarea").val(GM_getValue("ngword") ? GM_getValue("ngword") : "");
  754. $("#ngresTextarea").val(GM_getValue("ngres") ? GM_getValue("ngres") : "");
  755. checkcbConfig("seethroughNG");
  756. checkcbConfig("loadAll");
  757. checkcbConfig("addToOnePage");
  758. checkcbConfig("autoLoad");
  759. checkcbConfig("useNG");
  760. checkcbConfig("tooltipOnDicPage");
  761. checkcbConfig("showIDTooltip");
  762. checkcbConfig("showResAnchorTooltip");
  763. checkcbConfig("showResNumberTooltip");
  764. checkcbConfig("showResHandleTooltip");
  765. checkcbConfig("classificationID");
  766. checkcbConfig("classificationResNumber");
  767. checkcbConfig("switcherInTopMenu");
  768. });
  769. };
  770.  
  771. //-----ManegerToReadBbs-----
  772.  
  773. function ManagerToReadBbs(urls, ana){
  774. if(ana === undefined){
  775. ana = new UrlAnalyzer();
  776. }
  777. this.urlAnalyzer = ana;
  778. this.bbsUrls = urls;
  779. var nowUrl = ana.getNowUrl();
  780. if(!ana.inArticlePage()){
  781. if(nowUrl.indexOf("#") === -1){
  782. this.startIndex = urls.indexOf(nowUrl);
  783. }else{
  784. var mainurl = nowUrl.substring(0, nowUrl.indexOf("#"));
  785. if(mainurl.indexOf("-") === -1){
  786. mainurl = mainurl + "-";
  787. }
  788. this.startIndex = urls.indexOf(mainurl);
  789. }
  790. }
  791. this.endIndex = this.startIndex;
  792. this.isNowLoading = false;
  793. this.resCollection = new ResCollection(ana);
  794. this.ngOperator = new NgOperator(ana);
  795. this.menuOperator = new MenuOperator(this.resCollection, this.ngOperator);
  796. };
  797.  
  798. ManagerToReadBbs.prototype.readPreviousBbs = function(){
  799. if(this.isNowLoading || this.startIndex <= 0){
  800. return;
  801. }
  802. $("#bbsmain").prepend("<p id='loading'>now loading...</p>");
  803. this.isNowLoading = true;
  804. this.startIndex--;
  805. var self = this;
  806. $.get(this.bbsUrls[this.startIndex], function(r){
  807. self.prependBbs($(r).find("dl"));
  808. });
  809. if(this.startIndex === 0){
  810. $("#loadPreviousPageLinks").remove();
  811. }
  812. };
  813.  
  814. ManagerToReadBbs.prototype.prependBbs = function(dl){
  815. this.resCollection.revivalAllRes();
  816. $(".st-bbs-contents dl").prepend(dl.contents());
  817. this.createAndSetResList();
  818. $("#loading").remove();
  819. this.isNowLoading = false;
  820. };
  821.  
  822. ManagerToReadBbs.prototype.readNextBbs = function(){
  823. if(this.isNowLoading || this.endIndex >= this.bbsUrls.length - 1){
  824. return;
  825. }
  826. $("#bbsmain").append("<p id='loading'>now loading...</p>");
  827. this.isNowLoading = true;
  828. this.endIndex++;
  829. var self = this;
  830. $.get(this.bbsUrls[this.endIndex], function(r){
  831. self.appendBbs($(r).find("dl"));
  832. });
  833. if(this.endIndex === this.bbsUrls.length - 1){
  834. $("#loadNextPageLinks").remove();
  835. }
  836. };
  837.  
  838. ManagerToReadBbs.prototype.appendBbs = function(dl){
  839. this.resCollection.revivalAllRes();
  840. $(".st-bbs-contents dl").append(dl.contents());
  841. this.createAndSetResList();
  842. $("#loading").remove();
  843. this.isNowLoading = false;
  844. };
  845.  
  846. ManagerToReadBbs.prototype.initSmallBbs = function(){
  847. this.initPager();
  848. this.ngOperator.initNg();
  849. this.createAndSetResList();
  850. this.menuOperator.insertConfigHtml();
  851. this.menuOperator.bindMenu();
  852. this.menuOperator.bindContextMenu();
  853. };
  854.  
  855. ManagerToReadBbs.prototype.initPager = function(){
  856. return
  857. if(!GM_getValue("addToOnePage")){
  858. return;
  859. }
  860. var pager = $(".st-bbs-contents .pager");
  861. var self = this;
  862. if(this.urlAnalyzer.inArticlePage()){
  863. pager.find(".navi").remove();
  864. }else{
  865. pager.eq(0).find("a:not(:first), .current, span").remove();
  866. if(this.startIndex > 0){
  867. pager.eq(0).append("<a id='loadPreviousPageLinks' href='#'>前へ</a>");
  868. pager.find("#loadPreviousPageLinks").click(function(){self.readPreviousBbs(); return false;});
  869. }
  870. pager.eq(1).find("a:not(:first), .current, span").remove();
  871. if(this.endIndex < this.bbsUrls.length - 1){
  872. pager.eq(1).append("<a id='loadNextPageLinks' href='#'>次へ</a>");
  873. pager.find("#loadNextPageLinks").click(function(){self.readNextBbs(); return false;});
  874. }
  875. }
  876. };
  877.  
  878. ManagerToReadBbs.prototype.scrollLoader = function(){
  879. this.reserved = false;
  880. var self = this;
  881. setInterval(function(){
  882. if(self.reserved){
  883. self.reserved = false;
  884. self.readNextBbs();
  885. }
  886. }, 1000);
  887. $(window).scroll(function(){
  888. if($(".selected").attr("id") === "bbsLi" && GM_getValue("autoLoad") && $("html").scrollTop() + $(window).height() > $("#bbsmain").position().top + $("#bbsmain").height()){
  889. self.reserved = true;
  890. }
  891. });
  892. };
  893.  
  894. ManagerToReadBbs.prototype.createAndSetResList = function(){
  895. this.resCollection.createResList($(".st-bbs-contents dl"));
  896. this.resCollection.createResListById();
  897. this.resCollection.createResListByNumber();
  898. this.resCollection.makeTooltips();
  899. this.resCollection.setContextMenu();
  900. var resList = this.resCollection.resList;
  901. for(var i = 0; i < resList.length; i++){
  902. resList[i].backupRes();
  903. }
  904. this.ngOperator.applyNg(resList);
  905. };
  906.  
  907. //-----単体の関数-----
  908.  
  909. var removeUselessLines = function(s){
  910. if(!s){
  911. return;
  912. }
  913. var lines = s.split("\n");
  914. var storage = {};
  915. for(var i = 0; i < lines.length;){
  916. if(!lines[i] || lines[i] in storage){
  917. lines.splice(i, 1);
  918. }else{
  919. storage[lines[i]] = 0;
  920. i++;
  921. }
  922. }
  923. return lines.join("\n");
  924. };
  925.  
  926. var initConfig = function(ids){
  927. for(var i = 0; i < ids.length; i++){
  928. if(GM_getValue(ids[i]) === undefined){
  929. GM_setValue(ids[i], true);
  930. }
  931. }
  932. }
  933.  
  934. var insertStyle = function(){
  935. var idStyle = ".ID{text-decoration:underline; color:black; display:inline;} .IDMulti{text-decoration:underline; color:blue; display:inline;}" +
  936. ".IDMany{text-decoration:underline; color:red; display:inline;}";
  937. var numberStyle = ".Number{text-decoration: underline; display:inline;} .NumberMulti{text-decoration: underline; display:inline; color:blue;}" +
  938. ".NumberMany{text-decoration: underline; display:inline; color:red;}";
  939. var insideTooltipStyle = ".dic{display:inline;}";
  940. var onMouseIdStyle = ".ID:hover, .IDMulti:hover, .IDMany:hover, .dic:hover{text-decoration:none;}";
  941. var defaultTooltipStyle = ".ID>div, .IDMulti>div, .IDMany>div, .dic>div, .Number>div, .NumberMulti>div, .NumberMany>div, .NumberHandle>div{display:none;}";
  942. var onMouseTooltipStyle = ".ID:hover>div, .IDMulti:hover>div, .IDMany:hover>div, .numTooltip:hover>div," +
  943. " .Number:hover>div, .NumberMulti:hover>div, .NumberMany:hover>div, .NumberHandle:hover>div" +
  944. "{color:black; display:inline; position:absolute; background:#f5f5b5; border:solid black 1px; padding;5px; font-size:8pt; overflow:auto;" +
  945. " box-shadow:1px 1px; z-index:10000;font-weight:normal;}";
  946. var leftboxStyle = "div.left-box{border: groove 1px gray; border-radius: 5px; background-image:none;}";
  947. var ngStyle = "#ng{display:none;}";
  948. var hideMenu = "#topbarRightMenu #bbsLi.selected,#topbarRightMenu #ngLi.selected{display:none;}";
  949. var sidemenu = "ul#sidemenu li{border:solid 1px; width:100px;} ul#sidemenu li.selected{color:red;}";
  950. var contextMenuStyle = "#contextMenu{background : #d4d0c8;color : #000000;display : none;position : absolute;list-style : none; padding-left : 0px;box-shadow : 1px 1px;}";
  951. var contextItemStyle = "#contextMenu li{padding : 3px;}#contextMenu li:hover{background : #0a246a;color : #ffffff;}";
  952.  
  953. var styleTag = "<style id='nicoDicBBSViewerCSS' type='text/css'>" + idStyle + numberStyle + insideTooltipStyle + onMouseIdStyle + defaultTooltipStyle +
  954. onMouseTooltipStyle + leftboxStyle + ngStyle + hideMenu + sidemenu + contextMenuStyle + contextItemStyle + "</style>";
  955.  
  956. $("link").last().after($(styleTag));
  957. };
  958.  
  959. var counterAutopagerize = function(){
  960. $(document).bind("AutoPagerize_DOMNodeInserted", function(){
  961. $("[class^='autopagerize'] , dl:not(#bbsmain) , #autopagerize_message_bar").remove();
  962. });
  963. };
  964.  
  965. //以下main
  966. var main = function(ana){
  967. initConfig(["useNG", "tooltipOnDicPage", "showIDTooltip", "showResAnchorTooltip", "showResNumberTooltip", "showResHandleTooltip",
  968. "classificationID", "classificationResNumber"]);
  969. insertStyle();
  970. $(".st-bbs-contents dl").attr("id", "bbsmain");
  971. $(".border").remove();
  972. if(ana === undefined){
  973. var urlAnalyzer = new UrlAnalyzer();
  974. }else{
  975. var urlAnalyzer = ana;
  976. }
  977.  
  978. var manager = new ManagerToReadBbs(urlAnalyzer.getBBSURLs($(".st-bbs-contents .pager").eq(0)), urlAnalyzer);
  979. manager.initSmallBbs();
  980. counterAutopagerize();
  981. //if(!urlAnalyzer.inArticlePage()){
  982. // manager.scrollLoader();
  983. //}
  984. };
  985. //-----test用-----
  986. var c = net_threeaster_NicoDicBBSViewer;
  987. c.removeUselessLines = removeUselessLines;
  988. c.Res = Res;
  989. c.ManagerToReadBbs = ManagerToReadBbs;
  990. c.initConfig = initConfig;
  991. c.insertStyle = insertStyle;
  992. c.UrlAnalyzer = UrlAnalyzer;
  993. c.ResCollection = ResCollection;
  994. c.NgOperator = NgOperator;
  995. c.MenuOperator = MenuOperator;
  996. c.main = main;
  997.  
  998. //-----main実行/テスト時には途中で止まる-----
  999. if(typeof GM_getValue === 'function'){
  1000. main();
  1001. }
  1002. })(jQuery);