git ssb

10+

Matt McKegg / patchwork



Tree: 72d67ba07462592fcaf199c43c5b8b6547032788

Files: 72d67ba07462592fcaf199c43c5b8b6547032788 / main-window.js

6859 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 computed = require('mutant/computed')
8var toCollection = require('mutant/dict-to-collection')
9var MutantDict = require('mutant/dict')
10var MutantMap = require('mutant/map')
11var Url = require('url')
12var insertCss = require('insert-css')
13var nest = require('depnest')
14var addSuggest = require('suggest-box')
15
16require('./lib/context-menu-and-spellcheck.js')
17
18module.exports = function (config) {
19 var sockets = combine(
20 overrideConfig(config),
21 require('./modules'),
22 require('./plugs'),
23 require('patchcore'),
24 require('./overrides')
25 )
26
27 var api = entry(sockets, nest({
28 'page.html.render': 'first',
29 'keys.sync.id': 'first',
30 'blob.sync.url': 'first',
31 'profile.async.suggest': 'first',
32 'channel.async.suggest': 'first'
33 }))
34
35 var renderPage = api.page.html.render
36 var id = api.keys.sync.id()
37 var getProfileSuggestions = api.profile.async.suggest()
38 var getChannelSuggestions = api.channel.async.suggest()
39
40 var searchTimer = null
41 var searchBox = h('input.search', {
42 type: 'search',
43 placeholder: 'word, @key, #channel',
44 'ev-suggestselect': (ev) => {
45 setView(ev.detail.id)
46 searchBox.value = ev.detail.id
47 }
48 })
49
50 searchBox.oninput = function () {
51 clearTimeout(searchTimer)
52 searchTimer = setTimeout(doSearch, 500)
53 }
54
55 searchBox.onfocus = function () {
56 if (searchBox.value) {
57 doSearch()
58 }
59 }
60
61 var forwardHistory = []
62 var backHistory = []
63
64 var views = MutantDict({
65 // preload tabs (and subscribe to update notifications)
66 '/public': renderPage('/public'),
67 '/private': renderPage('/private'),
68 [id]: renderPage(id),
69 '/mentions': renderPage('/mentions')
70 })
71
72 var lastViewed = {}
73 var defaultViews = views.keys()
74
75 // delete cached view after 5 mins of last seeing
76 setInterval(() => {
77 views.keys().forEach((view) => {
78 if (!defaultViews.includes(view)) {
79 if (lastViewed[view] !== true && Date.now() - lastViewed[view] > (5 * 60e3) && view !== currentView()) {
80 views.delete(view)
81 }
82 }
83 })
84 }, 60e3)
85
86 var canGoForward = Value(false)
87 var canGoBack = Value(false)
88 var currentView = Value('/public')
89
90 var viewCollection = toCollection(views)
91
92 var mainElement = h('div.main', MutantMap(viewCollection, (item) => {
93 return h('div.view', {
94 hidden: computed([item.key, currentView], (a, b) => a !== b)
95 }, [ item.value ])
96 }))
97
98 insertCss(require('./styles'))
99
100 var container = h(`MainWindow -${process.platform}`, {
101 events: {
102 click: catchLinks
103 }
104 }, [
105 h('div.top', [
106 h('span.history', [
107 h('a', {
108 'ev-click': goBack,
109 classList: [ when(canGoBack, '-active') ]
110 }, '<'),
111 h('a', {
112 'ev-click': goForward,
113 classList: [ when(canGoForward, '-active') ]
114 }, '>')
115 ]),
116 h('span.nav', [
117 tab('Public', '/public'),
118 tab('Private', '/private')
119 ]),
120 h('span.appTitle', ['Patchwork']),
121 h('span', [ searchBox ]),
122 h('span.nav', [
123 tab('Profile', id),
124 tab('Mentions', '/mentions')
125 ])
126 ]),
127 mainElement
128 ])
129
130 addSuggest(searchBox, (inputText, cb) => {
131 if (inputText[0] === '@') {
132 cb(null, getProfileSuggestions(inputText.slice(1)), {idOnly: true})
133 } else if (inputText[0] === '#') {
134 cb(null, getChannelSuggestions(inputText.slice(1)))
135 }
136 }, {cls: 'SuggestBox'})
137
138 return container
139
140 // scoped
141
142 function catchLinks (ev) {
143 if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.defaultPrevented) {
144 return true
145 }
146
147 var anchor = null
148 for (var n = ev.target; n.parentNode; n = n.parentNode) {
149 if (n.nodeName === 'A') {
150 anchor = n
151 break
152 }
153 }
154 if (!anchor) return true
155
156 var href = anchor.getAttribute('href')
157
158 if (href) {
159 var url = Url.parse(href)
160 if (url.host) {
161 electron.shell.openExternal(href)
162 } else if (href.charAt(0) === '&') {
163 electron.shell.openExternal(api.blob.sync.url(href))
164 } else if (href !== '#') {
165 setView(href)
166 }
167 }
168
169 ev.preventDefault()
170 ev.stopPropagation()
171 }
172
173 function tab (name, view) {
174 var instance = views.get(view)
175 lastViewed[view] = true
176 return h('a', {
177 'ev-click': function (ev) {
178 if (instance.pendingUpdates && instance.pendingUpdates() && instance.reload) {
179 instance.reload()
180 }
181 },
182 href: view,
183 classList: [
184 when(selected(view), '-selected')
185 ]
186 }, [
187 name,
188 when(instance.pendingUpdates, [
189 ' (', instance.pendingUpdates, ')'
190 ])
191 ])
192 }
193
194 function goBack () {
195 if (backHistory.length) {
196
197 canGoForward.set(true)
198 forwardHistory.push(currentView())
199
200 var view = backHistory.pop()
201 if (!views.has(view)) {
202 views.put(view, renderPage(view))
203 }
204
205 currentView.set(view)
206 canGoBack.set(backHistory.length > 0)
207 }
208 }
209
210 function goForward () {
211 if (forwardHistory.length) {
212 backHistory.push(currentView())
213
214 var view = forwardHistory.pop()
215 if (!views.has(view)) {
216 views.put(view, renderPage(view))
217 }
218
219 currentView.set(view)
220 canGoForward.set(forwardHistory.length > 0)
221 canGoBack.set(true)
222 }
223 }
224
225 function setView (view) {
226 if (!views.has(view)) {
227 views.put(view, renderPage(view))
228 }
229
230 if (lastViewed[view] !== true) {
231 lastViewed[view] = Date.now()
232 }
233
234 if (currentView() && lastViewed[currentView()] !== true) {
235 lastViewed[currentView()] = Date.now()
236 }
237
238 if (view !== currentView()) {
239 canGoForward.set(false)
240 canGoBack.set(true)
241 forwardHistory.length = 0
242 backHistory.push(currentView())
243 currentView.set(view)
244 }
245 }
246
247 function doSearch () {
248 var value = searchBox.value.trim()
249 if (value.startsWith('/') || value.startsWith('?') || value.startsWith('@') || value.startsWith('#') || value.startsWith('%')) {
250 if (value.startsWith('@') && value.length < 30) {
251 return // probably not a key
252 } else if (value.length > 2) {
253 setView(value)
254 }
255 } else if (value.trim()) {
256 if (value.length > 2) {
257 setView(`?${value.trim()}`)
258 }
259 } else {
260 setView('/public')
261 }
262 }
263
264 function selected (view) {
265 return computed([currentView, view], (currentView, view) => {
266 return currentView === view
267 })
268 }
269}
270
271function overrideConfig (config) {
272 return [{
273 gives: nest('config.sync.load'),
274 create: function (api) {
275 return nest('config.sync.load', () => config)
276 }
277 }]
278}
279

Built with git-ssb-web