aboutsummaryrefslogtreecommitdiffstats
path: root/exopen.js
blob: 90b6722039f4648ca2bd3da34aae66e3c0a128c5 (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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
var PLUGIN_INFO =
<VimperatorPlugin>
<name>{NAME}</name>
<description>Open URL from a template</description>
<description lang="ja">テンプレートからURLをOpenします</description>
<minVersion>2.0pre</minVersion>
<maxVersion>2.0pre</maxVersion>
<updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/exopen.js</updateURL>
<author homepage="http://vimperator.g.hatena.ne.jp/pekepekesamurai/">pekepekesamurai</author>
<version>0.10.1</version>
<detail lang="ja"><![CDATA[
== Command ==
:exopen [template_name]
  [template_name] で設定されたURLを開きます

=== Example ===
:exopen http://www.google.co.jp/search?q=%TITLE%:
  %TITLE%を現在開いているWebページのタイトルに展開してURLを開きます
:exopen [title]
  テンプレートで設定されたURLを開きます

== Keyword ==
%TITLE%:
  現在のWebページのタイトル
%URL%:
  現在のWebページのURL
%SEL%:
  選択中の文字列
%HTMLSEL%:
  選択中のHTMLソース

== .vimperatorrc ==
>||
javascript <<EOM
liberator.globalVariables.exopen_templates = [
  {
    label: 'vimpnightly',
    value: 'http://code.google.com/p/vimperator-labs/downloads/list?can=1&q=label:project-vimperator',
    description: 'open vimperator nightly xpi page',
    newtab: true
  }, {
    label: 'vimplab',
    value: 'http://www.vimperator.org/vimperator',
    description: 'open vimperator trac page',
    newtab: true
  }, {
    label: 'vimpscript',
    value: 'http://code.google.com/p/vimperator-labs/issues/list?can=2&q=label%3Aproject-vimperator+label%3Atype-plugin',
    description: 'open vimperator trac script page',
    newtab: true
  }, {
    label: 'coderepos',
    value: 'http://coderepos.org/share/browser/lang/javascript/vimperator-plugins/trunk/',
    description: 'open coderepos vimperator-plugin page',
    newtab: true
  }, {
    label: 'sldr',
    value: 'http://reader.livedoor.com/subscribe/%URL%'
  }
];
EOM
||<
label:
  テンプレート名コマンドの引数で指定してください
value:
  OpenするURL
custom:
  関数か配列で指定してください
  関数の場合return された文字列をオープンします
  配列の場合value で指定された文字列を置換します(条件Array[0]置換文字列Array[1])
description:
  補完時に表示する説明文
newtab:
  新規タブで開く場合は true を指定してください
escape:
  URLエンコードする場合true を指定してください
]]></detail>
</VimperatorPlugin>;

liberator.plugins.exOpen = (function() {
  var global = liberator.globalVariables.exopen_templates;
  if (!global) {
    global = [{
      label: 'vimpnightly',
      value: 'http://code.google.com/p/vimperator-labs/downloads/list?can=1&q=label:project-vimperator',
      description: 'open vimperator nightly xpi page',
      newtab: true
    }, {
      label: 'vimplab',
      value: 'http://www.vimperator.org/vimperator',
      description: 'open vimperator trac page',
      newtab: true
    }, {
      label: 'vimpscript',
      value: 'http://code.google.com/p/vimperator-labs/issues/list?can=2&q=label%3Aproject-vimperator+label%3Atype-plugin',
      description: 'open vimperator trac script page',
      newtab: true
    }, {
      label: 'coderepos',
      value: 'http://coderepos.org/share/browser/lang/javascript/vimperator-plugins/trunk/',
      description: 'open coderepos vimperator-plugin page',
      newtab: true
    }, {
      label: 'sldr',
      value: 'http://reader.livedoor.com/subscribe/%URL%'
    }];
  }

  function openTabOrSwitch(url) {
    var tabs = gBrowser.mTabs;
    for (let i=0, l=tabs.length; i<l; i++)
      if (tabs[i].linkedBrowser.contentDocument.location.href == url) return (gBrowser.tabContainer.selectedIndex = i);
    return liberator.open(url, liberator.NEW_TAB);
  }

  function replacer(str, isEscape) {
    if (!str) return '';
    var win = new XPCNativeWrapper(window.content.window);
    var sel = '', htmlsel = '';
    var selection = win.getSelection();
    function __replacer(val) {
      switch (val) {
        case '%TITLE%':
          return buffer.title;
        case '%URL%':
          return buffer.URL;
        case '%SEL%':
          if (sel) return sel;
          else if (selection.rangeCount < 1) return '';
          for (let i=0, c=selection.rangeCount; i<c;
            sel += selection.getRangeAt(i++).toString());
          return sel;
        case '%HTMLSEL%':
          if (htmlsel) return sel;
          if (selection.rangeCount < 1) return '';

          let serializer = new XMLSerializer();
          for (let i=0, c=selection.rangeCount; i<c;
            htmlsel += serializer.serializeToString(selection.getRangeAt(i++).cloneContents()));
          return htmlsel;
      }
      return '';
    }
    var _replacer = __replacer;
    if (isEscape) _replacer = function(val) escape( __replacer(val) );

    return str.replace(/%(TITLE|URL|SEL|HTMLSEL)%/g, _replacer);
  }

  var ExOpen = function() this.initialize.apply(this, arguments);
  ExOpen.prototype = {
    initialize: function() {
      this.createCompleter();
      this.registerCommand();
    },
    createCompleter: function() {
        this.completer = global.map(
          function(t) [t.label, util.escapeString((t.description ? t.description + ' - ' : '') + t.value)]
        );
    },
    registerCommand: function() {
      var self = this;
      commands.addUserCommand(['exopen'], 'Open byextension URL',
        function(args) self.open(args), {
          completer: function(context, args) {
            context.title = ['Template', 'Description - Value'];
            if (!context.filter) {
              context.completions = self.completer;
              return;
            }
            var filter = context.filter.toLowerCase();
            context.completions = self.completer.filter( function( t ) t[0].toLowerCase().indexOf(filter) == 0 );
          }
      });
    },
    find: function(label) {
      var ret = null;
      global.some(function(template) (template.label == label) && (ret = template));
      return ret;
    },
    open: function(args) {
      var url = '';
      if (!args) return;
      var name = args.string;
      if (args instanceof Array) {
        name = args.shift();
        args.string = args.string.replace(new RegExp(name.replace(/(\W)/g,'\\$1')+'\\s+'),'');
      }
      var template = this.find(name) || {value: name};
      if (typeof template.custom == 'function') {
        url = template.custom.call(this, template.value, args);
      } else if (template.custom instanceof Array) {
        url = replacer(template.value).replace(template.custom[0], template.custom[1], template.escape);
      } else {
        url = replacer(template.value, template.escape);
      }
      if (!url) return;
      if (template.newtab || args.bang) openTabOrSwitch(url);
      else liberator.open(url);
    },
  };
  return new ExOpen();
})();
> (typeof f == 'function') ? f : function (v) v == f; for (let i = 0, l = ary.length; i < l; i++) { if (func(ary[i])) { return ary[i]; } } return null; } // 要素をクリックする function clickElement (elem) buffer.followLink(elem); // 開いたURIなどの表示 function displayOpened (link) { var msg = 'open: ' + link.type + ' <' + removeSpace(link.text) + '> ' + link.uri; setTimeout(function () liberator.echo(msg, commandline.FORCE_SINGLELINE), gv().displayDelay); } // リンクを開く function open (link) { if (link.element) { clickElement(link.element); } else if (link.uri) { link.frame.location.href = link.uri; } displayOpened(link); } // 元の文字列、詰め込む文字、長さ function padChar (s, c, n) s.replace(new RegExp('^(.{0,'+(n-1)+'})$'), function (s) padChar(c+s, c, n)); // ID っぽい文字か考えてみる! // 数字だけで長いのは ID っぽい! // 西暦っぽいのは無視しない方が良いかも。 // 根拠はないが、1980-2029 の範囲で。 // 後方00 が含まれているパターンは、インクリメントしてもいい気がする // 830000 => 830001 // XXX 根拠があやしぎる! function likeID (s) /^\d{6,}$/.test(s) && !/^(19[89]|20[012])\d/.test(s) && !/00\d\d$/.test(s); // (次|前)の数字文字列リストを取得 function succNumber (n, next, ignoreId) { if (ignoreId && likeID(n)) return []; var m = (parseInt(n || 0, 10) + (next ? 1 : -1)).toString(); var result = [m]; if (m.length < n.length) result.unshift(padChar(m.toString(), '0', n.length)); return result; } // (次|前)の文字列リストを取得 function succString (s, next) { var result = [], d = next ? 1 : -1; var 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, ignoreId) { var urim = uri.match(/^(.+\/)([^\/]+)$/); if (!urim) return []; var [_, dir, file] = urim, result = []; // succ number let (dm, file = file, left = '', temp = []) { while (file && (dm = file.match(/\d+/))) { let [rcontext, lcontext, lmatch] = [RegExp.rightContext, RegExp.leftContext, RegExp.lastMatch]; left += lcontext; succNumber(lmatch, next, ignoreId).forEach(function (succ) { temp.push(dir + left + succ + rcontext); }); left += lmatch; file = rcontext; } result = result.concat(temp.reverse()); } // succ string let (dm, file = file, left = '', temp = []) { 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]; succString(dm[2], next).forEach(function (succ) { temp.push(dir + left + succ + dm[3] + rcontext); }); left += dm[1]; file = dm[3] + rcontext; } result = result.concat(temp.reverse()); } return result; } // パターンマッチング function match (pattern, link) pattern instanceof Function ? pattern(link) : !link.text ? null : pattern instanceof RegExp ? pattern.test(link.text) : link.text.toLowerCase().indexOf(pattern.toString().toLowerCase()) >= 0; // 要素が表示されているか? function isVisible (element) { var st; try { st = content.document.defaultView.getComputedStyle(element, null); return !(st.display && st.display.indexOf('none') >= 0) && (!element.parentNode || isVisible(element.parentNode)) } catch (e) { return true; } } // リンクのフィルタ function linkElementFilter (elem) isVisible(elem) && elem.href && elem.href.indexOf('@') < 0 && /^(?:(?:https?|f(?:ile|tp)):\/\/|javascript:)/.test(elem.href) && elem.textContent; // 全てのリンクを取得 // 再帰的にフレーム内のも取得する function getAllLinks (content) { var result = []; // Anchor var elements = content.document.links; for (let i = 0, l = elements.length; i < l; i++) { let it = elements[i]; if (linkElementFilter(it)) result.push({ type: 'link', frame: content, uri: it.href, rel: it.rel, text: it.textContent, element: it }); } // Form elements = content.document.getElementsByTagName('input'); for (let i = 0, l = elements.length; i < l; i++) { (function (input) { result.push({ type: 'input', frame: content, uri: input.form && input.form.action, text: input.value, click: input.click, element: input, }); })(elements[i]); } // Frame if (content.frames) { for (let i = 0, l = content.frames.length; i < l; i++) { result = result.concat(getAllLinks(content.frames[i])); } } return result; } // 全フレームの URL を得る function getAllLocations (content) { let result = [content.location.href]; if (content.frames) { for (let i = 0, l = content.frames.length; i < l; i++) { result = result.concat(getAllLocations(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; } // 相対アドレスから絶対アドレスに変換するんじゃないの? function toAbsPath (path) { with (content.document.createElement('a')) return (href = path) && href; } // AutoPagerize のデータからマッチする物を取得 function getAutopagerizeNext () { if (!ap_cache) return; var info = (function () { var uri = buffer.URL; for each (let cache in ap_cache) { for (let i = 0, l = cache.info.length; i < l; i++) { let info = cache.info[i]; if (uri.match(info.url)) return info; } } })(); if (!info) return; var doc = content.document; var result = doc.evaluate(info.nextLink, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); if (result.singleNodeValue) return result.singleNodeValue; } //////////////////////////////////////////////////////////////// // main //////////////////////////////////////////////////////////////// // リンクを探す function detect (next, setting) { try { setting = getCurrentSetting(setting); // TODO if (setting.useAutoPagerize && next) { let apnext = getAutopagerizeNext(); if (apnext) { return { type: 'aplink', frame: content, uri: apnext.href || apnext.action || apnext.value, text: apnext.textContent || apnext.title || apnext, element: apnext }; } } patterns = next ? setting.nextPatterns : setting.backPatterns; let uri = window.content.location.href; let links = getAllLinks(window.content); // rel="prev|next" { let relValue = next ? /(?:^|[ \t\r\n])next(?:[ \t\n\r]|$)/ : /(?:^|[ \t\r\n])prev(?:[ \t\n\r]|$)/; let link = find(links, function (link) ((typeof link.rel == 'string') && relValue.test(link.rel.toLowerCase()))); if (link) return link; } // keywords { let link; if (patterns.some(function (pattern) { link = find(links, function (link) match(pattern, link)); return link ? true : false; })) return link; } // succ let succs = []; getAllLocations(window.content).forEach(function (uri) { succs = succs.concat(succURI(uri, next, setting.ignoreId)); }); if (setting.useSuccPattern) { let link; if (succs.some(function (succ) { link = find(links, function (link) link.uri && (link.uri.indexOf(succ) >= 0)); return link ? true : false; })) return link; } // force if (setting.force && succs.length) { return { type: 'force', 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; } var link = detect(next, setting); if (link) open(link); } // 外部から使用可能にする。 if (liberator.plugins) liberator.plugins.autoDetectLink = {detect: detect, go: go}; //////////////////////////////////////////////////////////////// // Mappings //////////////////////////////////////////////////////////////// if (gv().nextMappings.length) { mappings.remove([modes.NORMAL], gv().nextMappings); mappings.addUserMap( [modes.NORMAL], gv().nextMappings, 'Go next', function () go(true) ); } if (gv().backMappings.length) { mappings.remove([modes.NORMAL], gv().backMappings); mappings.addUserMap( [modes.NORMAL], gv().backMappings, 'Go back', function () go(false) ); } liberator.log('auto_detect_link.js loaded'); })();