").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cc=a.document.documentElement;function dc(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dc(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cc;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cc})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dc(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=La(k.pixelPosition,function(a,c){return c?(c=Ja(a,b),Ha.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ec=a.jQuery,fc=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fc),b&&a.jQuery===m&&(a.jQuery=ec),m},typeof b===K&&(a.jQuery=a.$=m),m});
6 |
--------------------------------------------------------------------------------
/data/keycodes.ts:
--------------------------------------------------------------------------------
1 | /* MIT license
2 | * https://github.com/timoxley/keycode/blob/master/index.js
3 | */
4 | const KeyCodeByName = {
5 | 'backspace': 8,
6 | 'tab': 9,
7 | 'enter': 13,
8 | 'shift': 16,
9 | 'ctrl': 17,
10 | 'alt': 18,
11 | 'pause/break': 19,
12 | 'caps lock': 20,
13 | 'esc': 27,
14 | 'space': 32,
15 | 'page up': 33,
16 | 'page down': 34,
17 | 'end': 35,
18 | 'home': 36,
19 | 'left': 37,
20 | 'up': 38,
21 | 'right': 39,
22 | 'down': 40,
23 | 'insert': 45,
24 | 'delete': 46,
25 | 'command': 91,
26 | 'right click': 93,
27 | 'numpad *': 106,
28 | 'numpad +': 107,
29 | 'numpad -': 109,
30 | 'numpad .': 110,
31 | 'numpad /': 111,
32 | 'num lock': 144,
33 | 'scroll lock': 145,
34 | 'my computer': 182,
35 | 'my calculator': 183,
36 | ';': 186,
37 | '=': 187,
38 | ',': 188,
39 | '-': 189,
40 | '.': 190,
41 | '/': 191,
42 | '`': 192,
43 | '[': 219,
44 | '\\': 220,
45 | ']': 221,
46 | "'": 222,
47 | "a": 65,
48 | "b": 66,
49 | "c": 67,
50 | "d": 68,
51 | "e": 69,
52 | "f": 70,
53 | "g": 71,
54 | "h": 72,
55 | "i": 73,
56 | "j": 74,
57 | "k": 75,
58 | "l": 76,
59 | "m": 77,
60 | "n": 78,
61 | "o": 79,
62 | "p": 80,
63 | "q": 81,
64 | "r": 82,
65 | "s": 83,
66 | "t": 84,
67 | "u": 85,
68 | "v": 86,
69 | "w": 87,
70 | "x": 88,
71 | "y": 89,
72 | "z": 90
73 | }
74 |
75 | const KeyNameByCode = {
76 | 8 : 'backspace',
77 | 9 : 'tab',
78 | 13 : 'enter',
79 | 16 : 'shift',
80 | 17 : 'ctrl',
81 | 18 : 'alt',
82 | 19 : 'pause/break',
83 | 20 : 'caps lock',
84 | 27 : 'esc',
85 | 32 : 'space',
86 | 33 : 'page up',
87 | 34 : 'page down',
88 | 35 : 'end',
89 | 36 : 'home',
90 | 37 : 'left',
91 | 38 : 'up',
92 | 39 : 'right',
93 | 40 : 'down',
94 | 45 : 'insert',
95 | 46 : 'delete',
96 |
97 | 65 : "a",
98 | 66 : "b",
99 | 67 : "c",
100 | 68 : "d",
101 | 69 : "e",
102 | 70 : "f",
103 | 71 : "g",
104 | 72 : "h",
105 | 73 : "i",
106 | 74 : "j",
107 | 75 : "k",
108 | 76 : "l",
109 | 77 : "m",
110 | 78 : "n",
111 | 79 : "o",
112 | 80 : "p",
113 | 81 : "q",
114 | 82 : "r",
115 | 83 : "s",
116 | 84 : "t",
117 | 85 : "u",
118 | 86 : "v",
119 | 87 : "w",
120 | 88 : "x",
121 | 89 : "y",
122 | 90 : "z",
123 |
124 | 91 : 'command',
125 | 93 : 'right click',
126 | 106 : 'numpad *',
127 | 107 : 'numpad +',
128 | 109 : 'numpad -',
129 | 110 : 'numpad .',
130 | 111 : 'numpad /',
131 | 144 : 'num lock',
132 | 145 : 'scroll lock',
133 | 182 : 'my computer',
134 | 183 : 'my calculator',
135 | 186 : ';',
136 | 187 : '=',
137 | 188 : ',',
138 | 189 : '-',
139 | 190 : '.',
140 | 191 : '/',
141 | 192 : '`',
142 | 219 : '[',
143 | 220 : '\\',
144 | 221 : ']',
145 | 222 : "'",
146 |
147 | }
148 |
149 | function isAlphaNumeric(keyCode : number) : boolean{
150 | return (keyCode >= 65 && keyCode <= 90);
151 | }
152 |
--------------------------------------------------------------------------------
/data/messaging.ts:
--------------------------------------------------------------------------------
1 | declare var content;
2 |
3 | const KeyCodeShift = 16;
4 | const KeyCodeEsc = 27;
5 | const KeyCodeD = 68;
6 | const KeyCodeG = 71;
7 | const KeyCodeI = 73;
8 | const KeyCodeN = 78;
9 | const KeyCodeForwardSlash = 191;
10 | const KeyCodeEnter = 13;
11 |
12 |
13 | interface Message {
14 | name : string;
15 | sync : boolean;
16 | json : Object;
17 | objects? : Object[];
18 | }
19 |
20 | interface Listener {
21 | receiveMessage : (message : Message) => void
22 | }
23 |
24 | class KeypressListener {
25 | receiveMessageFunc : (message : Message) => void
26 | constructor(receiveMessage : (message : Message) => void){
27 | }
28 |
29 | receiveMessage(message : Message) : void {
30 | content.console.log("received message", message);
31 | this.receiveMessageFunc(message);
32 | }
33 | }
34 |
35 | class KeypressMessage {
36 | name : string;
37 | sync : boolean;
38 | json : Object;
39 | objects : Object[];
40 |
41 | keyCode : number;
42 |
43 | constructor(keycode : number, json){
44 | this.name = "keypress";
45 | this.json = json;
46 | }
47 | }
48 |
49 |
50 | class LogMessage {
51 | name : string;
52 | sync : boolean;
53 | json : {contents : any[]}
54 |
55 |
56 | constructor(...objs: any[]){
57 | this.name = "log";
58 | this.json = {"contents" : objs};
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/data/style.css:
--------------------------------------------------------------------------------
1 | #electrovim-overlay{
2 | background: hsla(60, 100%, 50%, 0.5);
3 | position: fixed;
4 | top:auto;
5 | bottom:0;
6 | right:0;
7 |
8 | font: 12px/14px sans-serif;
9 | color: black;
10 | }
11 |
12 | #electrovim-overlay input {
13 | background-color: transparent;
14 | border: 0px solid;
15 | margin-left: 5px;
16 | }
17 |
18 | .electrovim-highlight {
19 | background-color: yellow;
20 | }
21 |
22 | .electrovim-current-find {
23 | background-color: hsl(169, 100%, 50%);
24 | }
25 |
--------------------------------------------------------------------------------
/electrovim.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
252 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChimeraCoder/electrovim/3f512ad85d6a9e5307645fbae8ecf0f8d3c52c70/icon.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var KeyCodeShift = 16;
2 | var KeyCodeEsc = 27;
3 | var KeyCodeD = 68;
4 | var KeyCodeG = 71;
5 | var KeyCodeI = 73;
6 | var KeyCodeN = 78;
7 | var KeyCodeForwardSlash = 191;
8 | var KeyCodeEnter = 13;
9 | var KeypressListener = (function () {
10 | function KeypressListener(receiveMessage) {
11 | }
12 | KeypressListener.prototype.receiveMessage = function (message) {
13 | content.console.log("received message", message);
14 | this.receiveMessageFunc(message);
15 | };
16 | return KeypressListener;
17 | })();
18 | var KeypressMessage = (function () {
19 | function KeypressMessage(keycode, json) {
20 | this.name = "keypress";
21 | this.json = json;
22 | }
23 | return KeypressMessage;
24 | })();
25 | var LogMessage = (function () {
26 | function LogMessage() {
27 | var objs = [];
28 | for (var _i = 0; _i < arguments.length; _i++) {
29 | objs[_i - 0] = arguments[_i];
30 | }
31 | this.name = "log";
32 | this.json = { "contents": objs };
33 | }
34 | return LogMessage;
35 | })();
36 | ///
37 | var buttons = require('sdk/ui/button/action');
38 | var tabs = require("sdk/tabs");
39 | var Hotkey = (require("sdk/hotkeys")).Hotkey;
40 | var pageMod = require("sdk/page-mod");
41 | var slf = require("sdk/self");
42 | var data = require("sdk/self").data;
43 | var ui = require("sdk/ui");
44 | var tabWorkers = new WeakMap();
45 | var tabLeft = Hotkey({
46 | combo: "control-p",
47 | onPress: function () {
48 | var activeTab = tabs.activeTab;
49 | var activeTabIndex = activeTab.index;
50 | var targetIndex = ((activeTabIndex - 1) + tabs.length) % tabs.length;
51 | for (var _i = 0; _i < tabs.length; _i++) {
52 | var tab = tabs[_i];
53 | if (tab.index === targetIndex) {
54 | tab.activate();
55 | return;
56 | }
57 | }
58 | console.log("could not find tab with index ", targetIndex);
59 | }
60 | });
61 | var tabRight = Hotkey({
62 | combo: "control-n",
63 | onPress: function () {
64 | var activeTab = tabs.activeTab;
65 | var activeTabIndex = activeTab.index;
66 | var targetIndex = (activeTabIndex + 1) % tabs.length;
67 | for (var _i = 0; _i < tabs.length; _i++) {
68 | var tab = tabs[_i];
69 | if (tab.index === targetIndex) {
70 | tab.activate();
71 | return;
72 | }
73 | }
74 | console.log("could not find tab with index ", targetIndex);
75 | }
76 | });
77 | var pagedown = Hotkey({
78 | combo: "control-f",
79 | onPress: function () {
80 | var activeTab = tabs.activeTab;
81 | if (!tabWorkers.has(activeTab)) {
82 | console.log("tab has no worker!", activeTab);
83 | }
84 | var worker = tabWorkers.get(activeTab);
85 | worker.port.emit("pagedown", {});
86 | }
87 | });
88 | var pageup = Hotkey({
89 | combo: "control-b",
90 | onPress: function () {
91 | var activeTab = tabs.activeTab;
92 | if (!tabWorkers.has(activeTab)) {
93 | console.log("tab has no worker!", activeTab);
94 | }
95 | var worker = tabWorkers.get(activeTab);
96 | worker.port.emit("pageup", {});
97 | }
98 | });
99 | function closeTab(tab, message) {
100 | console.log("chrome received message", message);
101 | console.log("closing tab", tab);
102 | tab.close();
103 | }
104 | pageMod.PageMod({
105 | include: ["http://*", "https://*"],
106 | contentStyleFile: "./style.css",
107 | contentScriptFile: [slf.data.url("jquery-1.11.3.min.js"), slf.data.url("content-script.js")],
108 | contentScriptWhen: "ready",
109 | onAttach: function (worker) {
110 | if (!worker.tab) {
111 | console.log("worker has no tab!", worker);
112 | }
113 | tabWorkers.set(worker.tab, worker);
114 | worker.port.on("keypress", function (message) {
115 | var tab = tabs.activeTab;
116 | closeTab(tab, message);
117 | });
118 | worker.port.on("log", function (message) {
119 | var tabIndex = worker.tab ? worker.tab.index : -1;
120 | console.log.apply(console, ["(tab", tabIndex, "):"].concat(message.json.contents));
121 | });
122 | }
123 | });
124 |
--------------------------------------------------------------------------------
/lightning-bolt-145473_1280.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChimeraCoder/electrovim/3f512ad85d6a9e5307645fbae8ecf0f8d3c52c70/lightning-bolt-145473_1280.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "electrovim",
3 | "name": "electrovim",
4 | "version": "0.0.8",
5 | "description": "Add basic vim hotkeys in Firefox for browsing with e10s (electrolysis) support",
6 | "main": "index.js",
7 | "author": "chimeracoder",
8 | "engines": {
9 | "firefox": ">=38.0a1",
10 | "fennec": ">=38.0a1"
11 | },
12 | "permissions": ["multiprocess"],
13 | "license": "GPLv3"
14 | }
15 |
--------------------------------------------------------------------------------
/test/test-index.js:
--------------------------------------------------------------------------------
1 | var main = require("../");
2 |
3 | exports["test main"] = function(assert) {
4 | assert.pass("Unit test running!");
5 | };
6 |
7 | exports["test main async"] = function(assert, done) {
8 | assert.pass("async Unit test running!");
9 | done();
10 | };
11 |
12 | exports["test dummy"] = function(assert, done) {
13 | main.dummy("foo", function(text) {
14 | assert.ok((text === "foo"), "Is the text actually 'foo'");
15 | done();
16 | });
17 | };
18 |
19 | require("sdk/test").run(exports);
20 |
--------------------------------------------------------------------------------
/ts/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare const require;
4 |
5 | const buttons = require('sdk/ui/button/action');
6 | const tabs = require("sdk/tabs");
7 | const { Hotkey} = require("sdk/hotkeys");
8 | const pageMod = require("sdk/page-mod");
9 | const slf = require("sdk/self");
10 | const data = require("sdk/self").data;
11 |
12 | const ui = require("sdk/ui");
13 |
14 |
15 | const tabWorkers = new WeakMap();
16 |
17 | const tabLeft = Hotkey({
18 | combo: "control-p",
19 | onPress: function() {
20 | const activeTab = tabs.activeTab;
21 | const activeTabIndex = activeTab.index;
22 | const targetIndex = ((activeTabIndex - 1) + tabs.length) % tabs.length;
23 | for(let tab of tabs){
24 | if(tab.index === targetIndex ){
25 | tab.activate();
26 | return;
27 | }
28 | }
29 | console.log("could not find tab with index ", targetIndex);
30 | }
31 | });
32 |
33 | const tabRight = Hotkey({
34 | combo: "control-n",
35 | onPress: function() {
36 | const activeTab = tabs.activeTab;
37 | const activeTabIndex = activeTab.index;
38 | const targetIndex = (activeTabIndex + 1) % tabs.length;
39 | for(let tab of tabs){
40 | if(tab.index === targetIndex ){
41 | tab.activate();
42 | return;
43 | }
44 | }
45 | console.log("could not find tab with index ", targetIndex);
46 | }
47 | });
48 |
49 | const pagedown = Hotkey({
50 | combo: "control-f",
51 | onPress: function() {
52 | const activeTab = tabs.activeTab;
53 | if(!tabWorkers.has(activeTab)){
54 | console.log("tab has no worker!", activeTab);
55 | }
56 | const worker : any = tabWorkers.get(activeTab);
57 | worker.port.emit("pagedown", {});
58 | }
59 | });
60 |
61 | const pageup = Hotkey({
62 | combo: "control-b",
63 | onPress: function() {
64 | const activeTab = tabs.activeTab;
65 | if(!tabWorkers.has(activeTab)){
66 | console.log("tab has no worker!", activeTab);
67 | }
68 | const worker : any = tabWorkers.get(activeTab);
69 | worker.port.emit("pageup", {});
70 | }
71 | });
72 |
73 |
74 | function closeTab(tab, message){
75 | console.log("chrome received message", message);
76 | console.log("closing tab", tab);
77 | tab.close();
78 | }
79 |
80 |
81 | pageMod.PageMod({
82 | include: ["http://*", "https://*"],
83 | contentStyleFile: "./style.css",
84 | contentScriptFile: [slf.data.url("jquery-1.11.3.min.js"), slf.data.url("content-script.js")],
85 | contentScriptWhen: "ready",
86 | onAttach: function(worker){
87 | if(!worker.tab){
88 | console.log("worker has no tab!", worker);
89 | }
90 | tabWorkers.set(worker.tab, worker);
91 | worker.port.on("keypress", (message : Message) => {
92 | const tab = tabs.activeTab;
93 | closeTab(tab, message);
94 | });
95 |
96 | worker.port.on("log", (message : LogMessage) => {
97 | const tabIndex = worker.tab ? worker.tab.index : -1;
98 | console.log("(tab", tabIndex, "):", ...message.json.contents);
99 | });
100 | }
101 | });
102 |
--------------------------------------------------------------------------------