aboutsummaryrefslogtreecommitdiffstats
path: root/_smooziee.js
blob: c00035a9c28c9a43642deb7cb1237c2b33b62d19 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//
// _smooziee.js
//
// LICENSE: {{{
//   Copyright (c) 2009 snaka<snaka.gml@gmail.com>
//
//     distributable under the terms of an MIT-style license.
//     http://www.opensource.jp/licenses/mit-license.html
// }}}
//
// PLUGIN INFO: {{{
var PLUGIN_INFO =
<VimperatorPlugin>
  <name>smooziee</name>
  <description>At j,k key scrolling to be smooth.</description>
  <description lang="ja">j,kキーでのスクロールをスムースに</description>
  <minVersion>2.3pre</minVersion>
  <maxVersion>2.3pre</maxVersion>
  <updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/_smooziee.js</updateURL>
  <author mail="snaka.gml@gmail.com" homepage="http://vimperator.g.hatena.ne.jp/snaka72/">snaka</author>
  <license>MIT style license</license>
  <version>0.9.3</version>
  <detail><![CDATA[
    == Subject ==
    j,k key scrolling to be smoothly.

    == Global variables ==
    You can configure following variable as you like.
    :smooziee_scroll_amount: Scrolling amount(unit:px). Default value is 400px.
    :smooziee_interval: Scrolling interval(unit:ms). Default value is 20ms.

    === Excample ===
    Set scroll amount is 300px and interval is 10ms.
    >||
    let g:smooziee_scroll_amount="300"
    let g:smooziee_scroll_interval="10"
    ||<

    == API ==
    >||
    smooziee.smoothScrollBy(amount);
    ||<
    Example.
    >||
    :js liberator.plugins.smooziee.smoothScrollBy(600)
    :js liberator.plugins.smooziee.smoothScrollBy(-600)
    ||<

    == ToDo ==

  ]]></detail>

  <detail lang="ja"><![CDATA[
    == 概要 ==
    普段のj,kキーのスクロールをLDRizeライクにスムースにします

    == グローバル変数 ==
    以下の変数を.vimperatorrcなどで設定することで動作を調整することができます
    :smooziee_scroll_amount:
      1回にスクロールする幅です単位ピクセル)。デフォルトは"400"です
    :smooziee_interval:
      スクロール時のアニメーションのインターバルです単位ミリ秒)。
      "1"以上の値を設定しますデフォルトは"20"です
    === 設定例 ===
    スクロール量を300pxにインターバルを10msに設定します
    >||
    let g:smooziee_scroll_amount="300"
    let g:smooziee_scroll_interval="10"
    ||<

    == API ==
    他のキーにマップする場合やスクリプトから呼び出せるようAPIを用意してます
    >||
    smooziee.smoothScrollBy(amount);
    ||<
    amountにはスクロール量(ピクセル)を指定してください正の値で下方向へ負の値で上方向へスクロールします

    Example.
    >||
    :js liberator.plugins.smooziee.smoothScrollBy(600)
    :js liberator.plugins.smooziee.smoothScrollBy(-600)
    ||<

    == ToDo ==
    - 読み込みの順番によっては他のプラグインと競合する可能性があるのをなんとかしたい

  ]]></detail>
</VimperatorPlugin>;
// }}}

