Files: 958235c2141b96dc42b5dff7893d5906dc1bdfd1 / app / sync / catch-keyboard-shortcut.js
3374 bytesRaw
1 | const nest = require('depnest') |
2 | |
3 | exports.gives = nest('app.sync.catchKeyboardShortcut') |
4 | |
5 | exports.needs = nest({ |
6 | 'app.html.searchBar': 'first', |
7 | 'app.html.tabs': 'first', |
8 | 'app.sync.goTo': 'first', |
9 | 'history.obs.store': 'first' |
10 | }) |
11 | |
12 | var gPressed = false |
13 | |
14 | exports.create = function (api) { |
15 | return nest('app.sync.catchKeyboardShortcut', catchKeyboardShortcut) |
16 | |
17 | function catchKeyboardShortcut (root) { |
18 | var tabs = api.app.html.tabs() |
19 | var search = api.app.html.searchBar() |
20 | var goTo = api.app.sync.goTo |
21 | |
22 | root.addEventListener('keydown', (ev) => { |
23 | isTextFieldEvent(ev) |
24 | ? textFieldShortcuts(ev) |
25 | : genericShortcuts(ev, { tabs, search, goTo }) |
26 | }) |
27 | } |
28 | } |
29 | |
30 | function isTextFieldEvent (ev) { |
31 | const tag = ev.target.nodeName |
32 | return (tag === 'INPUT' || tag === 'TEXTAREA') |
33 | } |
34 | |
35 | function textFieldShortcuts (ev) { |
36 | if (ev.keyCode === 13 && ev.ctrlKey) { |
37 | ev.target.publish() // expects the textField to have a publish method |
38 | } |
39 | } |
40 | |
41 | function genericShortcuts (ev, { tabs, goTo, search }) { |
42 | // Messages |
43 | if (ev.keyCode === 71) { // gg = scroll to top |
44 | if (!gPressed) { |
45 | gPressed = true |
46 | window.setTimeout(() => { |
47 | gPressed = false |
48 | }, 3000) |
49 | return |
50 | } |
51 | tabs.getCurrent().firstChild.scroll('first') |
52 | } |
53 | gPressed = false |
54 | |
55 | switch (ev.keyCode) { |
56 | // Messages (cont'd) |
57 | case 74: // j = older |
58 | return tabs.getCurrent().firstChild.scroll(1) |
59 | case 75: // k = newer |
60 | return tabs.getCurrent().firstChild.scroll(-1) |
61 | case 13: // enter = open |
62 | return goToMessage(ev, { tabs, goTo }) |
63 | case 79: // o = open |
64 | return goToMessage(ev, { tabs, goTo }) |
65 | case 192: // ` = toggle raw message view |
66 | return toggleRawMessage(ev) |
67 | |
68 | // Tabs |
69 | case 72: // h = left |
70 | return tabs.selectRelative(-1) |
71 | case 76: // l = right |
72 | return tabs.selectRelative(1) |
73 | case 88: // x = close |
74 | if (tabs.selected) { |
75 | var sel = tabs.selected |
76 | var i = sel.reduce(function (a, b) { return Math.min(a, b) }) |
77 | tabs.remove(sel) |
78 | tabs.select(Math.max(i - 1, 0)) |
79 | } |
80 | // TODO add history call in here |
81 | return |
82 | |
83 | // Search |
84 | case 191: // / = routes search |
85 | if (ev.shiftKey) search.activate('?', ev) |
86 | else search.activate('/', ev) |
87 | return |
88 | case 50: // @ = mention search |
89 | if (ev.shiftKey) search.activate('@', ev) |
90 | return |
91 | case 51: // # = channel search |
92 | if (ev.shiftKey) search.activate('#', ev) |
93 | return |
94 | case 53: // % = message search |
95 | if (ev.shiftKey) search.activate('%', ev) |
96 | } |
97 | } |
98 | |
99 | function goToMessage (ev, { tabs, goTo }) { |
100 | const msg = ev.target |
101 | if (!msg.classList.contains('Message')) return |
102 | |
103 | const { root, id } = msg.dataset |
104 | if (!root) return goTo(id) |
105 | |
106 | goTo(root) |
107 | setTimeout(() => scrollDownToMessage(id, tabs), 250) |
108 | } |
109 | |
110 | function scrollDownToMessage (id, tabs) { |
111 | tabs.getCurrent().firstChild.scroll('first') |
112 | locateKey() |
113 | |
114 | function locateKey () { |
115 | const msg = tabs.getCurrent().querySelector(`[data-id='${id}']`) |
116 | if (msg === null) return setTimeout(locateKey, 100) |
117 | |
118 | ;(msg.scrollIntoViewIfNeeded || msg.scrollIntoView).call(msg) |
119 | msg.focus() |
120 | } |
121 | } |
122 | |
123 | function toggleRawMessage (ev) { |
124 | const msg = ev.target |
125 | if (!msg.classList.contains('Message')) return |
126 | |
127 | // this uses a crudely exported nav api |
128 | msg.querySelector('.meta .toggle-raw-msg').click() |
129 | } |
130 |
Built with git-ssb-web