git ssb

10+

Matt McKegg / patchwork



Tree: d86342bc5140f453a001261bcccfcde98f04df41

Files: d86342bc5140f453a001261bcccfcde98f04df41 / main-window.js

6612 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 30 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 mainElement = h('div.main', MutantMap(toCollection(views), (item) => {
91 return h('div.view', {
92 hidden: computed([item.key, currentView], (a, b) => a !== b)
93 }, [ item.value ])
94 }))
95
96 insertCss(require('./styles'))
97
98 var container = h(`MainWindow -${process.platform}`, {
99 events: {
100 click: catchLinks
101 }
102 }, [
103 h('div.top', [
104 h('span.history', [
105 h('a', {
106 'ev-click': goBack,
107 classList: [ when(canGoBack, '-active') ]
108 }, '<'),
109 h('a', {
110 'ev-click': goForward,
111 classList: [ when(canGoForward, '-active') ]
112 }, '>')
113 ]),
114 h('span.nav', [
115 tab('Public', '/public'),
116 tab('Private', '/private')
117 ]),
118 h('span.appTitle', ['Patchwork']),
119 h('span', [ searchBox ]),
120 h('span.nav', [
121 tab('Profile', id),
122 tab('Mentions', '/mentions')
123 ])
124 ]),
125 mainElement
126 ])
127
128 addSuggest(searchBox, (inputText, cb) => {
129 if (inputText[0] === '@') {
130 cb(null, getProfileSuggestions(inputText.slice(1)), {idOnly: true})
131 } else if (inputText[0] === '#') {
132 cb(null, getChannelSuggestions(inputText.slice(1)))
133 }
134 }, {cls: 'SuggestBox'})
135
136 return container
137
138 // scoped
139
140 function catchLinks (ev) {
141 if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.defaultPrevented) {
142 return true
143 }
144
145 var anchor = null
146 for (var n = ev.target; n.parentNode; n = n.parentNode) {
147 if (n.nodeName === 'A') {
148 anchor = n
149 break
150 }
151 }
152 if (!anchor) return true
153
154 var href = anchor.getAttribute('href')
155
156 if (href) {
157 var url = Url.parse(href)
158 if (url.host) {
159 electron.shell.openExternal(href)
160 } else if (href.charAt(0) === '&') {
161 electron.shell.openExternal(api.blob.sync.url(href))
162 } else if (href !== '#') {
163 setView(href)
164 }
165 }
166
167 ev.preventDefault()
168 ev.stopPropagation()
169 }
170
171 function tab (name, view) {
172 var instance = views.get(view)
173 lastViewed[view] = true
174 return h('a', {
175 'ev-click': function (ev) {
176 if (instance.pendingUpdates && instance.pendingUpdates() && instance.reload) {
177 instance.reload()
178 }
179 },
180 href: view,
181 classList: [
182 when(selected(view), '-selected')
183 ]
184 }, [
185 name,
186 when(instance.pendingUpdates, [
187 ' (', instance.pendingUpdates, ')'
188 ])
189 ])
190 }
191
192 function goBack () {
193 if (backHistory.length) {
194 canGoForward.set(true)
195 forwardHistory.push(currentView())
196 currentView.set(backHistory.pop())
197 canGoBack.set(backHistory.length > 0)
198 }
199 }
200
201 function goForward () {
202 if (forwardHistory.length) {
203 backHistory.push(currentView())
204 currentView.set(forwardHistory.pop())
205 canGoForward.set(forwardHistory.length > 0)
206 canGoBack.set(true)
207 }
208 }
209
210 function setView (view) {
211 if (!views.has(view)) {
212 views.put(view, renderPage(view))
213 }
214
215 if (lastViewed[view] !== true) {
216 lastViewed[view] = Date.now()
217 }
218
219 if (currentView() && lastViewed[currentView()] !== true) {
220 lastViewed[currentView()] = Date.now()
221 }
222
223 if (view !== currentView()) {
224 canGoForward.set(false)
225 canGoBack.set(true)
226 forwardHistory.length = 0
227 backHistory.push(currentView())
228 currentView.set(view)
229 }
230 }
231
232 function doSearch () {
233 var value = searchBox.value.trim()
234 if (value.startsWith('/') || value.startsWith('?') || value.startsWith('@') || value.startsWith('#') || value.startsWith('%')) {
235 if (value.startsWith('@') && value.length < 30) {
236 return // probably not a key
237 } else if (value.length > 2) {
238 setView(value)
239 }
240 } else if (value.trim()) {
241 if (value.length > 2) {
242 setView(`?${value.trim()}`)
243 }
244 } else {
245 setView('/public')
246 }
247 }
248
249 function selected (view) {
250 return computed([currentView, view], (currentView, view) => {
251 return currentView === view
252 })
253 }
254}
255
256function overrideConfig (config) {
257 return [{
258 gives: nest('config.sync.load'),
259 create: function (api) {
260 return nest('config.sync.load', () => config)
261 }
262 }]
263}
264

Built with git-ssb-web