let self = liberator.plugins.smooziee = (function(){

  // Mappings  {{{
  mappings.addUserMap(
    [modes.NORMAL],
    ["j"],
    "Smooth scroll down",
    function(){
      self.smoothScrollBy(getScrollAmount());
    }
  );
  mappings.addUserMap(
    [modes.NORMAL],
    ["k"],
    "Smooth scroll up",
    function(){
      self.smoothScrollBy(getScrollAmount() * -1);
    }
  );
  // }}}
  // PUBLIC {{{
  var PUBLICS = {
    smoothScrollBy: function(moment) {
      win = Buffer.findScrollableWindow();
      interval = window.eval(liberator.globalVariables.smooziee_scroll_interval) || 20;
      destY = win.scrollY + moment;
      clearTimeout(next);
      smoothScroll(moment);
    }
  }

  // }}}
  // PRIVATE {{{
  var next;
  var destY;
  var win;
  var interval;

  function getScrollAmount() window.eval(liberator.globalVariables.smooziee_scroll_amount) || 400;

  function smoothScroll(moment) {
    if (moment > 0)
      moment = Math.floor(moment / 2);
    else
      moment = Math.ceil(moment / 2);

    win.scrollBy(win.scrollX, moment);

    if (Math.abs(moment) < 1) {
      setTimeout(makeScrollTo(win.scrollX, destY), interval);
      destY = null;
      return;
    }
    next = setTimeout(function() smoothScroll(moment), interval);
  }

  function makeScrollTo(x, y) function() win.scrollTo(x, y);
  // }}}
  return PUBLICS;
})();
// vim: sw=2 ts=2 et si fdm=marker:
span>function() { // initialize //{{{ if (!liberator.plugins.libly) { liberator.log("nextlink: needs _libly.js"); return; } var libly = liberator.plugins.libly; var $U = libly.$U; var logger = $U.getLogger("nextlink"); var $H = Cc["@mozilla.org/browser/global-history;2"].getService(Ci.nsIGlobalHistory2); const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; const UUID = "{3b72c049-a347-4777-96f6-b128fc76ed6a}"; // siteinfo cache key const DEFAULT_PREVMAP = "[["; const DEFAULT_NEXTMAP = "]]"; var prevMap = liberator.globalVariables.nextlink_prevmap || DEFAULT_PREVMAP; var nextMap = liberator.globalVariables.nextlink_nextmap || DEFAULT_NEXTMAP; var isFollowLink = typeof liberator.globalVariables.nextlink_followlink == "undefined" ? false : $U.eval(liberator.globalVariables.nextlink_followlink); const MICROFORMAT = { url: "^https?://.", nextLink: '//a[translate(normalize-space(@rel), "NEXT", "next")="next"] | //link[translate(normalize-space(@rel), "NEXT", "next")="next"]', insertBefore: '//*[contains(concat(" ", @class, " "), " autopagerize_insert_before ")]', pageElement: '//*[contains(concat(" ", @class, " "), " autopagerize_page_element ")]', } const nositeinfoActions = { // vimperator [[, ]] action f: function(doc, count) { if (count < 0) { return buffer.followDocumentRelationship("previous"); } return buffer.followDocumentRelationship("next"); }, e: function(doc, count) { var url = doc.location.href; return liberator.echo("No SITEINFO match " + url); }, n: function() true, }; var actpattern = liberator.globalVariables.nextlink_nositeinfo_act || "e"; var nositeinfoAct = nositeinfoActions[actpattern]; var localSiteinfo = storage.newMap("nextlink-local-siteinfo", false); if (localSiteinfo) localSiteinfo = [ info for ([ i, info ] in localSiteinfo) ]; var pageNaviCss = <style type="text/css"><![CDATA[ .vimperator-nextlink-page { background-color: #555; color: #fff; opacity: .85; padding: 10px; margin-top: 5px; margin-bottom: 5px; height: 1em; font-weight: bold; text-align: left; -moz-border-radius: 5px; } .vimperator-nextlink-page a:link { color: #EF6D29; } .vimperator-nextlink-page a:visited { color: #A50000; } ]]></style>; //}}} var NextLink = function() {//{{{ this.initialize.apply(this, arguments); }; NextLink.prototype = { initialize: function(pager) { this.initialized = false; this.siteinfo = []; this.pager = pager; this.browserModes = config.browserModes || [ modes.NORMAL, modes.VISUAL ]; this.is2_0later = config.autocommands.some(function ([ k, v ]) k == "DOMLoad"); // toriaezu var wedata = new libly.Wedata("AutoPagerize"); wedata.getItems(24 * 60 * 60 * 1000, null, $U.bind(this, function(isSuccess, data) { if (!isSuccess) return; this.siteinfo = data.map(function(item) item.data); }) ); if (localSiteinfo) this.siteinfo = this.siteinfo.concat(localSiteinfo); this.siteinfo = this.siteinfo.sort(function(a, b) b.url.length - a.url.length); // sort url.length desc this.initialized = true; this.customizeMap(this); }, initDoc: function(context, doc) { var value = doc[UUID] = {}; value.siteinfo = this.getSiteinfo(doc); this.pager.initDoc(context, doc); }, getSiteinfo: function(doc) { function valid(prop) $U.getNodesFromXPath(MICROFORMAT[prop], doc).length > 0; if (valid("nextLink") && valid("pageElement")) return MICROFORMAT; var url = doc.location.href; for (let i = 0, len = this.siteinfo.length; i < len; i++) { if (url.match(this.siteinfo[i].url) && this.siteinfo[i].url != "^https?://.") { return this.siteinfo[i]; } } return null; }, nextLink: function(count) { if (!this.initialized) { liberator.echo("before initialized."); return false; } var doc = window.content.document; if (!doc[UUID]) this.initDoc(this, doc); this.pager.nextLink(doc, count); }, customizeMap: function(context) { mappings.addUserMap(context.browserModes, [ prevMap ], "customize by nextlink.js", function(count) context.nextLink(count > 0 ? -1 * count : -1), { count: true }); mappings.addUserMap(context.browserModes, [ nextMap ], "customize by nextlink.js", function(count) context.nextLink(count > 0 ? count : 1), { count: true }); }, };//}}} var Autopager = function() {};//{{{ Autopager.prototype = { initDoc: function(context, doc) { doc[UUID].loadURLs = []; if (context.is2_0later) { let css = $U.xmlToDom(pageNaviCss, doc); let node = doc.importNode(css, true); doc.body.insertBefore(node, doc.body.firstChild); //doc.body.appendChild(css); } }, nextLink: function(doc, count) { var value = doc[UUID]; // TODO: support MICROFORMAT // rel="next", rel="prev" if (!value.siteinfo && nositeinfoAct) { return nositeinfoAct(doc, count); } var curPage = this.getCurrentPage(doc); logger.log(curPage); var page = (count < 0 ? Math.round : Math.floor)(curPage + count); if (page <= 1) { value.isLoading = false; doc.defaultView.scrollTo(0, 0); return true; } if (this.focusPagenavi(doc, page)) { value.isLoading = false; return true; } if (value.isLoading) { logger.echo("loading now..."); return false; } value.isLoading = true; if (value.terminate) { value.isLoading = false; logger.echo("terminated."); return false; } var req = this.createNextRequest(doc); if (!req) { value.isLoading = false; let win = doc.defaultView; win.scrollTo(0, win.scrollMaxY); logger.echo("end of pages."); return true; } req.addEventListener("onSuccess", $U.bind(this, this.onSuccess)); req.addEventListener("onFailure", $U.bind(this, this.onFailure)); req.addEventListener("onException", $U.bind(this, this.onFailure)); req.get(); }, onSuccess: function(res) { var doc = res.req.options.doc; var url = doc.location.href; var value = doc[UUID]; var pages = res.getHTMLDocument(value.siteinfo.pageElement) var resDoc = res.doc; var reqUrl = res.req.url; var [ next ] = $U.getNodesFromXPath(value.siteinfo.nextLink, resDoc); value.loadURLs.push(reqUrl); value.next = next; value.isLoading = false; // set reqUrl link-state visited $H.addURI(makeURI(reqUrl), false, true, makeURI(url)); if (!pages || pages.length == 0) return; var addPageNum = this.getPageNum(doc) + 1; this.addPage(doc, resDoc, pages, reqUrl, addPageNum); this.focusPagenavi(doc, addPageNum); }, addPage: function(doc, resDoc, pages, reqUrl, addPageNum) { var url = doc.location.href; var value = doc[UUID]; if (!value.insertPoint) value.insertPoint = this.getInsertPoint(doc, value.siteinfo); var insertPoint = value.insertPoint; this.insertRule(doc, addPageNum, reqUrl, pages[0], insertPoint); pages.forEach(function(elem) { var pe = resDoc.importNode(elem, true); insertPoint.parentNode.insertBefore(pe, insertPoint); }); return true; }, onFailure: function(res) { logger.log("onFailure"); var doc = res.req.options.doc; var url = doc.location.href; var value = doc[UUID]; value.isLoading = false; value.terminate = true; logger.echoerr("nextlink: loading failed. " + "[" + res.status + "]" + res.statusText + " > " + res.req.url); }, focusPagenavi: function(doc, page) { var xpath = '//*[@id="vimperator-nextlink-' + page + '"]'; var [ elem ] = $U.getNodesFromXPath(xpath, doc); var win = doc.defaultView; if (elem) { let p = $U.getElementPosition(elem); win.scrollTo(0, p.top); return true; } return false; }, createNextRequest: function(doc) { var value = doc[UUID]; var url = doc.location.href; var next = value.next; if (!next) [ next ] = $U.getNodesFromXPath(value.siteinfo.nextLink, doc); if (!next) return false; var reqUrl = $U.pathToURL(next, url, doc); if (value.loadURLs.some(function(url) url == reqUrl)) return false; var req = new libly.Request( reqUrl, null, { asynchronous: true, encoding: doc.characterSet, doc: doc } ); return req; }, getPageNum: function(doc) { var xpath = '//*[@class="vimperator-nextlink-page"]'; var page = 1 + $U.getNodesFromXPath(xpath, doc).length; return page; }, getCurrentPage: function(doc) { var xpath = '//*[@class="vimperator-nextlink-page"]'; var markers = $U.getNodesFromXPath(xpath, doc); var win = doc.defaultView; var curPos = win.scrollY; // top of page if (curPos <= 0) return 1.0; // bottom of page if (curPos >= win.scrollMaxY) { if (markers.length > 0) { let lastMarker = $U.getElementPosition(markers[markers.length-1]).top; if (curPos <= lastMarker) return markers.length + 1; } return markers.length + 1.5; } // return n.5 if between n and n+1 var page = 1.0; for (let i = 0, len = markers.length; i < len; i++) { let pos = $U.getElementPosition(markers[i]).top; if (curPos == pos) return page + 1; if (curPos < pos) return page + 0.5; ++page; } return page + 0.5; }, getInsertPoint: function(doc, siteinfo) { var insertPoint, lastPageElement; if (siteinfo.insertBefore) [ insertPoint ] = $U.getNodesFromXPath(siteinfo.insertBefore, doc); if (!insertPoint) { let elems = $U.getNodesFromXPath(siteinfo.pageElement, doc); if (elems.length > 0) lastPageElement = elems.pop(); } if (lastPageElement) insertPoint = lastPageElement.nextSibling || lastPageElement.parentNode.appendChild(doc.createTextNode(" ")); return insertPoint; }, insertRule: function(doc, addPageNum, reqUrl, page, insertPoint) { var p = doc.createElementNS(HTML_NAMESPACE, "p"); p.id = "vimperator-nextlink-" + addPageNum; p.innerHTML = 'page: <a href="' + reqUrl + '">' + addPageNum + "</a>"; p.className = "vimperator-nextlink-page"; var tagName; if (page && page.tagName) tagName = page.tagName.toLowerCase(); if (tagName == "tr") { let insertParent = insertPoint.parentNode; let colNodes = getElementsByXPath("child::tr[1]/child::*[self::td or self::th]", insertParent); let colums = 0; for (let i = 0, l = colNodes.length, f = function(col) { colums += parseInt(col, 10) || 1; }; i < l; f(colNodes[i++].getAttribute("colspan"))); let td = insertParent.insertBefore(doc.createElement("tr"), insertPoint) .appendChild(doc.createElement("td")); td.setAttribute("colspan", colums); td.appendChild(p); } else if (tagName == "li") { insertPoint.parentNode.insertBefore(doc.createElementNS(HTML_NAMESPACE, "li"), insertPoint) .appendChild(p); } else { insertPoint.parentNode.insertBefore(p, insertPoint); } }, }; //}}} var FollowLink = function() {};//{{{ FollowLink.prototype = { initDoc: function(context, doc) { }, nextLink: function(doc, count) { var url = doc.location.href; var value = doc[UUID]; function followXPath(xpath) { var [ elem ] = $U.getNodesFromXPath(xpath, doc); if (elem) { let tagName = elem.tagName.toLowerCase(); if (tagName == "link") { liberator.open(elem.href); } else { buffer.followLink(elem, liberator.CURRENT_TAB); } return true; } return false; } if (count < 0) { let xpath = [ "link", "a" ].map(function(e) "//" + e + '[translate(normalize-space(@rel), "PREV", "prev")="prev"]') .join(" | "); if (followXPath(xpath)) return; buffer.followDocumentRelationship("previous"); } else { let xpath = [ "link", "a" ].map(function(e) "//" + e + '[translate(normalize-space(@rel), "NEXT", "next")="next"]') .join(" | "); if (followXPath(xpath)) return; if (value.siteinfo && followXPath(value.siteinfo.nextLink)) return; buffer.followDocumentRelationship("next"); } } }; //}}} var instance = new NextLink((isFollowLink ? new FollowLink() : new Autopager())); return instance; })(); // vim: set fdm=marker sw=2 ts=2 sts=0 et: