Files: edabe8be49e24684f650eea0bbebd38e65df4f9f / modules_core / tabs.js
6128 bytesRaw
1 | const Tabs = require('hypertabs') |
2 | const h = require('../h') |
3 | const keyscroll = require('../keyscroll') |
4 | const open = require('open-external') |
5 | const { webFrame, remote } = require('electron') |
6 | |
7 | function ancestor (el) { |
8 | if(!el) return |
9 | if(el.tagName !== 'A') return ancestor(el.parentElement) |
10 | return el |
11 | } |
12 | |
13 | exports.needs = { |
14 | build_scroller: 'first', |
15 | screen_view: 'first', |
16 | search_box: 'first', |
17 | menu: 'first', |
18 | external_confirm:'first' |
19 | } |
20 | |
21 | exports.gives = 'screen_view' |
22 | |
23 | exports.create = function (api) { |
24 | return function (path) { |
25 | if(path !== 'tabs') return |
26 | |
27 | function setSelected (indexes) { |
28 | const ids = indexes.map(index => tabs.get(index).content.id) |
29 | if(search) |
30 | if(ids.length > 1) |
31 | search.input.value = 'split('+ids.join(',')+')' |
32 | else |
33 | search.input.value = ids[0] |
34 | } |
35 | |
36 | const tabs = Tabs(setSelected) |
37 | const search = api.search_box((path, change) => { |
38 | if(tabs.has(path)) { |
39 | tabs.select(path) |
40 | return true |
41 | } |
42 | |
43 | const el = api.screen_view(path) |
44 | if (!el) return |
45 | |
46 | if(!el.title) el.title = path |
47 | el.scroll = keyscroll(el.querySelector('.Scroller .\\.content')) |
48 | tabs.add(el, change) |
49 | // localStorage.openTabs = JSON.stringify(tabs.tabs) |
50 | return change |
51 | }) |
52 | |
53 | // TODO add options to Tabs : e.g. Tabs(setSelected, { append: el }) |
54 | tabs.firstChild.appendChild( |
55 | h('div.extra', [ |
56 | search, |
57 | api.menu() |
58 | ]) |
59 | ) |
60 | |
61 | var saved = [] |
62 | // try { saved = JSON.parse(localStorage.openTabs) } |
63 | // catch (_) { } |
64 | |
65 | if(!saved || saved.length < 3) |
66 | saved = ['/public', '/private', '/notifications', '/data'] |
67 | |
68 | saved.forEach(function (path) { |
69 | var el = api.screen_view(path) |
70 | if(!el) return |
71 | el.id = el.id || path |
72 | if (!el) return |
73 | el.scroll = keyscroll(el.querySelector('.Scroller .\\.content')) |
74 | if(el) tabs.add(el, false, false) |
75 | }) |
76 | |
77 | tabs.select(0) |
78 | search.input.value = null // start with an empty field to show placeholder |
79 | |
80 | //handle link clicks |
81 | window.onclick = function (ev) { |
82 | var link = ancestor(ev.target) |
83 | if(!link) return |
84 | var path = link.hash.substring(1) |
85 | |
86 | ev.preventDefault() |
87 | ev.stopPropagation() |
88 | |
89 | //let the application handle this link |
90 | if (link.getAttribute('href') === '#') return |
91 | |
92 | //open external links. |
93 | //this ought to be made into something more runcible |
94 | if(link.href && open.isExternal(link.href)) return api.external_confirm(link.href) |
95 | |
96 | if(tabs.has(path)) |
97 | return tabs.select(path, !ev.ctrlKey, !!ev.shiftKey) |
98 | |
99 | var el = api.screen_view(path) |
100 | if(el) { |
101 | el.id = el.id || path |
102 | el.scroll = keyscroll(el.querySelector('.Scroller .\\.content')) |
103 | tabs.add(el, !ev.ctrlKey, !!ev.shiftKey) |
104 | // localStorage.openTabs = JSON.stringify(tabs.tabs) |
105 | } |
106 | |
107 | return false |
108 | } |
109 | |
110 | var gPressed = false |
111 | window.addEventListener('keydown', function (ev) { |
112 | if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA') |
113 | return |
114 | |
115 | // scroll to top |
116 | if (ev.keyCode == 71) { // g |
117 | if (!gPressed) return gPressed = true |
118 | var el = tabs.get(tabs.selected[0]).firstChild.scroll('first') |
119 | gPressed = false |
120 | } else { |
121 | gPressed = false |
122 | } |
123 | |
124 | switch(ev.keyCode) { |
125 | // scroll through tabs |
126 | case 72: // h |
127 | return tabs.selectRelative(-1) |
128 | case 76: // l |
129 | return tabs.selectRelative(1) |
130 | |
131 | // scroll through messages |
132 | case 74: // j |
133 | return tabs.get(tabs.selected[0]).firstChild.scroll(1) |
134 | case 75: // k |
135 | return tabs.get(tabs.selected[0]).firstChild.scroll(-1) |
136 | |
137 | // close a tab |
138 | case 88: // x |
139 | if (tabs.selected) { |
140 | var sel = tabs.selected |
141 | var i = sel.reduce(function (a, b) { return Math.min(a, b) }) |
142 | tabs.remove(sel) |
143 | tabs.select(Math.max(i-1, 0)) |
144 | } |
145 | return |
146 | |
147 | // activate the search field |
148 | case 191: // / |
149 | if (ev.shiftKey) |
150 | search.activate('?', ev) |
151 | else |
152 | search.activate('/', ev) |
153 | return |
154 | |
155 | // navigate to a feed |
156 | case 50: // 2 |
157 | if (ev.shiftKey) |
158 | search.activate('@', ev) |
159 | return |
160 | |
161 | // navigate to a channel |
162 | case 51: // 3 |
163 | if (ev.shiftKey) |
164 | search.activate('#', ev) |
165 | return |
166 | |
167 | // navigate to a message |
168 | case 53: // 5 |
169 | if (ev.shiftKey) |
170 | search.activate('%', ev) |
171 | return |
172 | } |
173 | }) |
174 | |
175 | // errors tab |
176 | var { |
177 | container: errors, |
178 | content: errorsContent |
179 | } = api.build_scroller() |
180 | |
181 | // remove loader error handler |
182 | if (window.onError) { |
183 | window.removeEventListener('error', window.onError) |
184 | delete window.onError |
185 | } |
186 | |
187 | // put errors in a tab |
188 | window.addEventListener('error', ev => { |
189 | const err = ev.error || ev |
190 | if(!tabs.has('errors')) |
191 | tabs.add(errors, false) |
192 | const el = h('div.message', [ |
193 | h('strong', err.message), |
194 | h('pre', err.stack) |
195 | ]) |
196 | if (errorsContent.firstChild) |
197 | errorsContent.insertBefore(el, errorsContent.firstChild) |
198 | else |
199 | errorsContent.appendChild(el) |
200 | }) |
201 | |
202 | if (process.versions.electron) { |
203 | |
204 | window.addEventListener('mousewheel', ev => { |
205 | const { ctrlKey, deltaY } = ev |
206 | if (ctrlKey) { |
207 | const direction = (deltaY / Math.abs(deltaY)) |
208 | const currentZoom = webFrame.getZoomLevel() |
209 | webFrame.setZoomLevel(currentZoom - direction) |
210 | } |
211 | }) |
212 | |
213 | window.addEventListener('contextmenu', ev => { |
214 | ev.preventDefault() |
215 | const Menu = remote.Menu |
216 | const MenuItem = remote.MenuItem |
217 | const menu = new Menu() |
218 | menu.append(new MenuItem({ |
219 | label: 'Inspect Element', |
220 | click: () => { |
221 | remote.getCurrentWindow().inspectElement(ev.x, ev.y) |
222 | } |
223 | })) |
224 | menu.popup(remote.getCurrentWindow()) |
225 | }) |
226 | } |
227 | |
228 | return tabs |
229 | } |
230 | |
231 | } |
232 |
Built with git-ssb-web