git ssb

2+

mixmix / ticktack



Tree: 49bee78f3c1fec7174a11ee2c7277f5c9a7f47fc

Files: 49bee78f3c1fec7174a11ee2c7277f5c9a7f47fc / app / page / home.js

5276 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 return api.message.html.markdown(firstLine(subject|| text))
64 }
65
66 function link(location) {
67 return {'ev-click': () => api.history.sync.push(location)}
68 }
69
70 function item (context, thread, opts = {}) {
71 if(!thread.value) return
72
73 const subjectEl = h('div.subject', [
74 opts.nameRecipients
75 ? h('div.recps', buildRecipientNames(thread).map(recp => h('div.recp', recp)))
76 : null,
77 subject(thread)
78 ])
79
80 const lastReply = thread.replies && last(thread.replies)
81 const replyEl = lastReply
82 ? h('div.reply', [
83 h('div.replySymbol', '► '),
84 subject(lastReply)
85 ])
86 : null
87
88
89 // REFACTOR: move this to a template?
90 function buildRecipientNames (thread) {
91 const myId = api.keys.sync.id()
92
93 return thread.value.content.recps
94 .map(link => isString(link) ? link : link.link)
95 .filter(link => link !== myId)
96 .map(api.about.obs.name)
97 }
98
99 return h('div.thread', link(thread), [
100 h('div.context', context),
101 h('div.content', [
102 subjectEl,
103 replyEl
104 ])
105 ])
106 }
107
108 function threadGroup (threads, obj, toContext, opts) {
109 // threads = a state object for all the types of threads
110 // obj = a map of keys to root ids, where key ∈ (channel | group | concatenated list of pubkeys)
111 // toContext = fn that derives the context of the group
112 // opts = { nameRecipients }
113
114 var groupEl = h('div.threads')
115 for(var k in obj) {
116 var id = obj[k]
117 var thread = get(threads, ['roots', id])
118 if(thread && thread.value) {
119 var el = item(toContext(k, thread), thread, opts)
120 if(el) groupEl.appendChild(el)
121 }
122 }
123 return groupEl
124 }
125
126 pull(
127 api.feed.pull.public({reverse: true, limit: 1000}),
128 pull.collect(function (err, messages) {
129
130 var threads = messages
131 .map(function (data) {
132 if(isObject(data.value.content)) return data
133 return api.message.sync.unbox(data)
134 })
135 .filter(Boolean)
136 .reduce(threadReduce, null)
137
138 const privateUpdatesSection = h('section.updates -directMessage', [
139 h('h2', 'Direct Messages'),
140 threadGroup(
141 threads,
142 threads.private,
143 function (_, msg) {
144 // NB: msg passed in is actually a 'thread', but only care about root msg
145 const myId = api.keys.sync.id()
146
147 return msg.value.content.recps
148 .map(link => isString(link) ? link : link.link)
149 .filter(link => link !== myId)
150 .map(api.about.html.image)
151 },
152 { nameRecipients: true }
153 )
154 ])
155
156 const channelUpdatesSection = h('section.updates -channel', [
157 h('h2', 'Channels'),
158 threadGroup(
159 threads,
160 threads.channels,
161 ch => '#'+ch
162 )
163 ])
164
165 const groupUpdatesSection = h('section.updates -group', [
166 h('h2', 'Groups'),
167 'TODO: complete + enable when groups are live'
168 // threadGroup(
169 // threads,
170 // threads.groups,
171 // toName ...
172 // )
173 ])
174
175 container.appendChild(privateUpdatesSection)
176 container.appendChild(channelUpdatesSection)
177 container.appendChild(groupUpdatesSection)
178 })
179 )
180
181 return h('Page -home', [
182 h('h1', 'Home'),
183 api.app.html.nav(),
184 container
185 ])
186 }
187}
188
189

Built with git-ssb-web