git ssb

2+

mixmix / ticktack



Tree: d12d963d1dc2340bf7726e17d111c407fda58c7d

Files: d12d963d1dc2340bf7726e17d111c407fda58c7d / app / page / home.js

6077 bytesRaw
1const nest = require('depnest')
2const { h, computed } = require('mutant')
3const {threadReduce} = require('ssb-reduce-stream')
4const pull = require('pull-stream')
5const isObject = require('lodash/isObject')
6const isString = require('lodash/isString')
7const last = require('lodash/last')
8const get = require('lodash/get')
9const More = require('hypermore')
10exports.gives = nest('app.page.home')
11const morphdom = require('morphdom')
12const Next = require('pull-next')
13
14exports.needs = nest({
15 'about.html.image': 'first',
16 'about.obs.name': 'first',
17 'app.html.nav': 'first',
18 'sbot.pull.log': 'first',
19 'history.sync.push': 'first',
20 'keys.sync.id': 'first',
21 'message.sync.unbox': 'first',
22 'message.html.markdown': 'first',
23 'translations.sync.strings': 'first'
24})
25
26function firstLine (text) {
27 if(text.length < 80 && !~text.indexOf('\n')) return text
28
29 var line = ''
30 var lineNumber = 0
31 while (line.length === 0) {
32 const rawLine = text.split('\n')[lineNumber]
33 line = trimLeadingMentions(rawLine)
34
35 lineNumber++
36 }
37
38 var sample = line.substring(0, 80)
39 if (hasBrokenLink(sample))
40 sample = sample + line.substring(81).match(/[^\)]*\)/)[0]
41
42 return sample
43
44 function trimLeadingMentions (str) {
45 return str.replace(/^(\s*\[@[^\)]+\)\s*)*/, '')
46 // deletes any number of pattern " [@...) " from start of line
47 }
48
49 function hasBrokenLink (str) {
50 return /\[[^\]]*\]\([^\)]*$/.test(str)
51 // matches "[name](start_of_link"
52 }
53}
54
55exports.create = (api) => {
56 return nest('app.page.home', function (location) {
57 var strings = api.translations.sync.strings()
58 // location here can expected to be: { page: 'home' }
59
60 var container = h('div.container', [])
61
62 function subject (msg) {
63 const { subject, text } = msg.value.content
64 return api.message.html.markdown(firstLine(subject|| text))
65 }
66
67 function link(location) {
68 return {'ev-click': () => api.history.sync.push(location)}
69 }
70
71 function item (context, thread, opts = {}) {
72 if(!thread.value) return
73
74 const subjectEl = h('div.subject', [
75 opts.nameRecipients
76 ? h('div.recps', buildRecipientNames(thread).map(recp => h('div.recp', recp)))
77 : null,
78 subject(thread)
79 ])
80
81 const lastReply = thread.replies && last(thread.replies)
82 const replyEl = lastReply
83 ? h('div.reply', [
84 h('div.replySymbol', strings.replySymbol),
85 subject(lastReply)
86 ])
87 : null
88
89
90 // REFACTOR: move this to a template?
91 function buildRecipientNames (thread) {
92 const myId = api.keys.sync.id()
93
94 return thread.value.content.recps
95 .map(link => isString(link) ? link : link.link)
96 .filter(link => link !== myId)
97 .map(api.about.obs.name)
98 }
99
100 return h('div.thread', link(thread), [
101 h('div.context', context),
102 h('div.content', [
103 subjectEl,
104 replyEl
105 ])
106 ])
107 }
108
109 function threadGroup (threads, obj, toContext, opts) {
110 // threads = a state object for all the types of threads
111 // obj = a map of keys to root ids, where key (channel | group | concatenated list of pubkeys)
112 // toContext = fn that derives the context of the group
113 // opts = { nameRecipients }
114
115 var groupEl = h('div.threads')
116 for(var k in obj) {
117 var id = obj[k]
118 var thread = get(threads, ['roots', id])
119 if(thread && thread.value) {
120 var el = item(toContext(k, thread), thread, opts)
121 if(el) groupEl.appendChild(el)
122 }
123 }
124 return groupEl
125 }
126
127 var initial
128 try { initial = JSON.parse(localStorage.threadsState) }
129 catch (_) { }
130 var lastTimestamp = initial ? initial.last : Date.now()
131
132 var timer
133 function update (threadsState) {
134 clearTimeout(timer)
135 setTimeout(function () {
136 threadsState.last = lastTimestamp
137 localStorage.threadsState = JSON.stringify(threadsState)
138 }, 1000)
139 }
140
141 var threadsObs = More(
142 threadReduce,
143 pull(
144 Next(function () {
145 return api.sbot.pull.log({reverse: true, limit: 500, lte: lastTimestamp})
146 }),
147 pull.map(function (data) {
148 lastTimestamp = data.timestamp
149 if(isObject(data.value.content)) return data
150 return api.message.sync.unbox(data)
151 }),
152 pull.filter(Boolean)
153 ),
154 function render (threads) {
155 update(threads)
156 morphdom(container,
157 h('div.container', [
158 //private section
159 h('section.updates -directMessage', [
160 h('h2', strings.directMessages),
161 threadGroup(
162 threads,
163 threads.private,
164 function (_, msg) {
165 // NB: msg passed in is actually a 'thread', but only care about root msg
166 const myId = api.keys.sync.id()
167
168 return msg.value.content.recps
169 .map(link => isString(link) ? link : link.link)
170 .filter(link => link !== myId)
171 .map(api.about.html.image)
172 },
173 { nameRecipients: true }
174 )
175 ]),
176 //channels section
177 h('section.updates -channel', [
178 h('h2', strings.channels),
179 threadGroup(
180 threads,
181 threads.channels,
182 ch => '#'+ch
183 )
184 ]),
185 //group section
186 h('section.updates -group', [
187 h('h2', 'Groups'),
188 'TODO: complete + enable when groups are live'
189 // threadGroup(
190 // threads,
191 // threads.groups,
192 // toName ...
193 // )
194 ])
195 ])
196 )
197 return container
198 },
199 initial
200 )
201
202 return h('Page -home', [
203 h('h1', 'Home'),
204 api.app.html.nav(),
205 threadsObs,
206 h('button', {'ev-click': threadsObs.more}, [strings.showMore])
207 ])
208 })
209}
210
211

Built with git-ssb-web