diff options
author | anekos | 2008-07-29 13:24:50 +0000 |
---|---|---|
committer | anekos | 2008-07-29 13:24:50 +0000 |
commit | c0a00686c2224c53f3880e9beb292b8c5f11489a (patch) | |
tree | 98d8d1a3575965c63d70609db7a85739331ff40a /auto_detect_link.js | |
parent | 64c5018ffc3278b60440aa68b8eebe89dea9af8d (diff) | |
download | vimperator-plugins-c0a00686c2224c53f3880e9beb292b8c5f11489a.tar.bz2 |
added: 自動リンク検出&移動プラグイン ([[/]] の自分的改良版)
git-svn-id: http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk@16811 d0d07461-0603-4401-acd4-de1884942a52
Diffstat (limited to 'auto_detect_link.js')
-rw-r--r-- | auto_detect_link.js | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/auto_detect_link.js b/auto_detect_link.js new file mode 100644 index 0000000..70ad6e3 --- /dev/null +++ b/auto_detect_link.js @@ -0,0 +1,402 @@ +// ==VimperatorPlugin== +// @name Auto Detect Link +// @description-ja (次|前)っぽいページへのリンクを探してジャンプ +// @license Creative Commons 2.1 (Attribution + Share Alike) +// @version 1.0 +// ==/VimperatorPlugin== +// +// Usage: +// デフォルトの設定では、"]]" "[[" を上書きします。 +// ]] 次っぽいページへ +// [[ 前っぽいページへ +// +// Setting: +// liberator.globalVariables.autoDetectLink +// nextPatterns: +// backPatterns: +// (次|前)のパターンの配列。 +// 要素は、 +// ・リンク文字列に対する(正規表現|文字列) +// ・リンクに対する関数のリスト +// nextMappings: +// backPatterns: +// (次|前)移動のマッピング(Array) +// useNextHistory: +// useBackHistory: +// 履歴を併用。 +// 履歴がある場合はそっちを優先します。 +// useSuccPattern: +// doc_01.html のときは、 doc_02.html を次と見なす…ようなパターン。 +// ファイル名に当たる部分の、数字列あるいは一文字のアルファベットが対象です。 +// (つながっているアルファベットは無視されます。) +// doc_02.html => doc_03.html +// doc_a.html => doc_b.html +// force: +// (次|前)っぽいURIを捏造してそこに移動します。 +// +// Function: +// (次|前)へのリンクを検出する。 +// liberator.plugins.autoDetectLink.detect(next, setting) +// next: 次のリンクを探すときは、true。 +// setting: 設定を一時的に上書きする。省略可。 +// return: リンクのURIなどを含んだオブジェクト +// uri: アドレス。 +// text: リンクテキストなど。 +// frame: リンクの存在するフレームの Window オブジェクト。 +// +// (次|前)へのリンクに移動。 +// liberator.plugins.autoDetectLink.go(next, setting) +// 引数は detect と同じ。 +// +// example: +// 履歴を使用しないで、前のリンクを探す。 +// liberator.plugins.autoDetectLink.detect(false, {useBackHistory: false}); +// +// Note: +// 単純なリンクと、フォームのボタンを検出できます。 +// +// License: +// http://creativecommons.org/licenses/by-sa/2.1/jp/ +// http://creativecommons.org/licenses/by-sa/2.1/jp/deed.en_CA +// +// TODO: +// input / form +// history + + +(function () { try{ + liberator.log('auto_detect_link.js loading'); + + //////////////////////////////////////////////////////////////// + // default setting + //////////////////////////////////////////////////////////////// + + let defaultSetting = { + nextPatterns: [ + /NEXT/, /next/, /Next/, + /^次(へ|の)/, /つぎへ/, /つづく/, /続/, /次/, /つぎ/, /next/i, /進む/, + /^>$/, />>/, />/ + ], + backPatterns: [ + /back/i, /back/i, /BACK/, /PREV/, + /^前(へ|の)/, /前/, /戻る/, /^<$/, /<</, /</ + ], + nextMappings: [']]'], + backMappings: ['[['], + useSuccPattern: true, + useNextHistory: false, + useBackHistory: false, + //clickButton: true, + force: false, + } + + //////////////////////////////////////////////////////////////// + // setting + //////////////////////////////////////////////////////////////// + + let _gv; + + // 評価を遅延するために関数にしておく + function gv () { + if (_gv) + return _gv; + + if (liberator.globalVariables) { + if (!liberator.globalVariables.autoDetectLink) + liberator.globalVariables.autoDetectLink = {}; + _gv = liberator.globalVariables.autoDetectLink; + } + + for (let key in defaultSetting) { + if (_gv[key] == undefined) + _gv[key] = defaultSetting[key]; + } + + return _gv; + } + + + //////////////////////////////////////////////////////////////// + // functions + //////////////////////////////////////////////////////////////// + + // Array#find + function find (ary, f) { + let func = (typeof f == 'function') ? f : function (v) v == f; + for (var i = 0; i < ary.length; i++) { + if (func(ary[i])) { + return ary[i]; + } + } + return null; + } + + + // 要素をクリックする + function clickElement (elem) { + liberator.log('click: ' + elem); + var e = content.document.createEvent('MouseEvents'); + e.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( e ); + } + + + // 開いたURIなどの表示 + function displayOpened (link) { + let msg = 'open <' + link.text + '> ' + link.uri; + setTimeout(function () liberator.echo(msg), 1000); + liberator.log(msg); + } + + + // リンクを開く + function open (link) { + liberator.log(link); + if (link.element) { + clickElement(link.element); + } else if (link.uri) { + link.frame.location.href = link.uri + } + displayOpened(link); + } + + + // 元の文字列、詰め込む文字、長さ + function padChar (s, c, n) { + return s.replace(new RegExp('^(.{0,'+(n-1)+'})$'), function(s)padChar(c+s,c,n)); + } + + + // (次|前)の数字文字列リストを取得 + function succNumber (n, next) { + let m = (parseInt(n.replace(/^0*(.)/,'$1')||0) + (next ? 1 : -1)).toString(); + let result = [m]; + if (m.length < n.length) + result.unshift(padChar(m.toString(), '0', n.length)); + return result; + } + + + // (次|前)の文字列リストを取得 + function succString (s, next) { + let result = [], d = next ? 1 : -1; + let c = String.fromCharCode(s.charCodeAt(0) + d); + if (('a' <= c && c <= 'z') || 'A' <= c && c <= 'Z') + result.push(c); + return result; + } + + + // (次|前)のURIリストを取得 + function succURI (uri, next) { + let urim = uri.match(/^(.+\/)([^\/]+)$/); + if (!urim) + return []; + let [_, dir, file] = urim, result = []; + // succ number + let (dm, succs, file = file, left = '') { + while (file && (dm = file.match(/\d+/))) { + let [rcontext, lcontext, lmatch] = [RegExp.rightContext, RegExp.leftContext, RegExp.lastMatch]; + left += lcontext; + succs = succNumber(lmatch, next); + for each (let succ in succs) { + result.push(dir + left + succ + rcontext); + } + left += lmatch; + file = rcontext; + } + } + // succ string + let (dm, succs, file = file, left = '') { + while (file && (dm = file.match(/(^|[^a-zA-Z])([a-zA-Z])([^a-zA-Z]|$)/))) { + let [rcontext, lcontext] = [RegExp.rightContext, RegExp.leftContext]; + left += lcontext + dm[1]; + succs = succString(dm[2], next); + for each (let succ in succs) { + result.push(dir + left + succ + dm[3] + rcontext); + } + left += dm[1]; + file = dm[3] + rcontext; + } + } + return result; + } + + + // パターンマッチング + function match (pattern, link) { + if (pattern instanceof Function) + return pattern(link); + if (!link.text) + return; + if (pattern instanceof RegExp) + return pattern.test(link.text); + return link.text.toLowerCase().indexOf(pattern.toString().toLowerCase()) >= 0; + } + + + // XPath + function getElementByXPath (xpath, root) { + let res = content.document.evaluate(xpath, root, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); + if (res) + return res.singleNodeValue; + } + + + // XPath + function getElementsByXPath (xpath, root) { + let result = [], res = content.document.evaluate(xpath, root, null, 7, null); + for (let i = 0; i < res.snapshotLength; i++) + result.push(res.snapshotItem(i)); + return result; + } + + + // リンクのフィルタ + function linkFilter (link) { + return link.href && !link.href.match(/@/) && link.href.match(/^((https?|file|ftp):\/\/|javascript:)/) && link.textContent; + } + + + // 全てのリンクを取得 + // 再帰的にフレーム内のも取得する + function getAllLinks (content) { + var result = []; + // Anchor + for each (let it in content.document.links) { + if (linkFilter(it)) + result.push({frame: content, uri: it.href, text: it.textContent, element: it}); + } + // Form + for each (let input in content.document.getElementsByTagName('input')) { + (function (input) { + result.push({ + frame: content, + uri: input.form.action, + text: input.value, + click: input.click, + element: input, + }); + })(input); + } + // Frame + if (content.frames) { + for (let i = 0; i < content.frames.length; i++) { + result = result.concat(getAllLinks(content.frames[i])); + } + } + return result; + } + + + // 上書きした設定を返す。 + function getCurrentSetting (setting) { + if (!setting) + setting = {}; + for (let n in gv()) { + if (setting[n] == undefined) + setting[n] = gv()[n]; + } + return setting; + } + + + //////////////////////////////////////////////////////////////// + // main + //////////////////////////////////////////////////////////////// + + // リンクを探す + function detect (next, setting) { + try { + setting = getCurrentSetting(setting); + + patterns = next ? setting.nextPatterns : setting.backPatterns; + + let uri = window.content.location.href; + let links = getAllLinks(window.content); + + // keywords + if (1) { + for each (let pattern in patterns) { + let link = find(links, function (link) match(pattern, link)); + if (link) + return link; + } + } + + // succ + let succs = succURI(uri, next); + if (setting.useSuccPattern) { + for each (succ in succs) { + let link = find(links, function (link) (link.uri.indexOf(succ) >= 0)); + if (link) + return link; + } + } + + // force + if (setting.force && succs.length) + return { + uri: succs[0], + text: '!force!', + frame: window.content, + }; + + } catch (e) { + liberator.log(e); + liberator.echoerr(e); + } + } + + + // 猫又 + function go (next, setting) { + setting = getCurrentSetting(setting); + + if ((next && setting.useNextHistory) || (!next && setting.useBackHistory)) { + next ? BrowserForward() : BrowserBack(); + displayOpened({uri: 'history', text: next ? 'next' : 'back'}); + return; + } + + let link = detect(next, setting); + if (link) + open(link); + } + + + // 外部から使用可能にする。 + if (liberator.plugins) + liberator.plugins.autoDetectLink = {detect: detect, go: go}; + + + //////////////////////////////////////////////////////////////// + // Mappings + //////////////////////////////////////////////////////////////// + + if (gv().nextMappings.length) { + liberator.mappings.addUserMap( + [liberator.modes.NORMAL], + gv().nextMappings, + 'Go next', + function () go(true) + ); + } + + + if (gv().backMappings.length) { + liberator.mappings.addUserMap( + [liberator.modes.NORMAL], + gv().backMappings, + 'Go back', + function () go(false) + ); + } + + + liberator.log('auto_nextandback.js loaded'); + +}catch(e){liberator.log(e)} +})(); + + |