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