git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Tree: 47f88c71eec1667f35848253793375e28d250237

Files: 47f88c71eec1667f35848253793375e28d250237 / main-window.js

9153 bytesRaw
1var combine = require('depject')
2var entry = require('depject/entry')
3var electron = require('electron')
4var h = require('mutant/h')
5var Value = require('mutant/value')
6var when = require('mutant/when')
7var onceTrue = require('mutant/once-true')
8var computed = require('mutant/computed')
9var catchLinks = require('./lib/catch-links')
10var ObserveLinkHover = require('./lib/observe-link-hover')
11var insertCss = require('insert-css')
12var nest = require('depnest')
13var LatestUpdate = require('./lib/latest-update')
14var ref = require('ssb-ref')
15var setupContextMenuAndSpellCheck = require('./lib/context-menu-and-spellcheck')
16var watch = require('mutant/watch')
17
18module.exports = function (config) {
19 var sockets = combine(
20 overrideConfig(config),
21 addCommand('app.navigate', setView),
22 require('./modules'),
23 require('./plugs'),
24 require('patch-settings'),
25 require('patchcore'),
26 require('./overrides')
27 )
28
29 var api = entry(sockets, nest({
30 'config.sync.load': 'first',
31 'keys.sync.id': 'first',
32 'sbot.obs.connection': 'first',
33 'sbot.async.get': 'first',
34 'blob.sync.url': 'first',
35 'page.html.render': 'first',
36 'app.html.search': 'first',
37 'app.html.channels': 'first',
38 'app.views': 'first',
39 'app.sync.externalHandler': 'first',
40 'app.html.progressNotifier': 'first',
41 'profile.sheet.edit': 'first',
42 'profile.html.preview': 'first',
43 'app.navigate': 'first',
44 'channel.obs.subscribed': 'first',
45 'settings.obs.get': 'first',
46 'intl.sync.i18n': 'first',
47 }))
48
49 setupContextMenuAndSpellCheck(api.config.sync.load())
50
51 const i18n = api.intl.sync.i18n
52
53 var id = api.keys.sync.id()
54 var latestUpdate = LatestUpdate()
55 var subscribedChannels = api.channel.obs.subscribed(id)
56
57 // prompt to setup profile on first use
58 onceTrue(api.sbot.obs.connection, (sbot) => {
59 sbot.latestSequence(sbot.id, (_, key) => {
60 if (key == null) {
61 api.profile.sheet.edit()
62 }
63 })
64 })
65
66 var views = api.app.views(api.page.html.render, [
67 '/public', '/private', id, '/mentions'
68 ])
69
70 var pendingCount = views.get('/mentions').pendingUpdates
71
72 watch(pendingCount, count => {
73 electron.remote.app.setBadgeCount(count)
74 })
75
76
77 watch(api.settings.obs.get('patchwork.theme', 'light'), name => {
78 Array.from(document.head.children)
79 .filter(c => c.tagName == 'STYLE')
80 .forEach(c => c.innerText = '')
81
82 var theme = require('./styles')[name]
83 if (!theme) theme = require('./styles')['light']
84 insertCss(theme)
85 })
86
87 var container = h(`MainWindow -${process.platform}`, [
88 h('div.top', [
89 h('span.history', [
90 h('a', {
91 'ev-click': views.goBack,
92 classList: [ when(views.canGoBack, '-active') ]
93 }),
94 h('a', {
95 'ev-click': views.goForward,
96 classList: [ when(views.canGoForward, '-active') ]
97 })
98 ]),
99 h('span.nav', [
100 tab(i18n("Public"), '/public'),
101 tab(i18n("Private"), '/private'),
102 dropTab(i18n('More'), [
103 getSubscribedChannelMenu,
104 [i18n('Gatherings'), '/gatherings'],
105 [i18n('Extended Network'), '/all'],
106 {separator: true},
107 [i18n('Settings'), '/settings']
108 ])
109 ]),
110 h('span.appTitle', [
111 h('span.title', i18n("Patchwork")),
112 api.app.html.progressNotifier()
113 ]),
114 h('span', [ api.app.html.search(api.app.navigate) ]),
115 h('span.nav', [
116 tab(i18n('Profile'), id),
117 tab(i18n('Mentions'), '/mentions')
118 ])
119 ]),
120 when(latestUpdate,
121 h('div.info', [
122 h('a.message -update', { href: 'https://github.com/ssbc/patchwork/releases' }, [
123 h('strong', ['Patchwork ', latestUpdate, i18n(' has been released.')]), i18n(' Click here to download and view more info!'),
124 h('a.ignore', {'ev-click': latestUpdate.ignore}, 'X')
125 ])
126 ])
127 ),
128 views.html
129 ])
130
131 var currentHover = ObserveLinkHover(container, 500)
132 var previewElement = Value()
133 previewElement(currentHover.active.set)
134
135 currentHover(element => {
136 var href = element && element.getAttribute('href')
137 var preview = null
138
139 if (href) {
140 if (ref.isFeed(href)) {
141 preview = api.profile.html.preview(href)
142 } else if (href.includes('://')) {
143 preview = h('ProfilePreview', [
144 h('section', [
145 h('strong', [i18n('External Link'), ' ๐ŸŒ']), h('br'),
146 h('code', href)
147 ])
148 ])
149 }
150 }
151
152 if (preview) {
153 var rect = element.getBoundingClientRect()
154 var width = 510
155 var maxLeft = window.innerWidth - width
156 var maxTop = window.innerHeight - 100
157 var distanceFromRight = window.innerWidth - rect.right
158 var shouldDisplayBeside = rect.bottom > maxTop || rect.left < 50 || distanceFromRight < 50
159
160 if (shouldDisplayBeside && rect.bottom > 50) {
161 if (rect.right > maxLeft && (rect.left - width) < 0) {
162 // no room, just give up!
163 previewElement.set(null)
164 return
165 } else {
166 preview.style.top = `${Math.min(rect.top, maxTop)}px`
167 if (rect.right > maxLeft) {
168 preview.style.left = `${rect.left - width}px`
169 } else {
170 preview.style.left = `${rect.right + 5}px`
171 }
172 }
173 } else {
174 preview.style.top = `${rect.bottom + 5}px`
175 preview.style.left = `${Math.min(rect.left, maxLeft)}px`
176 }
177
178 previewElement.set(preview)
179 } else if (element !== false) {
180 previewElement.set(null)
181 }
182 })
183
184 catchLinks(container, (href, external, anchor) => {
185 if (external) {
186 electron.shell.openExternal(href)
187 } else if (ref.isBlob(href)) {
188 electron.shell.openExternal(api.blob.sync.url(href))
189 } else if (ref.isMsg(href)) {
190 getExternalHandler(href, (err, handler) => {
191 if (err) throw err
192 if (handler) {
193 handler(href)
194 } else {
195 api.app.navigate(href, anchor)
196 }
197 })
198 } else {
199 api.app.navigate(href, anchor)
200 }
201 })
202
203 return [container, previewElement]
204
205 // scoped
206
207 function getSubscribedChannelMenu () {
208 var channels = Array.from(subscribedChannels()).sort(localeCompare)
209
210 if (channels.length) {
211 return {
212 label: i18n('Channels'),
213 submenu: [
214 { label: i18n('Browse All'),
215 click () {
216 setView('/channels')
217 }
218 },
219 {type: 'separator'}
220 ].concat(channels.map(channel => {
221 return {
222 label: `#${channel}`,
223 click () {
224 setView(`#${channel}`)
225 }
226 }
227 }))
228 }
229 } else {
230 return {
231 label: i18n('Browse Channels'),
232 click () {
233 setView('/channels')
234 }
235 }
236 }
237 }
238
239 function dropTab (title, items) {
240 var element = h('a -drop', {
241 'ev-click': (ev) => {
242 var rects = element.getBoundingClientRect()
243 electron.remote.getCurrentWindow().webContents.getZoomFactor((factor) => {
244 var menu = electron.remote.Menu.buildFromTemplate(items.map(item => {
245 if (typeof item === 'function') {
246 return item()
247 } else if (item.separator) {
248 return { type: 'separator' }
249 } else {
250 return {
251 label: item[0],
252 click () {
253 setView(item[1])
254 }
255 }
256 }
257 }))
258 menu.popup(electron.remote.getCurrentWindow(), {
259 x: Math.round(rects.left * factor),
260 y: Math.round(rects.bottom * factor) + 4,
261 async: true
262 })
263 })
264 }
265 }, title)
266 return element
267 }
268
269 function setView (href, anchor) {
270 currentHover.cancel()
271 previewElement.set(null)
272 views.setView(href, anchor)
273 }
274
275 function getExternalHandler (key, cb) {
276 api.sbot.async.get(key, function (err, value) {
277 if (err) return cb(err)
278 cb(null, api.app.sync.externalHandler({key, value}))
279 })
280 }
281
282 function tab (name, view) {
283 var instance = views.get(view)
284 return h('a', {
285 'ev-click': function (ev) {
286 if (instance.pendingUpdates && instance.pendingUpdates() && instance.reload) {
287 instance.reload()
288 }
289 },
290 href: view,
291 classList: [
292 when(selected(view), '-selected')
293 ]
294 }, [
295 name,
296 when(instance.pendingUpdates, [
297 ' (', instance.pendingUpdates, ')'
298 ])
299 ])
300 }
301
302 function selected (view) {
303 return computed([views.currentView, view], (currentView, view) => {
304 return currentView === view
305 })
306 }
307}
308
309function overrideConfig (config) {
310 return {
311 'patchwork/config': {
312 gives: nest('config.sync.load'),
313 create: function (api) {
314 return nest('config.sync.load', () => config)
315 }
316 }
317 }
318}
319
320function addCommand (id, cb) {
321 return {
322 [`patchwork/command/${id}`]: {
323 gives: nest(id),
324 create: function (api) {
325 return nest(id, cb)
326 }
327 }
328 }
329}
330
331function localeCompare (a, b) {
332 return a.localeCompare(b)
333}
334

Built with git-ssb-web