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