diff options
-rw-r--r-- | nextlink.js | 443 |
1 files changed, 209 insertions, 234 deletions
diff --git a/nextlink.js b/nextlink.js index d2184a3..5947997 100644 --- a/nextlink.js +++ b/nextlink.js @@ -11,7 +11,8 @@ var PLUGIN_INFO = <description>mapping "[[", "]]" by AutoPagerize XPath.</description> <description lang="ja">AutoPagerize 用の XPath より "[[", "]]" をマッピングします。</description> <author mail="suvene@zeromemory.info" homepage="http://zeromemory.sblo.jp/">suVene</author> - <version>0.2.10</version> + <author mail="konbu.komuro@gmail.com" homepage="http://d.hatena.ne.jp/hogelog/">hogelog</author> + <version>0.3.0</version> <license>MIT</license> <minVersion>1.2</minVersion> <maxVersion>2.0pre</maxVersion> @@ -31,6 +32,10 @@ var PLUGIN_INFO = :nextlink: autocmd によって呼び出されます。 +== TODO == +- 同一URLのページを複数開いている場合のバグフィックス +- Autopager利用時のMICROFORMATの対応 + ]]></detail> </VimperatorPlugin>; //}}} @@ -46,6 +51,7 @@ 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'; var isFollowLink = typeof liberator.globalVariables.nextlink_followlink == 'undefined' ? false : $U.eval(liberator.globalVariables.nextlink_followlink); @@ -79,7 +85,6 @@ NextLink.prototype = { initialize: function(pager) { this.initialized = false; - this.isCurOriginalMap = true; this.siteinfo = []; this.cache = {}; // {url: {xpath: xpath, next: element, prev: url}} or null this.pager = pager; @@ -106,76 +111,36 @@ NextLink.prototype = { } ]; */ - - commands.addUserCommand(['nextlink'], 'map ]] by AutoPagerize XPath.', - $U.bind(this, function(args) { this.handler(args); }), null, true - ); - var loadEvent = autocommands['DOMLoad'] || 'PageLoad'; // for 1.2 - liberator.execute(':autocmd ' + (this.is2_0later ? 'DOMLoad' : 'PageLoad') + ' .* :nextlink onLoad'); - liberator.execute(':autocmd LocationChange .* :nextlink onLocationChange'); + ; + + getBrowser().addEventListener("DOMContentLoaded", + $U.bind(this, function(event) { + let win = (event.target.contentDocument||event.target).defaultView; + this.onLoad(win); + }), false); + this.customizeMap(this); }, - handler: function(args) { - event = args.string || args; - this[event](buffer.URL); - }, - onLoad: function(url) { + onLoad: function(win) { if (!this.initialized) return; - if (this.cache[url] && - this.cache[url].hasOwnProperty('xpath')) { - this.cache[url].doc = window.content.document; - this.onLocationChange(url, true); - return; - } + let doc = win.document; + let 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?://.') { - window.content.addEventListener('unload', $U.bind(this, - function() { this.cache[url] = null; }), false); + win.addEventListener('unload', + $U.bind(this, function() { + this.cache[url] = null; + }), false); this.setCache(url, - ['doc', 'xpath', 'siteinfo'], - [window.content.document, this.siteinfo[i].nextLink, this.siteinfo[i]] + ['xpath', 'siteinfo'], + [this.siteinfo[i].nextLink, this.siteinfo[i]] ); - this.onLocationChange(url, true); - return; } } - this.setCache(url, ['doc', 'xpath', 'prev', 'next'], [null, null, null, null]); - }, - onLocationChange: function(url, isCallerLoaded) { - - if (!this.initialized || - !this.cache[url] || - !this.cache[url].hasOwnProperty('xpath')) return; - - if (this.cache[url]['xpath'] == null) { - this.restorOrginalMap(); - return; - } - - this.pager.onLocationChange(this, url, isCallerLoaded); - this.customizeMap(this, url); - this.isCurOriginalMap = false; + return this.cache[url]; }, - customizeMap: function(context, url) { - - var cache = this.cache[url]; - var prev = cache.prev; - var next = cache.next; - - if (!prev) - this.removeMap('[['); - - if (!next) - this.removeMap(']]'); - - this.pager.customizeMap(context, url, prev, next); - }, - restorOrginalMap: function() { - - if (this.isCurOriginalMap) return; - this.removeMap('[['); - this.removeMap(']]'); - this.isCurOriginalMap = true; + nextLink: function(count) { + this.pager.nextLink(this, count); }, setCache: function(key, subKeys, values) { if (!this.cache[key]) this.cache[key] = {}; @@ -183,152 +148,122 @@ NextLink.prototype = { [].concat(subKeys).forEach($U.bind(this, function(subKey, i) { this.cache[key][subKey] = values[i]; })); + return this.cache[key]; + }, + customizeMap: function(context) { + mappings.addUserMap(context.browserModes, ['[['], 'customize by nextlink.js', + function(count) context.nextLink(count>0 ? -1*count : -1), + { flags: Mappings.flags.COUNT }); + + mappings.addUserMap(context.browserModes, [']]'], 'customize by nextlink.js', + function(count) context.nextLink(count>0 ? count : 1), + { flags: Mappings.flags.COUNT }); }, - removeMap: function(cmd) { - try { - if (mappings.hasMap(this.browserModes, cmd)) { - mappings.remove(this.browserModes, cmd); - } - return true; - } catch (e) { - return false; - } - } };//}}} var Autopager = function() {};//{{{ Autopager.prototype = { - onLocationChange: function(context, url, isCallerLoaded) { - - var cache = context.cache[url]; - var doc = cache.doc; - var elems = cache.next; - var elem; - - if (isCallerLoaded) { - let insertPoint, lastPageElement; - - if (cache.insertBefore) - insertPoint = $U.getNodesFromXPath(cache.siteinfo.insertBefore, doc); - - if (!insertPoint) - lastPageElement = $U.getNodesFromXPath(cache.siteinfo.pageElement, doc).pop(); - if (lastPageElement) - insertPoint = lastPageElement.nextSibling || - lastPageElement.parentNode.appendChild(doc.createTextNode(' ')); - - 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(context, count) { + let self = this; + let win = window.content; + let doc = win.document; + let url = doc.location.href; + let cache = context.cache[url] || context.onLoad(win); + + // TODO: support MICROFORMAT + // rel="next", rel="prev" + + // no siteinfo, defalut [[, ]] action + if(!cache) { + if(count < 0) { + buffer.followDocumentRelationship("previous"); + } else { + buffer.followDocumentRelationship("next"); } + return; + } - $U.getNodesFromXPath(cache.xpath, doc, function(item) elem = item, this); - - context.setCache(url, - ['prev', 'next', 'curPage', 'insertPoint', 'terminate', 'lastReqUrl', 'loadedURLs', 'mark'], - [[], [elem], 1, insertPoint, 0, null, {}, []] - ); - cache.loadedURLs[url] = true; + 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); } - }, - customizeMap: function(context, url, prev, next) { - var cache = context.cache[url]; - var doc = cache.doc; + let page = this.getCurrentPage(win, doc) + count; + if (page <= 1) { + win.scrollTo(0, 0); + return true; + } + if(this.focusPagenavi(win, doc, page)) { + return true; + } - mappings.addUserMap(context.browserModes, ['[['], 'customize by nextlink.js', - $U.bind(this, function(count) { - if (cache.curPage == 1) { - return; - } else if (--cache.curPage == 1) { - window.content.scrollTo(0, 0); - } else { - this.focusPagenavi(context, url, cache.curPage); - } - }), - { flags: Mappings.flags.COUNT }); + if (cache.isLoading) { + logger.echo('loading now...'); + return; + } - mappings.addUserMap(context.browserModes, [']]'], 'customize by nextlink.js', - $U.bind(this, function(count) { - var reqUrl, lastReqUrl; - reqUrl = $U.pathToURL(cache.next[cache.curPage - 1], url, doc); - lastReqUrl = cache.lastReqUrl; - - if (cache.isLoading) { - logger.echo('loading now...'); - return; - } + let next = cache.next; + if(!next) + [next] = $U.getNodesFromXPath(cache.xpath, doc); + if(!next) { + logger.echo('end of pages.'); + return; + } - if (!reqUrl || cache.curPage == cache.terminate) { - logger.echo('end of pages.'); - return; - } - if (cache.loadedURLs[reqUrl]) { - this.focusPagenavi(context, url, ++cache.curPage); - return; - } - context.setCache(url, ['lastReqUrl', 'isLoading'], [reqUrl, true]); - var req = new libly.Request( - reqUrl, null, - { asynchronous: true, encoding: doc.characterSet, - context: context, url: url } - ); - 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(); - }), - { flags: Mappings.flags.COUNT } - ); + let reqUrl = $U.pathToURL(next, url, doc); + cache.isLoading = true; + var req = new libly.Request( + reqUrl, null, + { asynchronous: true, encoding: doc.characterSet, + context: context, url: url } + ); + + req.addEventListener('onSuccess', function(res) self.onSuccess(win, doc, res)); + req.addEventListener('onFailure', function(res) self.onFailure(win, doc, res)); + req.addEventListener('onException', function(res) self.onFailure(win, doc, res)); + req.get(); }, - onSuccess: function(res) { - + onSuccess: function(win, doc, res) { var context = res.req.options.context; var url = res.req.options.url; var cache = context.cache[url]; - var doc = cache.doc; var page = res.getHTMLDocument(cache.siteinfo.pageElement); - var htmlDoc = res.doc; - var prev = cache.next[cache.curPage]; - var next = $U.getNodesFromXPath(cache.xpath, htmlDoc); + var resDoc = res.doc; + var [next] = $U.getNodesFromXPath(cache.xpath, resDoc); + cache.next = next; cache.isLoading = false; - cache.loadedURLs[res.req.url] = true; if (!page || page.length < 1) page = res.getHTMLDocument('//*[contains(@class, "autopagerize_page_element")]'); if (!page || page.length < 1) { - context.setCache(url, 'terminate', cache.curPage); return; } - cache.curPage++; - if (next && next.length) { - cache.prev.push(prev); - cache.next.push(next[0]); - } else { - context.setCache(url, 'terminate', cache.curPage); - } - - this.addPage(context, htmlDoc, url, page, res.req.url); - this.focusPagenavi(context, url, cache.curPage); + let addPage = this.getPageNum()+1; + this.addPage(context, doc, resDoc, page, res.req.url, addPage); + this.focusPagenavi(win, doc, addPage); + context.setCache(doc, 'isLoading', false); }, - addPage: function(context, doc, url, page, reqUrl) { - - var cache = context.cache[url]; - var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; - //var hr = doc.createElementNS(HTML_NAMESPACE, 'hr'); - var p = doc.createElementNS(HTML_NAMESPACE, 'p'); + addPage: function(context, doc, resDoc, page, reqUrl, addPage) { + let url = doc.location.href; + let cache = context.cache[url]; + if(!cache.insertPoint) + cache.insertPoint = this.getInsertPoint(doc, cache.siteinfo); + let insertPoint = cache.insertPoint; + + let p = doc.createElementNS(HTML_NAMESPACE, 'p'); var tagName; + if (page[0] && page[0].tagName) tagName = page[0].tagName.toLowerCase(); if (tagName == 'tr') { - let insertParent = cache.insertPoint.parentNode; + let insertParent = insertPoint.parentNode; let colNodes = getElementsByXPath('child::tr[1]/child::*[self::td or self::th]', insertParent); let colums = 0; @@ -337,35 +272,32 @@ Autopager.prototype = { colums += parseInt(col, 10) || 1; } let td = doc.createElement('td'); - // td.appendChild(hr); td.appendChild(p); let tr = doc.createElement('tr'); td.setAttribute('colspan', colums); tr.appendChild(td); - insertParent.insertBefore(tr, cache.insertPoint); + insertParent.insertBefore(tr, insertPoint); } else if (tagName == 'li') { let li = doc.createElementNS(HTML_NAMESPACE, 'li'); - cache.insertPoint.parentNode.insertBefore(li, cache.insertPoint); + insertPoint.parentNode.insertBefore(li, insertPoint); li.appendChild(p); } else { - //cache.insertPoint.parentNode.insertBefore(hr, cache.insertPoint); - cache.insertPoint.parentNode.insertBefore(p, cache.insertPoint); + insertPoint.parentNode.insertBefore(p, insertPoint); } - p.id = 'vimperator-nextlink-' + cache.curPage; - p.innerHTML = 'page: <a href="' + reqUrl + '">' + cache.curPage + '</a>'; + p.id = 'vimperator-nextlink-' + addPage; + p.innerHTML = 'page: <a href="' + reqUrl + '">' + addPage + '</a>'; p.className = 'vimperator-nextlink-page'; - cache.mark.push(p); $H.addURI(makeURI(reqUrl), false, true, makeURI(url)); return page.map(function(elem) { - var pe = doc.importNode(elem, true); - cache.insertPoint.parentNode.insertBefore(pe, cache.insertPoint); + var pe = resDoc.importNode(elem, true); + insertPoint.parentNode.insertBefore(pe, insertPoint); return pe; }); }, - onFailure: function(res) { + onFailure: function(win, doc, res) { logger.log('onFailure'); var context = res.req.options.context; var url = res.req.options.url; @@ -374,61 +306,104 @@ Autopager.prototype = { logger.echoerr('nextlink: loading failed. ' + '[' + res.status + ']' + res.statusText + ' > ' + res.req.url); res.req.options.context.setCache(res.req.options.url, 'terminate', cache.curPage); }, - focusPagenavi: function(context, url, page) { - var elem, p; - try { - elem = context.cache[url].mark[page - 2]; - p = $U.getElementPosition(elem); - window.content.scrollTo(0, p.top); - } catch (e) { - logger.log('focusPagenavi: err ' + page + ' ' + e); - } - } + focusPagenavi: function(win, doc, page) { + let xpath = '//*[@id="vimperator-nextlink-' + page + '"]'; + let [elem] = $U.getNodesFromXPath(xpath, doc); + if(elem) { + let p = $U.getElementPosition(elem); + win.scrollTo(0, p.top); + return true; + } + return false; + }, + getPageNum: function(doc) { + let xpath = '//*[@class="vimperator-nextlink-page"]'; + let page = 1 + $U.getNodesFromXPath(xpath, doc).length; + return page; + }, + getCurrentPage: function(win, doc) { + let page = 1; + let xpath = '//*[@class="vimperator-nextlink-page"]'; + let makers = $U.getNodesFromXPath(xpath, doc); + let curPos = win.scrollY; + for(let i=0;i<makers.length;++i) { + let p = $U.getElementPosition(makers[i]); + if(curPos < p.top) break; + ++page; + } + return page; + }, + getInsertPoint: function(doc, siteinfo) { + let 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; + }, + setValue: function(doc, id, value) { + let realID = 'vimperator-nextlink-value-'+id; + let [input] = doc.getElementById(realID); + if(!input) { + input = doc.createElementNS(HTML_NAMESPACE, 'input'); + } + input.value = value; + }, + getValue: function(doc, id) { + let realID = 'vimperator-nextlink-value-'+id; + let [input] = doc.getElementById(realID); + if(!input) return false; + return input.value; + }, };//}}} var FollowLink = function() {};//{{{ FollowLink.prototype = { - onLocationChange: function(context, url, isCallerLoaded) { + nextLink: function(context, count) { + let win = window.content; + let doc = win.document; + let url = doc.location.href; + function followXPath(xpath) { + let [elem] = $U.getNodesFromXPath(xpath, doc); + if(elem) { + if (elem.tagName == 'LINK') { + liberator.open(elem.href); + } else { + buffer.followLink(elem, liberator.CURRENT_TAB); + } + return true; + } + return false; + } - var cache = context.cache[url]; - var doc = cache.doc; - var elem; - - //var matches = buffer.evaluateXPath(this.cache[url]); - //for each (let match in matches) elem = match; - $U.getNodesFromXPath(cache.xpath, doc, function(item) elem = item, this); - - var nextURL = $U.pathToURL(elem, doc); - var xpath = ['a', 'link'].map(function(e) - '//' + e + '[translate(normalize-space(@rel), "PREV", "prev")="prev"]') - .join(' | '); - var prev = $U.getNodesFromXPath(xpath, doc); - if (prev.length) - context.setCache(url, 'prev', prev[0]); - context.setCache(nextURL, 'prev', url); - context.setCache(url, 'next', elem); - }, - customizeMap: function(context, url, prev, next) { + if(count < 0) { + let xpath = ['link', 'a'].map(function(e) + '//' + e + '[translate(normalize-space(@rel), "PREV", "prev")="prev"]') + .join(' | '); - var cache = context.cache[url]; - var doc = cache.doc; - - if (prev) - mappings.addUserMap(context.browserModes, ['[['], 'customize by nextlink.js', - function(count) { - if (prev.href) { - buffer.followLink(prev, liberator.CURRENT_TAB); - } else { - liberator.open(prev, liberator.CURRENT_TAB); - } - }, - { flags: Mappings.flags.COUNT }); - - if (next) - mappings.addUserMap(context.browserModes, [']]'], 'customize by nextlink.js', - function(count) { buffer.followLink(next, liberator.CURRENT_TAB); }, - { flags: Mappings.flags.COUNT }); - } + 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; + + let cache = context.cache[url] || context.onLoad(win); + if(cache) { + if(followXPath(cache.xpath)) return; + } + buffer.followDocumentRelationship("next"); + } + }, };//}}} var instance = new NextLink((isFollowLink ? new FollowLink() : new Autopager())); |