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

Revision 36242, 46.8 kB (checked in by whym, 7 weeks ago)

* add 'all-node' command.
* fix set-pin and unset-pin for correctly displaying/hiding pins

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