root/lang/javascript/userscripts/ldrize.user.js

Revision 38627, 45.9 kB (checked in by snj14, 6 years ago)

拡張版AutoPagerizeに対応

Line 
1// ==UserScript==
2// @name           LDRize
3// @namespace      http://white.s151.xrea.com/
4// @description    j,k,v,p,o,:,f,? + l,s,i
5// @include        http://*
6// @include        https://*
7// @include        file:///*
8// ==/UserScript==
9
10const SCRIPT_VERSION = "2010.10.27"
11const SCRIPT_URL     = "http://userscripts.org/scripts/show/11562"
12
13// ------------------------------------------------------------------
14// user siteinfo
15// ------------------------------------------------------------------
16/* template
17
18     domain    : URL or XPath
19     paragraph : XPath
20     link      : XPath
21     focus     : XPath
22     height    : Number
23     disable   : true
24
25    {
26        domain:    '',
27        paragraph: '',
28        link:      '',
29    },
30*/
31const SITEINFO = [
32]
33
34const KEYBIND = {
35        'j' : 'Next',
36        'k' : 'Prev',
37        'p' : 'Pin',
38        'l' : 'List',
39        'f' : 'Focus',
40        'v' : 'View',
41        'o' : 'Open',
42        'i' : 'Iframe',
43        's' : 'Siteinfo'
44}
45const KEYBIND_DESCRIPTION = {
46        'Next'           : 'Scroll next item',
47        'Prev'           : 'Scroll previous item',
48        'Pin'            : 'Pin',
49        'List'           : 'Toggle pinned items list',
50        'Focus'          : 'Focus on search box',
51        'Iframe'         : 'Open in iframe',
52        'Siteinfo'       : 'Change Siteinfo',
53
54        'View'           : 'Open in current tab',
55        'Open'           : 'Open pinned items or current item',
56        'OpenForeground' : 'Open in new tab (foreground)'
57}
58// ------------------------------------------------------------------
59// URLS
60// ------------------------------------------------------------------
61const SITEINFO_URLS = ["http://wedata.net/databases/LDRize/items.json"];
62
63// ------------------------------------------------------------------
64// height
65// ------------------------------------------------------------------
66const DEFAULT_HEIGHT = 0
67
68// ------------------------------------------------------------------
69// scroll measure by j/k in iframe
70// ------------------------------------------------------------------
71const IFRAME_SCROLL = 400
72
73// ------------------------------------------------------------------
74// not open by iframe
75// ------------------------------------------------------------------
76const IFRAME_IGNORE = [/\.(?:pdf|mp3|wmv)(?:\?[^#]*)?(?:#.*)?$/i]
77
78// ------------------------------------------------------------------
79// image
80// ------------------------------------------------------------------
81const IMAGE_INDICATOR = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAALCAIAAADN+VtyAAAABnRSTlMA/wD/AP83WBt9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAtElEQVR42mP4////////Zxob/0cFDBDR5wsW1KqqIkswzTIx8cvN/ff9e2pKSp2aGgMMMKWdObNp8mQmTs6/P37E+/vD5ZgYGBggchzy8v++f4+ytobIMf7//x+iBGLmux07/n39uurGDSZk0fe7d//7+XPVjRtNt24xwUU/7N//7+fPVVeuNN26xcDAwCy1ZYtfbu7HQ4f+ff++8sIFiCgDAwPUH9eiotD8wQCh0ET///8PAI0Gmocmb3e4AAAAAElFTkSuQmCC'
82const IMAGE_UP        = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAJCAIAAACJ2loDAAAABnRSTlMAAAAAAABupgeRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAYklEQVR42mNgIAOs5QxaweGPJsiEzNnHFcXCwPSb4d9sDk/sio5zxcHZv///62V3Rld0hTuFU0wAWfdvhr8NbLYIRbd5MljY2f68/ozuQDbmMjYLhEmMP/9ieuL3r18MVAYAAusZJ28GkW8AAAAASUVORK5CYII='
83const IMAGE_DOWN      = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAJCAIAAACJ2loDAAAABnRSTlMAAAAAAABupgeRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAfElEQVR42mNgoBZgZGBguM2T8e///88Mvz79//n5P4xk+Pn5/6/fDP+6fp1ggqj9z86MaQArGxuEwcTAwKD6Zcafn79YRHnRVf362/XrBFQRAwODztc53199QDGGgbnh12GESRBg+W0RQgUjU/HPvTg9spYzaAWHPzlBAABpZDIJZmWxzwAAAABJRU5ErkJggg=='
84
85// ------------------------------------------------------------------
86// CSS
87// ------------------------------------------------------------------
88const CSS_HIGHLIGHT_LINK   = false
89const CSS_HIGHLIGHT_PINNED = 'outline: 2px solid #CC6060 !important;outline-offset: 1px !important;outline-radius: 3px !important;'
90
91// ------------------------------------------------------------------
92// !!! END OF SETTINGS !!!
93// ------------------------------------------------------------------
94
95var boot = function (){
96var Class = function(){return function(){this.initialize.apply(this, arguments)}}
97Object.extend = function(self, other){
98        for(var i in other) self[i] = other[i]
99        return self;
100}
101var LDRize = new Class();
102LDRize.prototype = {
103  scrollHeight: 10,
104  indicatorMargin: 15,
105
106  paragraphes: {},      // this.paragraphes[xpath] => instance of Paragraphes (not list)
107  pinlist: null,        // instance of Paragraphes
108  siteinfo_all: [],     // list of Siteinfo
109  keybinds: [],         // list of Key
110  html: {},             // pinlist_number pinlist_container pinlist help space
111  img: {},              // indicator, up, down
112  iframe_view: false,
113  disable: false,       // disabled by siteinfo
114  setup: false,         // whether setup has finished
115
116  initialize: function(){
117          this.siteinfo_all = arguments[0];
118          var self = this;
119
120          window.LDRize = {
121                getSiteinfo: function(){return self.getSiteinfo()},               // return current siteinfo
122                setSiteinfo: function(a){self.setSiteinfo(a)},                    // specify instance of siteinfo
123                getSiteinfoByName: function(a){return self.getSiteinfoByName(a)}, // specify name of siteinfo
124                setSiteinfoByName: function(a){self.setSiteinfoByName(a)},        //
125          }
126
127          var res = this.initSiteinfo();
128          if(this.isIframe() && GM_getValue('iframe', '') == window.location.href){
129                  GM_setValue('iframe', '');
130                  if(!res){
131                          this.initSubShortcutkey();
132                  }else{
133                          this.iframe_view = true;
134                  }
135          }
136          if(!res) return;
137
138          this.initParagraph();
139          this.initLDRize();
140  },
141
142  initLDRize: function(){
143          if(this.setup) return;
144          var self = this;
145          this.setup = true;
146          this.initMinibuffer();
147          this.initShortcutkey();
148
149          var addFilterHandler = function(evt){
150                       self.removeSpace();
151                       setTimeout(function(){
152                          self.initParagraph([evt.target]);
153                       }, 0);
154          }
155          window.addEventListener('AutoPagerize_DOMNodeInserted', addFilterHandler, false);
156
157          var css = '';
158          css += [this.initHTML(),
159                          this.initImage(),
160                          this.initHelp(),
161                          this.initCSS(),
162                          this.initPinList(),
163                          this.initSpace()
164                          ].join('');
165          if(css != '') GM_addStyle(css);
166  },
167
168  initSubShortcutkey: function(){
169          var self = this;
170          var opt = {
171                description: 'escape from iframe',
172                command: function(){self.blurIframe()}
173          }
174          window.Minibuffer.addShortcutkey(Object.extend({key: "ESC"}, opt));
175          window.Minibuffer.addShortcutkey(Object.extend({key: "C-["}, opt));
176          if(this.disable) return;
177          for(var key in KEYBIND){
178                  if(KEYBIND[key] == 'Iframe'){
179                          window.Minibuffer.addShortcutkey({key:key, command: function(){self.blurIframe()}});
180                  }else if(KEYBIND[key] == 'Next'){
181                          window.Minibuffer.addShortcutkey({key:key, command: function(){self.bindScrollForward()}});
182                  }else if(KEYBIND[key] == 'Prev'){
183                          window.Minibuffer.addShortcutkey({key:key, command: function(){self.bindScrollBackward()}});
184                  }
185          }
186  },
187  initShortcutkey: function(){
188          var self = this;
189          this.keybinds = [];
190          keys(KEYBIND).forEach(function(key){
191                  var fn = KEYBIND[key];
192                  var de = KEYBIND_DESCRIPTION[fn];
193                  window.Minibuffer.addShortcutkey({key:key, command:function(e){self['bind'+fn].call(self, e)}, description: de});
194          });
195          if(this.isIframe()){
196                  window.Minibuffer.addShortcutkey({key:'ESC', command: function(){self.blurIframe()}});
197          }
198  },
199  initMinibuffer: function(){
200          var self = this;
201          // register command as global MinibufferCommand
202          var lst = [
203                  { name: 'LDRize::toggle-smooth-scroll',
204                        command: function(){GM_setValue('smooth', (!eval(GM_getValue('smooth', 'true'))).toString())}},
205                  { name: 'LDRize::update-siteinfo',
206                        command: function(){SiteinfoOperator.prototype.updateSiteinfo.call()}},
207                  { name: 'LDRize::paragraph-position-correct',
208                        command: function(){self.getParagraphes().reCollectAll()}},
209                  { name: 'LDRize::paragraph-re-collect',
210                        command: function(){
211                                var xpath = self.getSiteinfo()['paragraph'];
212                                self.paragraphes[xpath] = null;
213                                self.initParagraph();
214                        }},
215                  { name: 'LDRize::next',
216                        command: function(){self.bindNext()}},
217                  { name: 'LDRize::prev',
218                        command: function(){self.bindPrev()}},
219
220                  { name: 'pinned-node',
221                        command: function(stdin){
222                                return self.getPinnedItems().map(function(i){return i.node})}},
223                  { name: 'pinned-link',
224                        command: function(stdin){
225                                var xpath = self.getSiteinfo()['link'];
226                                return xpath ? self.getPinnedItems().map(function(i){return i.XPath(xpath)}) : []}},
227                  { name: 'pinned-or-current-node',
228                        command: function(stdin){
229                                return self.getPinnedItemsOrCurrentItem().map(function(i){return i.node})}},
230                  { name: 'pinned-or-current-link',
231                        command: function(stdin){
232                                var xpath = self.getSiteinfo()['link'];
233                                return xpath ? self.getPinnedItemsOrCurrentItem().map(function(i){return i.XPath(xpath)}) : []}},
234                  { name: 'current-node',
235                        command: function(stdin){return [self.getParagraphes().current.paragraph.node]}},
236                  { name: 'current-link',
237                        command: function(stdin){return self.getCurrentLink() || []}},
238                  { name: 'all-node',
239                        command: function(stdin){
240                                return self.getParagraphes().list.map(function(i){return i.node;});}},
241                  { name: 'clear-pin',
242                        command: function(stdin){if(!!stdin) self.clearPinlist(); return stdin}},
243                  { name: 'toggle-pin',
244                        command: function(stdin){self.togglePin(stdin); return stdin}},
245                  { name: 'set-pin',
246                        command: function(stdin){self.setPin(stdin); return stdin}},
247                  { name: 'unset-pin',
248                        command: function(stdin){self.unsetPin(stdin); return stdin}},
249                  ];
250          lst.forEach(window.Minibuffer.addCommand);
251  },
252  initSiteinfo: function(){
253          var filter2 = function(arr, fn){
254                  var res=[], tmp;
255                  arr.forEach(function(arg){if(tmp=fn(arg)) res.push(tmp)});
256                  return res;
257          }
258          try{
259                  this.siteinfo_available = filter2(this.siteinfo_all, function(arg){
260                          var s=new Siteinfo(arg);
261                          return s.isAvailable() && s;
262                  });
263                  return this.siteinfo_current = this.siteinfo_available[0];
264          }catch(e){
265                  this.disable = true;
266                  return false;
267          }
268  },
269
270  initParagraph: function(pages){
271          var xpath = this.getSiteinfo()['paragraph'];
272          if(pages && this.paragraphes[xpath]){
273                  this.paragraphes[xpath].setContext(pages).collect();
274          }else if(!this.paragraphes[xpath]){
275                  var p = new Paragraphes(xpath);
276                  p.collect();
277                  this.paragraphes[xpath] = p;
278          }
279  },
280  initHTML: function(){
281          this.html.iframe_container = $N('div',{id:'gm_ldrize_iframe_container'});
282          this.html.container = $N('div',{id:'gm_ldrize'}, [this.html.iframe_container]);
283          document.body.appendChild(this.html.container);
284          return '';
285  },
286  initImage: function(){
287          this.img = {};
288          var self = this;
289          var cssc = ['margin: 0px',
290                      'border: 0px',
291                      'padding: 0px',
292                      'z-index: 1000'].join(';');
293          var cssp = ['right:2px', 'position:fixed'].join(';');
294          var para = this.getParagraphes().getNth(0).paragraph;
295          this.img.indicator = $N('img',{
296                id: "gm_ldrize_indicator",
297                src: IMAGE_INDICATOR,
298                style: [cssc, ";position:absolute;top:",
299                        (para.y+this.getScrollHeight()-DEFAULT_HEIGHT),
300                        "px; left:",
301                        Math.max((para.x-this.indicatorMargin), 0),
302                        "px;"].join('')});
303          this.img.up = $N('img',{src: IMAGE_UP, id: "gm_ldrize_up_arrow"});
304          this.img.down = $N('img',{src: IMAGE_DOWN, id: "gm_ldrize_down_arrow"});
305
306          this.html.container.appendChild(this.img.indicator);
307          this.html.container.appendChild(this.img.up);
308          this.html.container.appendChild(this.img.down);
309          return ['img#', this.img.up.id,   '{top:15px;', cssp, ';', cssc, ';}\n',
310                  'img#', this.img.down.id, '{bottom:5px;', cssp, ';', cssc, ';}\n',
311                  'img#', this.img.down.id, '{', cssc, ';}\n',
312          ].join('') || '';
313  },
314  initHelp: function(){
315          var getKeyHTML = function(key, description){
316                  return $N('div',{},
317                            [$N('kbd',{},key.replace('S-','<shift> + ').replace('C-','<ctrl> + ').replace('A-','<alt> + ')),
318                            $N('div',{},description)]);
319          }
320          var sig = $N('div',{id:'gm_ldrize_signature'}, [$N('a',{href:SCRIPT_URL},'LDRize'), SCRIPT_VERSION]);
321          var bind = [$N('h1',{},'Shortcut Keys')];
322          this.keybinds.forEach(function(key){
323                  if(key.description) bind[bind.length] = getKeyHTML(key.key, key.description);
324          });
325          bind.push(sig);
326          var box = $N('div',{id:'gm_ldrize_help'}, bind);
327          this.html.help = box;
328          var id = 'div#' + box.id;
329          var inherit = 'background:inherit; background-image:inherit; background-color:inherit; color:inherit; text-align:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; margin:inherit; opacity:inherit; text-decoration:inherit; border:0px; height:100%; padding:0; margin:inherit; font-family:inherit; vertical-align:inherit; line-height:inherit; font-stretch:inherit; font-variant:inherit; font-size-adjust:inherit; letter-spacing:inherit;';
330          return [id,'{', 'right: 10px;', 'left: 10px;', 'top: 10px;', 'line-height: 100%;', 'vertical-align: baseline;', 'border: 1px dotted #444;', 'font-family: sans-serif;', 'text-decoration: none;', 'font-weight: normal;', 'font-style: normal;', 'font-size: medium;', 'font-stretch: normal;', 'font-variant: normal;', 'font-size-adjust: none;', 'letter-spacing: normal;', 'background: none;', 'text-align: left;', 'position: fixed;', 'margin: 0;', 'padding: 20px;', 'background-color: #000;', 'background-image: none;', 'color: #aaa;', 'border-radius: 10px;-moz-border-radius: 10px;', 'opacity: 0.8;', 'z-index: 1000;', '}\n',
331                  id,' div{', inherit, 'opacity: 1.0;', 'text-align: center;', '}',
332                  id,' > div{', inherit, 'margin: 0px 20px 20px 0px;', 'opacity: 1.0;', 'text-align: center;', '}',
333                  id,' a,', id,' a:visited,', id,' a:active,', id,' a:hover{', inherit, 'padding-right: 5px;', 'text-decoration: underline;', 'color: #CB6161;', '}\n',
334                  id,' div#gm_ldrize_signature{', inherit, 'margin: auto;', 'text-align: right;', 'font-style: italic;', '}\n',
335                  id,' div#gm_ldrize_signature a,', id,' div#gm_ldrize_signature a:active,', id,' div#gm_ldrize_signature a:hover', '{', 'font-style: italic;', '}',
336                  id,' kbd{', inherit, 'font-size: 120%;', 'font-weight: bold;', 'color: #B83E3B;', 'text-align: right;', 'width: 50%;', 'float: left;', '}\n',
337                  id,' kbd + div{', 'margin-left: 50%;', 'text-align: left;', '}\n',
338                  id,' kbd + div:before{', 'content: ": ";', '}\n',
339                  id,' h1{', inherit, 'margin: 20px auto;', 'background-image: none;', "opacity: 1.0;", 'font-weight: bold;', 'font-size: 150%;', 'color: #fff;', 'padding-left: 20px;', 'text-align: center;', '}\n',
340                  id,' #gm_ldrize_toggle_detail {', 'cursor: pointer;', 'text-decoration: underline;', 'color: #CB6161;', '}',
341          ].join('');
342  },
343  initCSS: function(){
344          var css = '';
345          if(CSS_HIGHLIGHT_LINK) css += "\n.gm_ldrize_link {" + CSS_HIGHLIGHT_LINK + "}";
346          if(CSS_HIGHLIGHT_PINNED) css += "\n.gm_ldrize_pinned {" + CSS_HIGHLIGHT_PINNED + "}";
347          css += ".gm_ldrize_iframe { min-height:200px; position:fixed; bottom:0px; left:0px; right:0px; }";
348          return css;
349  },
350  initPinList: function(){
351          var number_container = $N('div',{id:'gm_ldrize_pinlist_number_container'},
352                                    [$N('div',{id:'gm_ldrize_pinlist_number'}),' item']);
353          var pin_container = $N('div',{style: 'display:'+GM_getValue('pinlist', 'block')});
354          var box = $N('div',{id:'gm_ldrize_pinlist', style:'display:none;'},
355                       [number_container, pin_container]);
356
357          this.html.pinlist_number = number_container;
358          this.html.pinlist_container = pin_container;
359          this.html.pinlist = box;
360          this.html.container.appendChild(box);
361          var id = 'div#' + box.id;
362          var inherit = 'background:inherit; background-image:inherit; background-color:inherit; color:inherit; text-align:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; margin:inherit; opacity:inherit; text-decoration:inherit; border:0px; height:100%; padding:0; margin:inherit; font-family:inherit; vertical-align:inherit; line-height:inherit; font-stretch:inherit; font-variant:inherit; font-size-adjust:inherit; letter-spacing:inherit;';
363          return [id,'{', 'line-height: 100%;', 'vertical-align: baseline;', 'border: 1px dotted #444;', 'font-family: sans-serif;', 'text-decoration: none;', 'font-weight: normal;', 'font-style: normal;', 'font-size: medium;', 'font-stretch: normal;', 'font-variant: normal;', 'font-size-adjust: none;', 'letter-spacing: normal;', 'background: none;', 'text-align: left;', 'position: fixed;', 'right: 20px;', 'bottom: 15px;', 'margin: 0px;', 'padding: 10px;', 'background-color: #000;', 'background-image: none;', 'color: #fff;', '-moz-border-radius: 10px;border-radius: 10px;', 'opacity: 0.7;', 'z-index: 1000;', '}\n',
364                  id,' div{', inherit, 'margin: 10px;', 'opacity: 1.0;', '}',
365                  id,' #gm_ldrize_pinlist_number_container > span {', 'color: #B83E3B;', 'font-size: 150%;', 'font-weight: bold;', 'display: inline;', '}',
366          ].join('');
367  },
368  initSpace: function(){
369          this.html.space = $N('div',{id:'gm_ldrize_space'},'dummy');
370          return [
371                  'div#gm_ldrize_space {',
372                  'visibility: hidden;',
373                  'position: absolute;',
374                  'height: ', window.innerHeight, 'px;',
375                  '}'].join('');
376  },
377// end of initialize functions
378
379// siteinfo
380  getSiteinfoByName: function(name){
381          return this.siteinfo_available.find(function(s){return s.name==name});
382  },
383  setSiteinfoByName: function(name){
384          var siteinfo = this.getSiteinfoByName(name);
385          this.siteinfo_current = siteinfo;
386          this.initParagraph();
387  },
388  setSiteinfo: function(siteinfo){
389          if(Siteinfo.prototype.isPrototypeOf(siteinfo)){
390                  this.siteinfo_current = siteinfo;
391                  this.initParagraph();
392                  this.initLDRize();
393          }
394  },
395
396  attachClassToNode: function(node, _class){
397          if(node){
398                  var oldclass = node.getAttribute('class');
399                  node.setAttribute('class', (oldclass ? oldclass + " " : "") + _class);
400                  return true;
401          }
402  },
403  removeClassToNode: function(node, _class){
404          if(node && node.getAttribute('class')){
405                  var re = new RegExp(' ?' + _class);
406                  node.setAttribute('class', node.getAttribute('class').replace(re, ""));
407                  if(node.getAttribute('class') == '') node.removeAttribute('class', 0);
408          }
409  },
410
411  // 一番下までスクロールしたときにもj/kで選んだ要素が上の方に表示されるように。
412  appendSpace: function(){
413          if(!document.getElementById('gm_ldrize_space')){
414                  this.html.space.style.top = Math.max(document.documentElement.scrollHeight,
415                                                                                           document.body.scrollHeight) + 'px';
416          }
417          this.html.container.appendChild(this.html.space);
418  },
419  removeSpace: function(){if(document.getElementById('gm_ldrize_space')) this.html.container.removeChild(this.html.space)},
420
421// navi (images at right side)
422  naviUpdate: function(){
423          var p=this.getParagraphes();
424          var i=this.img, up=i.up, down=i.down;
425          if(p){
426                  if(!p.getPrev()){up.style.display='none'; return;}
427                  if(!p.getNext()){down.style.display='none'; return;}
428          }
429          up.style.display = 'block';
430          down.style.display = 'block';
431  },
432
433// indicator
434  indicatorHide: function(){this.img.indicator.style.display = 'none'},
435  indicatorUpdate: function(){
436          var p=this.getParagraphes().current.paragraph;
437          if(!p)return;
438          var i=this.img.indicator;
439          i.style.display = 'block';
440          i.style.top = (p.y+this.getScrollHeight()-DEFAULT_HEIGHT) + 'px';
441          i.style.left = Math.max((p.x-this.indicatorMargin), 0) + 'px';
442  },
443
444// getter
445  getSiteinfo: function(){return this.siteinfo_current},
446  getParagraphes: function(){return this.paragraphes[this.getSiteinfo().paragraph]},
447  getScrollHeight: function(){
448          var h = this.getSiteinfo()['height'];
449          return DEFAULT_HEIGHT + ((typeof h != 'undefined') ? Number(h) : this.scrollHeight);
450  },
451  useSmoothScroll: function(){return eval(GM_getValue('smooth', 'true'))},
452  scrollTo: function(x, y){
453          (this.useSmoothScroll() ? SmoothScroll: window).scrollTo(x, y);
454  },
455
456// iframe
457  isIframe: function(){return self != top},
458  blurIframe: function(){
459          window.top.focus();
460  },
461
462// pin
463  setPin: function(nodes){
464          var self = this;
465          this.togglePin(nodes, function(node){return self.addPinToPinList(node);});
466  },
467  unsetPin: function(nodes){
468          var self = this;
469          this.togglePin(nodes, function(node){return self.removePinFromPinList(node);});
470  },
471  togglePin: function(nodes, fn){
472          var self = this;
473          if(typeof fn == 'undefined'){
474                  fn = function(node){
475                          return self.removePinFromPinList(node) || self.addPinToPinList(node);
476                  }
477          }
478          toArray(nodes).forEach(function(node){
479                  if ( fn(node) ) {
480                          self.toggleClassForPin(node);
481                  }
482          });
483          var a = this.html.pinlist_number;
484          var len = this.html.pinlist_container.childNodes.length;
485          if(len){
486                  a.innerHTML =
487                        ['<span>',
488                         len,
489                         '</span>item',
490                         len==1 ?'':'s'].join('');
491                  this.html.pinlist.style.display = 'block';
492          }else{
493                  a.innerHTML = '';
494                  this.html.pinlist.style.display = 'none';
495          }
496  },
497  toggleClassForPin: function(node){
498          var _class = node.getAttribute('class');
499          if(_class && (' ' + _class + ' ').indexOf(" gm_ldrize_pinned ") != -1){
500                  this.removeClassToNode(node, 'gm_ldrize_pinned');
501          }else{
502                  this.attachClassToNode(node, 'gm_ldrize_pinned');
503          }
504  },
505  addPinToPinList: function(node){
506          var res = [], text='';
507          var paragraph = this.getParagraphes().find(function(para){return node == para.node});
508          if(!paragraph.html){
509                  var getCloneImage = function(node){
510                          var clone = node.cloneNode(false);
511                          if(node.width > 40)  clone.width  = 40;
512                          if(node.height > 40) clone.height = 40;
513                          clone.setAttribute('style', '');
514                          return clone;
515                  }
516                  var appendText = function(node){
517                          if(text.length > 30) return;
518                          text = (text + node.nodeValue.replace(/\s+/g, '')).slice(0, 30);
519                  }
520                  var xpath = this.getSiteinfo()['view'];
521                  var matches = xpath && $X(xpath, node);
522                  if(matches){
523                          matches.forEach(function(n){
524                                  if(n.nodeType == 3) appendText(n);
525                                  else if(n.nodeName.toLowerCase() == 'img') res.push(getCloneImage(n));
526                                  else res.push(n.cloneNode(false));
527                          });
528                          if(text) res.push(text);
529                  }else{
530                          xpath = '(descendant-or-self::img | descendant::text()[normalize-space(self::text()) != ""])';
531                          matches = $X(xpath, node);
532                          if(!matches.length) return false;
533                          var height = new Paragraph(matches[0]).y;
534                          res = [];
535                          var allstringlength = 0;
536                          matches.some(function(m){
537                                  if(m.nodeName.toLowerCase() == 'img' || m.nodeType == 3){
538                                          var h = new Paragraph(m).y;
539                                          if(Math.abs(height - h) >= 20) return true;
540                                          if(m.nodeType == 3){
541                                                  var str = m.nodeValue.replace(/\s+/g, '');
542                                                  allstringlength += str.length;
543                                                  if(allstringlength > 30){
544                                                          res.push(str.slice(0, 30) + '...');
545                                                          return true;
546                                                  }
547                                                  res.push(m.nodeValue);
548                                          }else{
549                                                  res.push(getCloneImage(m));
550                                          }
551                                  }
552                          });
553                  }
554                  var div = $N('div',{},res);
555                  paragraph.html = div;
556          }
557          this.html.pinlist_container.appendChild(paragraph.html);
558          return true;
559  },
560  removePinFromPinList: function(node){
561          var para = this.getParagraphes().find(function(para){return node == para.node});
562          var view = para.html;
563          if(!view) return false;
564          var children = toArray(this.html.pinlist_container.childNodes);
565          var html = children.find(function(arg){return arg == view});
566          if(!html) return false;
567          this.html.pinlist_container.removeChild(html);
568          return true;
569  },
570  clearClassForPin: function(){
571          var self = this;
572          var children = this.getPinnedItems();
573          children.forEach(function(child){
574                  self.removeClassToNode(child.node, 'gm_ldrize_pinned');
575          });
576  },
577  clearPinlist: function(){
578          this.clearClassForPin();
579          this.html.pinlist_container.innerHTML = '';
580          this.html.pinlist_number.innerHTML = '';
581          this.html.pinlist.style.display = 'none';
582  },
583  pinIsEmpty: function(){return !this.html.pinlist_container.childNodes.length},
584  getPinnedItems: function(){
585          var paragraphes = this.getParagraphes();
586          return toArray(this.html.pinlist_container.childNodes).map(function(view){
587                  return paragraphes.find(function(arg){
588                          return arg.html == view});
589          });
590  },
591  getPinnedItemsOrCurrentItem: function(){
592        if(this.pinIsEmpty()){
593                var para = this.getParagraphes().current.paragraph;
594                return para ? [para] : [];
595        }
596        return this.getPinnedItems();
597  },
598
599// command
600
601// j -- next paragraph
602  bindNext: function(aEvent){
603          if(this.useSmoothScroll()) SmoothScroll.stop();
604          var scrollHeight = Math.max(document.documentElement.scrollHeight,
605                                                                  document.body.scrollHeight);
606          var scrollWidth = Math.max(document.documentElement.scrollWidth,
607                                                                  document.body.scrollWidth);
608          if(this.isIframe() && scrollHeight - window.self.innerHeight == window.self.scrollY){
609                  this.blurIframe();
610                  return;
611          }
612          var paragraphes = this.getParagraphes();
613          var next = paragraphes.setScrollY(window.self.scrollY + this.getScrollHeight())
614                                                        .getNextToMove();
615          if(next && !next.paragraph.check()){
616                  this.getParagraphes().reCollectAll();
617                  this.bindNext();
618                  return;
619          }
620          if(next){
621                  paragraphes.selectNth(next.position);
622                  // this should execute before scroll
623                  if(next.paragraph.y > (scrollHeight - window.self.innerHeight)) this.appendSpace();
624                  // these shoud execute after selectNth
625                  this.indicatorUpdate();
626                  this.naviUpdate();
627                  this.scrollTo((window.self.innerWidth < next.paragraph.x ? next.paragraph.x-this.indicatorMargin : window.self.pageXOffset)
628                                                                 , next.paragraph.y - this.getScrollHeight());
629          }else{
630                  this.scrollTo(window.self.pageXOffset,
631                                                                 scrollHeight - window.self.innerHeight);
632                  this.indicatorHide();
633                  paragraphes.selectNth(paragraphes.length);
634          }
635  },
636  bindScrollForward: function(){
637          if(this.useSmoothScroll()) SmoothScroll.stop();
638          var scrollHeight = Math.max(document.documentElement.scrollHeight,
639                                                                  document.body.scrollHeight);
640          if(this.isIframe() && scrollHeight - window.self.innerHeight == window.self.scrollY){
641                  this.blurIframe();
642                  return;
643          }
644          this.scrollTo(window.scrollX, window.scrollY + IFRAME_SCROLL);
645  },
646  bindScrollBackward: function(){
647          if(this.isIframe() && window.self.scrollX==0 && window.self.scrollY==0) this.blurIframe();
648          this.scrollTo(window.scrollX, window.scrollY - IFRAME_SCROLL);
649  },
650
651// k -- previous paragraph
652  bindPrev: function(){
653          var x, y;
654          if(this.useSmoothScroll()) [x, y] = SmoothScroll.stop();
655          if(this.isIframe() && window.self.scrollX==0 && window.self.scrollY==0) this.blurIframe();
656          var paragraphes = this.getParagraphes();
657          var prev = paragraphes.setScrollY(typeof y != 'undefined' ? y : window.scrollY + this.getScrollHeight())
658                                                        .getPreviousToMove();
659          paragraphes.selectNth(prev ? prev.position : -1);
660          if(prev && !prev.paragraph.check()){
661                  this.getParagraphes().reCollectAll();
662                  this.bindPrev();
663                  return;
664          }
665          this.indicatorUpdate();
666          if(prev){
667                  var x;
668                  if(window.pageXOffset > 0 && prev.paragraph.x < window.innerWidth){
669                          x = 0;
670                  }else if(window.pageXOffset > 0){
671                          x = prev.paragraph.x-this.indicatorMargin;
672                  }else{
673                          x = window.pageXOffset;
674                  }
675                  this.scrollTo(x, prev.paragraph.y - this.getScrollHeight());
676          }else{
677                  this.scrollTo(0, 0);
678          }
679  },
680
681// p -- pin
682  bindPin: function(){
683          var paragraphes = this.getParagraphes();
684          if(!paragraphes.currentIsValid()) this.bindNext();
685          if(!paragraphes.currentIsValid()) return;
686
687          var current = paragraphes.current.paragraph;
688          this.togglePin([current.node]);
689          this.bindNext();
690  },
691
692// l -- toggle pin list
693  bindList: function(){
694          var val = GM_getValue('pinlist', 'block') == 'none' ? 'block' : 'none';
695          GM_setValue('pinlist', this.html.pinlist_container.style.display = val);
696  },
697
698// f -- focus on search field
699  bindFocus: function(){
700          var xpath = this.getSiteinfo()['focus'] || '//input[@type="text" or not(@type)]';
701          var lst = $X(xpath);
702          if(!lst.length) return;
703          var elm = lst[0];
704
705          var shortcutkey = window.Minibuffer.getShortcutKey();
706          shortcutkey.addCommand({key:'ESC', command:function(aEvent){aEvent.target.blur()}});
707          shortcutkey.addCommand({key:'C-[', command:function(aEvent){aEvent.target.blur()}});
708          shortcutkey.addEventListener(elm, 'keypress', true);
709
710          elm.focus();
711          var para = new Paragraph(elm);
712          window.scrollTo(window.pageXOffset, para.y - this.getScrollHeight());
713          return true;
714  },
715
716// i -- view in iframe
717  bindIframe: function(){
718          if(this.isIframe()){
719                  this.blurIframe();
720                  return;
721          }
722          var paragraphes = this.getParagraphes();
723          if(!paragraphes.currentIsValid()) this.bindNext();
724          if(!paragraphes.currentIsValid()) return;
725          var current = paragraphes.current.paragraph;
726          var node = current.node;
727          if(!node) return;
728          var iframe = current.getIframe();
729          if(iframe){
730                  current.toggleIframe();
731                  setTimeout(function(){
732                          iframe.contentWindow.focus();
733                          current.setIframeAutoHide();
734                  }, 0);
735                  return;
736          }
737          var links = this.getCurrentLink();
738          if(!links.length) return;
739          var url = links[0].href;
740          if(IFRAME_IGNORE.some(function(re){return re.test(url)})) return;
741          window.scrollTo(window.pageXOffset, current.y);
742          current.iframe = $N('iframe',{
743                _class: 'gm_ldrize_iframe',
744                src: url,
745                style: 'top:'+current.node.offsetHeight+'px;'
746          });
747          GM_setValue('iframe', url);
748          this.html.iframe_container.appendChild(current.iframe);
749          current.iframe.contentWindow.focus();
750          current.iframe.addEventListener('load', function(){
751                  current.setIframeAutoHide();
752                }, false);
753  },
754  getCurrentLink: function(){
755          var xpath = this.getSiteinfo()['link'];
756          var paragraphes = this.getParagraphes();
757          var paragraph = paragraphes.current.paragraph || paragraphes.getNth(0).paragraph;
758          return xpath ? [paragraph.XPath(xpath)] : false;
759  },
760
761// o -- open in new tab
762  bindOpen: function(){
763          var nopin = this.pinIsEmpty();
764          window.Minibuffer.execute('pinned-or-current-link | open | clear-pin');
765          if(nopin) this.bindNext();
766  },
767  bindOpenForeground: function(){
768          window.Minibuffer.execute('pinned-or-current-link | open blank | clear-pin');
769  },
770
771// v -- view in current tab
772  bindView: function(){
773          window.Minibuffer.execute('current-link | open top | clear-pin');
774  },
775
776// s -- select siteinfo
777  bindSiteinfo: function(){
778          var current = this.getSiteinfo();
779          var lst = this.siteinfo_available.remove(current);
780          if(lst.length == 0) return;
781          var self = this;
782          var callback = function(str){if(str) self.setSiteinfoByName(str)};
783          var minibuffer = window.Minibuffer.getMinibuffer()
784                                                         .setPrompt('Change siteinfo ['+ current.name +'] :')
785                                                         .setCandidates(lst.map(function(s){return s.name}))
786                                                         .complete(callback);
787  }
788}
789
790var Paragraphes = new Class();
791Paragraphes.prototype = {
792  initialize: function(){
793          this.list = new Array();
794          this.xpath = arguments[0];
795          this.context = [];
796          this.current = {
797                paragraph: null,
798                position:  null,
799          };
800  },
801  collect: function(){
802          var matches = $X(this.xpath);
803          if(!matches || !matches.length) return;
804          var list = this.list;
805          var self = this;
806          matches.forEach(function(node){
807                  // when call by AutoPagerize, ignore old paragraphes
808                  if(self.context.length &&
809                     !self.context.some(function(e){
810                                            return e == node || (document.DOCUMENT_POSITION_CONTAINS & node.compareDocumentPosition(e) )}))
811                      return;
812
813                  // add paragraph to cache
814                  var para = new Paragraph(node);
815                  if(!list.length || (list[list.length-1]).greaterThan(para)){
816                          list[list.length] = para;
817                  }else{
818                          // if node is not next to previous node, insert into pertinent position
819                          var idx = list.bsearch_upper_boundary(function(e){return e.compare(para)});
820                          list = list.slice(0, idx).concat(para, list.slice(idx));
821                  }
822          });
823          this.context = [];
824  },
825  setContext: function(arg){this.context = arg; return this},
826
827  select: function(arg){this.selectNth(this.position(arg))},
828  selectNth: function(n){
829          if(n == -1){
830                  this.current = {position:-1, paragraph:null}
831          }else if(n == null || n === false){
832                  this.current = {};
833          }else{
834                  this.current = this.getNth(n);
835          }
836  },
837  bsearch_upper_boundary: function(fn){
838          var idx = this.list.bsearch_upper_boundary(fn);
839          if(this.length == idx) idx = this.length;
840          return this.getNth(idx);
841  },
842
843  getPrev: function(){
844          var n=this.current.position;
845          return (n == 0) ? false : this.getNth(n-1);
846  },
847  getNext: function(){
848          var n=this.current.position;
849          return (n == this.length-1) ? false : this.getNth(n+1);
850  },
851  currentIsValid: function(){
852          return this.current.position !== null && (this.current.position >= 0) && (this.current.position < this.length);
853  },
854  setScrollY: function(scrolly){
855          this.scrolly = scrolly;
856          return this;
857  },
858  getNextToMove: function(){
859          if(this.current.paragraph && this.current.paragraph.y == this.scrolly){
860                  return this.getNext();
861          }
862          var res = this.getCorrected();
863          return (res.position == this.length) ? false : res;
864  },
865  getPreviousToMove: function(){
866          if(this.current.paragraph && this.current.paragraph.y == this.scrolly){
867                  return this.getPrev();
868          }
869          var res = this.getCorrected();
870          return (res.position < 1) ? false : this.getNth(res.position - 1);
871  },
872  getCorrected: function(){
873          var self = this;
874          return this.bsearch_upper_boundary(function(para){
875                  if(para.y == self.scrolly){
876                          return 0;
877                  }
878                  return para.y - self.scrolly;
879          });
880  },
881  reCollectAll: function(){
882          this.list.forEach(function(para){para.setOffset()});
883  },
884// Array
885  get length(){return this.list.length}, // getter
886  add: function(paragraph){this.list[this.list.length] = paragraph},
887  getNth: function(n){return {paragraph:this.list[n], position:n}},
888  getAll: function(){return this.list},
889  find: function(arg){return this.list.find(arg)},
890  position: function(arg){return this.list.position(arg)},
891  remove: function(arg){return this.list.remove(arg)},
892  removeSelf: function(arg){return this.list = this.list.remove(arg)},
893}
894
895var Paragraph = new Class();
896Paragraph.prototype = {
897  initialize: function(){
898          this.node = arguments[0];
899          this.setOffset();
900          this.html = null;
901          this.iframe = null;
902  },
903  setOffset: function(){
904          var offsetx, offsety;
905          [offsetx, offsety] = this.getOffset();
906          this.x = offsetx;
907          this.y = offsety;
908          this.str = this.x+':'+this.y;
909  },
910  getOffset: function(){
911          var node=this.node, textnode;
912
913          if(node.nodeType==3){
914                  textnode = node;
915                  var span = $N('span');
916                  node.parentNode.insertBefore(span, node);
917                  node = span;
918          }
919
920          var offsetx = node.offsetLeft;
921          var offsety = node.offsetTop;
922          var count = 0;
923          var tmpnode=node;
924          while(tmpnode=tmpnode.offsetParent){
925                  offsety += tmpnode.offsetTop;
926                  offsetx += tmpnode.offsetLeft;
927          }
928          if(textnode) node.parentNode.removeChild(node);
929          return [offsetx, offsety];
930  },
931
932  check: function(){
933          var offsetx, offsety;
934          [offsetx, offsety] = this.getOffset();
935          if(offsetx != this.x || offsety != this.y) return false;
936          return true;
937  },
938
939  greaterThan: function(arg){
940          return this.y < arg.y || (this.y == arg.y && this.x < arg.x);
941  },
942
943  compare: function(e){
944          if(e.y < this.y || (e.y == this.y && e.x < this.x)) return 1;
945          if(e.y > this.y || (e.y == this.y && e.x > this.x)) return -1;
946          return 0;
947  },
948
949  XPath: function(xpath){
950          var links = $X(xpath, this.node);
951          if(!links || links.length == 0) return;
952          return links[0];
953  },
954  getIframe: function(){
955          var self = this;
956          var cantianer = document.getElementById('gm_ldrize_iframe_container');
957          var node = toArray(cantianer.childNodes).find(function(elm){return elm == self.iframe});
958          return node || false;
959  },
960  toggleIframe: function(){
961          var iframe = this.getIframe();
962          iframe.style.display = (iframe.style.display == 'block')?'none':'block';
963          return iframe;
964  },
965  hideIframe: function(){
966          var iframe = this.getIframe();
967          iframe.style.display = 'none';
968  },
969  setIframeAutoHide: function(){
970          var self = this;
971          var hide = function(){
972                  self.hideIframe();
973                  document.removeEventListener('focus', hide, false);
974          }
975          document.addEventListener('focus', hide, false);
976  },
977}
978
979//// Siteinfo
980var Siteinfo = new Class();
981Siteinfo.prototype = {
982  initialize: function(){
983          // ['name', 'domain', 'paragraph', 'link', 'view', 'height', 'focus', 'disable']
984          Object.extend(this, arguments[0]);
985  },
986  isAvailable: function(){
987          try{
988                  if((this.domain == true || this.domain == 'microformats') &&
989                         $X(this.paragraph).length){
990                          return true;
991                  }
992                  if(location.href.match(this.domain) && (this.disable || $X(this.paragraph).length)){
993                          if(this.disable) throw 0;
994                          return true;
995                  }
996                  if($X(this.domain).length && (this.disable || $X(this.paragraph).length)){
997                          if(this.disable) throw 0;
998                          return true;
999                  }
1000          }catch(e){
1001//                log(['errer', info]);
1002                  if(e==0) throw 0;
1003          }
1004          return false;
1005  }
1006}
1007var SiteinfoOperator = new Class();
1008SiteinfoOperator.prototype = {
1009  expire : 24 * 60 * 60 * 1000, // 24h
1010  counter : 0,
1011  cached_siteinfo : [], // to avoid save USER_SITEINFO
1012  initialize: function(){
1013          // name, version, siteinfo, urls, initializer, parser, expire
1014          // parser: function(response){return [list of siteinfo]}
1015          Object.extend(this, arguments[0]);
1016          this.init();
1017  },
1018  updateSiteinfo: function(){GM_setValue('cacheInfo', '({})')},
1019  getCache: function(){return eval(GM_getValue('cacheInfo', '({})'))},
1020  setCache: function(e){return GM_setValue('cacheInfo', uneval(e))},
1021  getCacheErrorCallback: function(url){
1022          if(this.cached_siteinfo[url]){
1023                  this.cached_siteinfo[url]['expire'] = new Date(new Date().getTime() + this.expire),
1024                  this.setCache(this.cached_siteinfo);
1025          }
1026          this.initializerCaller();
1027  },
1028  getCacheCallback: function(res, url){
1029          if(res.status != 200) return this.getCacheErrorCallback(url);
1030          var info_list = this.parser(res);
1031          if(info_list.length){
1032                  this.cached_siteinfo[url] = {
1033                        url: url,
1034                        expire: new Date(new Date().getTime() + this.expire),
1035                        info: info_list
1036                  }
1037                  this.setCache(this.cached_siteinfo);
1038                  this.siteinfo = this.siteinfo.concat(this.cached_siteinfo[url].info);
1039          }
1040          this.initializerCaller();
1041  },
1042  initializerCaller: function(){
1043          // only last call will be allowed
1044          if(++this.counter == this.urls.length && this.siteinfo.length){
1045                  this.initializer(this.siteinfo);
1046          }
1047  },
1048  init: function(){
1049          if(window.Minibuffer){
1050                  window.Minibuffer.addCommand({
1051                        name: this.name+'::update-siteinfo',
1052                        command: this.updateSiteinfo
1053                  });
1054          }
1055          GM_registerMenuCommand(this.name + ' - update siteinfo', this.updateSiteinfo);
1056          this.cached_siteinfo = this.getCache();
1057          var self = this;
1058          this.urls.forEach(function(url){
1059                  if(!self.cached_siteinfo || !self.cached_siteinfo[url] || self.cached_siteinfo[url].expire < new Date()){
1060                          var opt = {
1061                                method: 'get',
1062                                url: url,
1063                                headers: {
1064                                        'User-agent': 'Mozilla/5.0 Greasemonkey ('+self.name+'/'+self.version+')',
1065                                },
1066                                onload:  function(res){self.getCacheCallback(res, url)},
1067                                onerror: function(res){self.getCacheErrorCallback(url)},
1068                          }
1069                          GM_xmlhttpRequest(opt);
1070                  }else{
1071                          self.siteinfo = self.siteinfo.concat(self.cached_siteinfo[url].info);
1072                          self.initializerCaller();
1073                  }
1074          });
1075  }
1076}
1077
1078var SmoothScroll = {
1079  steps : 200,
1080  duration : 6000,
1081  destinationx: null,
1082  destinationy: null,
1083  id_list : [],
1084  stop : function(){
1085          var x, y;
1086          if(SmoothScroll.id_list.length){
1087                  SmoothScroll.clearTimer();
1088                  if(SmoothScroll.destinationx !== null||
1089                         SmoothScroll.destinationy !== null){
1090                          window.scrollTo(SmoothScroll.destinationx, SmoothScroll.destinationy);
1091                          x = SmoothScroll.destinationx;
1092                          y = SmoothScroll.destinationy;
1093                        }
1094          }
1095          SmoothScroll.resetDestination();
1096          return [x, y]
1097  },
1098  resetDestination : function(){
1099          SmoothScroll.destinationx = null;
1100          SmoothScroll.destinationy = null;
1101  },
1102  scrollTo : function(destX, destY){
1103          SmoothScroll.destinationx = destX;
1104          SmoothScroll.destinationy = destY;
1105          var y = window.pageYOffset;
1106          var x = window.pageXOffset;
1107          var time;
1108          for(var i=1; i<SmoothScroll.steps; i++){
1109                  x = destX-((destX-x)/2);
1110                  y = destY-((destY-y)/2);
1111                  time = (SmoothScroll.duration/SmoothScroll.steps) * i;
1112                  if((Math.abs(destY-y)<1 && Math.abs(destX-x)<1) || i+1 == SmoothScroll.steps){
1113                          var id = setTimeout(SmoothScroll.makeScrollTo(destX, destY), time);
1114                          var id2 = setTimeout(SmoothScroll.resetDestination, time);
1115                          SmoothScroll.id_list.push(id);
1116                          SmoothScroll.id_list.push(id2);
1117                          break;
1118                  }else{
1119                          var id = setTimeout(SmoothScroll.makeScrollTo(x, y), time);
1120                          SmoothScroll.id_list.push(id);
1121                  }
1122          }
1123  },
1124  clearTimer: function(){
1125          SmoothScroll.id_list.forEach(function(id){
1126                  clearTimeout(id);
1127          });
1128          SmoothScroll.id_list = [];
1129  },
1130  makeScrollTo: function(x, y){
1131          return function(){
1132                  window.scrollTo(x, y);
1133          }
1134  },
1135}
1136
1137//// library
1138Function.prototype.later = function(ms){
1139        var self = this;
1140        return function(){
1141                var args = arguments;
1142                var thisObject = this;
1143                var res = {
1144                        arg: args,
1145                        complete: false,
1146                        cancel: function(){clearTimeout(PID);},
1147                        notify: function(){clearTimeout(PID);later_func()}
1148                };
1149                var later_func = function(){
1150                        self.apply(thisObject, args);
1151                        res.complete = true;
1152                };
1153                var PID = setTimeout(later_func, ms);
1154                return res;
1155        };
1156}
1157Array.prototype.position = function(obj){
1158        var f = (typeof obj == 'function') ? obj : function(a){return a == obj}; //===
1159        var idx;
1160        return this.some(function(v, i){idx = i; return f(v)}) ? idx : false;
1161}
1162Array.prototype.find = function(obj){
1163        var i = this.position(obj);
1164        return typeof i == 'number' ? this[i] : false;
1165}
1166Array.prototype.remove = function(obj){
1167        var test = (typeof obj == 'function') ? obj : function(a){return a == obj}; //===
1168        return this.filter(function(e){return !test(e)});
1169}
1170
1171function addStyle(css, id){ // GM_addStyle is slow
1172        var link = document.createElement('link');
1173        link.rel = 'stylesheet';
1174        link.href = 'data:text/css,' + escape(css);
1175        document.documentElement.childNodes[0].appendChild(link);
1176}
1177
1178// %o %s %i
1179function log(){if(console) console.log(arguments);}
1180function group(){if(console) console.group(arguments)}
1181function groupEnd(){if(console) console.groupEnd();}
1182
1183function hasKeys(hash){
1184        for(var key in hash) return true;
1185        return false;
1186}
1187function keys(hash){
1188        var tmp = [];
1189        for(var key in hash)if(hash.hasOwnProperty(key))tmp.push(key);
1190        return tmp;
1191}
1192
1193function toArray(arg){
1194        var arr = new Array();
1195        for(var i=0,l=arg.length; i<l; arr.push(arg[i++]));
1196        return arr;
1197}
1198
1199var $N = window.Minibuffer.$N
1200var $X = window.Minibuffer.$X
1201
1202// ------------------------------------------------------------------
1203// binary search
1204// ------------------------------------------------------------------
1205//           0  1  2  3  4 (5) 6  7 (8) 9 (index)
1206// var a =  [1, 2, 2, 3, 3, 4, 4, 4, 5, 5];
1207// var i =  a.bsearch_lower_boundary(function(e){return e - 4})
1208// i     => 5 // compare 3 times
1209// var i =  a.bsearch_lower_boundary(function(e){return e - 4}, 5, 6)
1210// i     => 5 // compare 1 time
1211//
1212// var i =  a.bsearch_upper_boundary(function(e){return (e<4)?(-1): (e>4)?(1): 0})
1213// i     => 8 // compare 3 times
1214// var i =  a.bsearch_upper_boundary(function(e){return e - 4}, 8, 9)
1215// i     => 8 // compare 1 time
1216Array.prototype.bsearch_lower_boundary = function(compare, begin, end){
1217        var lower = (typeof begin == 'undefined') ? -1 : begin-1;
1218        var upper = (typeof end   == 'undefined' || end >= this.length) ? this.length : end;
1219        while(lower + 1 != upper){
1220                var mid = Math.floor((lower + upper) / 2);
1221                if(compare(this[mid]) < 0) lower = mid;
1222                else upper = mid;
1223        }
1224        return upper;
1225}
1226Array.prototype.bsearch_upper_boundary = function(compare, begin, end){
1227        var lower = (typeof begin == 'undefined') ? -1 : begin-1;
1228        var upper = (typeof end   == 'undefined' || end >= this.length) ? this.length : end;
1229        while(lower + 1 != upper){
1230                var mid = Math.floor((lower + upper) / 2);
1231                if(compare(this[mid]) <= 0) lower = mid;
1232                else upper = mid;
1233        }
1234        return lower+1;
1235}
1236
1237//// livedoor Reader Fastladder only
1238if(/^http:\/\/(?:reader\.livedoor|fastladder)\.com\/(?:reader|public)\//.test(window.location.href) &&
1239        typeof unsafeWindow != "undefined"){
1240        var w = unsafeWindow;
1241        var _onload = w.onload;
1242        w.onload = function(){
1243                _onload();
1244                [
1245                        { name: 'pinned-or-current-link',
1246                          command: function(){
1247                                  if(w.pin.pins.length){
1248                                          return w.pin.pins.map(function(e){return e.url});
1249                                  }
1250                                  var item = w.get_active_item(true);
1251                                  if(item) return [item.link];
1252                          }
1253                        },
1254                        { name: 'pinned-link',
1255                          command: function(){return w.pin.pins.map(function(e){return e.url})}
1256                        },
1257                        { name: 'current-link',
1258                          command: function(){
1259                                  var item = w.get_active_item(true);
1260                                  if(item) return [item.link];
1261                          }
1262                        },
1263                        { name: 'clear-pin',
1264                          command: function(stdin){w.Control.clear_pin(); return stdin}},
1265                        { name: 'toggle-show-all',
1266                          command: function(stdin){w.Control.toggle_show_all(); return stdin}}
1267                ].forEach(window.Minibuffer.addCommand);
1268        }
1269}
1270
1271
1272if(document.body){
1273        var ldrize = function(siteinfo){new LDRize(siteinfo)}
1274
1275        var parser = function(response){
1276                var result = JSON.parse(response.responseText).map(function(o){
1277                        var res = o.data;
1278                        res.name = o.name;
1279                        return res;
1280                });
1281                return result;
1282        }
1283        new SiteinfoOperator({
1284          name:        'LDRize',
1285          version:     SCRIPT_VERSION,
1286          urls:        SITEINFO_URLS,
1287          siteinfo:    SITEINFO,
1288          initializer: ldrize,
1289          parser:      parser
1290        });
1291}
1292};
1293
1294if(window.Minibuffer){
1295        boot();
1296}else{
1297        window.addEventListener('GM_MinibufferLoaded', boot, false);
1298}
Note: See TracBrowser for help on using the browser.