git ssb

2+

mixmix / ticktack



Tree: d2ef619dd77cafe0c113840831438790127a1875

Files: d2ef619dd77cafe0c113840831438790127a1875 / app / page / home.js

5277 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')
9
10exports.gives = nest('app.page.home')
11
12exports.needs = nest({
13 'about.html.image': 'first',
14 'about.obs.name': 'first',
15 'app.html.nav': 'first',
16 'feed.pull.public': 'first',
17 'history.sync.push': 'first',
18 'keys.sync.id': 'first',
19 'message.sync.unbox': 'first',
20 'message.html.markdown': 'first'
21})
22
23function firstLine (text) {
24 if(text.length < 80 && !~text.indexOf('\n')) return text
25
26 var line = ''
27 var lineNumber = 0
28 while (line.length === 0) {
29 const rawLine = text.split('\n')[lineNumber]
30 line = trimLeadingMentions(rawLine)
31
32 lineNumber++
33 }
34
35 var sample = line.substring(0, 80)
36 if (hasBrokenLink(sample))
37 sample = sample + line.substring(81).match(/[^\)]*\)/)[0]
38
39 const ellipsis = (sample.length < line.length) ? '...' : ''
40 return sample + ellipsis
41}
42
43function trimLeadingMentions (str) {
44 return str.replace(/^(\s*\[@[^\)]+\)\s*)*/, '')
45 // deletes any number of pattern " [@...) " from start of line
46}
47
48function hasBrokenLink (str) {
49 return /\[[^\]]*\]\([^\)]*$/.test(str)
50 // matches "[name](start_of_link"
51}
52
53exports.create = (api) => {
54 return nest('app.page.home', home)
55
56 function home (location) {
57 // location here can expected to be: { page: 'home' }
58
59 var container = h('div.container', [])
60
61 function subject (msg) {
62 const { subject, text } = msg.value.content
63
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', '► '),
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 pull(
128 api.feed.pull.public({reverse: true, limit: 1000}),
129 pull.collect(function (err, messages) {
130
131 var threads = messages
132 .map(function (data) {
133 if(isObject(data.value.content)) return data
134 return api.message.sync.unbox(data)
135 })
136 .filter(Boolean)
137 .reduce(threadReduce, null)
138
139 const privateUpdatesSection = h('section.updates -directMessage', [
140 h('h2', 'Direct Messages'),
141 threadGroup(
142 threads,
143 threads.private,
144 function (_, msg) {
145 // NB: msg passed in is actually a 'thread', but only care about root msg
146 const myId = api.keys.sync.id()
147
148 return msg.value.content.recps
149 .map(link => isString(link) ? link : link.link)
150 .filter(link => link !== myId)
151 .map(api.about.html.image)
152 },
153 { nameRecipients: true }
154 )
155 ])
156
157 const channelUpdatesSection = h('section.updates -channel', [
158 h('h2', 'Channels'),
159 threadGroup(
160 threads,
161 threads.channels,
162 ch => '#'+ch
163 )
164 ])
165
166 const groupUpdatesSection = h('section.updates -group', [
167 h('h2', 'Groups'),
168 'TODO: complete + enable when groups are live'
169 // threadGroup(
170 // threads,
171 // threads.groups,
172 // toName ...
173 // )
174 ])
175
176 container.appendChild(privateUpdatesSection)
177 container.appendChild(channelUpdatesSection)
178 container.appendChild(groupUpdatesSection)
179 })
180 )
181
182 return h('Page -home', [
183 h('h1', 'Home'),
184 api.app.html.nav(),
185 container
186 ])
187 }
188}
189
190

Built with git-ssb-web